corenet: extend AuC to support TUAK and a 5G SIDF

This commit is contained in:
p1-bmu 2021-02-16 14:47:47 +01:00
parent c8868b3050
commit d107081f87
3 changed files with 446 additions and 107 deletions

View File

@ -5,12 +5,30 @@
# file used by ServerAuC.py
#
# csv'style file, with the following fields:
# IMSI (digits);
# K (hex, 16-bytes authentication key);
# 2G algorithm id (int, 0 for Milenage, 1, 2 and 3 for comp128v1, 2 and 3)
# SQN (int, Milenage authentication counter, -1 if Milenage is not supported)
# OP (hex, optional, subscriber-specific OP parameter)
# - IMSI (digits);
# - K (hex), subscriber authentication key,
# 16-bytes when using Milenage or comp128
# 16 or 32-bytes when using TUAK;
# - algorithm id (uint)
# 0 for Milenage
# 1 for comp123v1 (only for 2G / 3G)
# 2 for comp128v2 (only for 2G / 3G)
# 3 for comp128v3 (only for 2G / 3G)
# 4 for TUAK;
# - SQN (int), Milenage / TUAK authentication counter,
# -1 if Milenage / TUAK is not supported (i.e. comp128 is used) or counter is to be disabled (i.e. for testing purpose)
# >= 0 otherwise and will be incremented;
# - OP / TOP (hex, optional),
# subscriber-specific OP parameter (16 bytes for Milenage) or TOP parameter (32 bytes for TUAK);
#
# examples:
# Milenage:
#001010000000001;0123456789abcdef0123456789abcdef;0;1;
# Milenage with subscriber-specific OP:
#001010000000002;0123456789abcdef0123456789abcdef;0;1;00112233445566778899aabbccddeeff;
# TUAK:
#001010000000003;0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef;4;1;
# TUAK with subscriber-specific TOP:
#001010000000004;0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef;4;1;0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef;
# COMP128-3:
#001010000000005;0123456789abcdef0123456789abcdef;3;-1;

View File

@ -38,9 +38,10 @@ HOWTO:
2) To use the AuC (in case your IMSI is '001010000000001'):
>>> MyAuc = AuC()
>>> vec2g = MyAuc.make_2g_vector('001010000000001')
>>> vec3g = MyAuc.make_3g_vector('001010000000001', AMF='\x00\x00')
>>> vec4g = MyAuc.make_4g_vector('001010000000001', SN_ID='\x00\xf1\x10', AMF='\x80\x00')
>>> MyAuc.synchronize('001010000000001', RAND=16*'\0', AMF='\0\0', AUTS=14*'\0')
>>> vec3g = MyAuc.make_3g_vector('001010000000001', AMF=b'\x00\x00')
>>> vec4g = MyAuc.make_4g_vector('001010000000001', SN_ID=b'\x00\xf1\x10', AMF=b'\x80\x00')
>>> vec5g = MyAuc.make_5g_vector('001010000000001', SNName=b'5G:mnc001.mcc001.3gppnetwork.org', AMF=b'\x80\x00')
>>> MyAuc.synch_sqn('001010000000001', RAND=16*b'\x00', AMF=b'\x00\x00', AUTS=14*b'\x00')
3) That's all !
"""
@ -48,12 +49,14 @@ HOWTO:
# filtering exports
__all__ = ['AuC']
import os
import time as timemod
from binascii import hexlify, unhexlify
from struct import pack, unpack
from time import sleep
# random generator
try:
from os import urandom as genrand
except ImportError:
@ -62,46 +65,78 @@ except ImportError:
_rand = SystemRandom()
genrand = lambda n: uint_to_bytes(_rand.getrandbits(8*n), 8*n)
# CryptoMobile imports
try:
from CryptoMobile.Milenage import Milenage, xor_buf, conv_C2, conv_C3, conv_A2
# other available conversion functions are:
# conv_C4, conv_C5, conv_A2, conv_A3, conv_A4, conv_A7
from pycomp128 import comp128v1, comp128v2, comp128v3
from CryptoMobile.Milenage import Milenage
from CryptoMobile.TUAK import TUAK
from pycomp128 import comp128v1, comp128v2, comp128v3
from CryptoMobile.utils import xor_buf, CMException
from CryptoMobile.conv import (
conv_102_C2, conv_102_C3, conv_401_A2, conv_501_A2, conv_501_A4
)
except ImportError as err:
print('CryptoMobile library is required for Milenage and Comp-128')
print('CryptoMobile library is required for Milenage, TUAK and Comp-128')
raise(err)
# CryptoMobile with cryptography backend import
try:
from CryptoMobile.ECIES import ECIES_HN
except ImportError as err:
print('CryptoMobile library with cryptography backend is required for ECIES / SIDF')
raise(err)
# local utilities
from .utils import *
class AuC:
"""3GPP Authentication Centre
"""3GPP Authentication Centre (AuC), ARPF and SIDF
use the AuC.db file with (IMSI, K, SQN[, OP]) records to produce 2G, 3G or
4G auth vectors, and resynchronize SQN
Use the AuC.db file with (IMSI, K, SQN[, OP]) records to then produce 2G, 3G,
4G or 5G authentication vectors, and resynchronize SQN. It supports all standard
authentication algorithms: comp123v1, v2, v3, Milenage and TUAK.
Set SIDF home-network private keys for profile A and / or B and use it to decrypt
concealed subscriber 5G identities.
"""
# verbosity level: list of log types to be displayed
DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
# path to the local AuC.db file, should be overwritten
AUC_DB_PATH = os.path.dirname(os.path.abspath( __file__ )) + os.sep
#AUC_DB_PATH = 'C:\Python27\Lib\sitepackages\pycrate_corenet\'
# when rewriting the AuC.db, do a back-up of the last version of the file
DO_BACKUP = True
# MNO OP diversification parameter
# The AuC supports also a per-subscriber OP, to be set optionally in the AuC.db database
OP = b'ffffffffffffffff'
# MNO OP (Milenage) and TOP (TUAK) diversification parameter
# The AuC supports also a per-subscriber OP / TOP, to be set optionally in the AuC.db database
OP = b'ffffffffffffffff'
TOP = b'ffffffffffffffffffffffffffffffff'
# SQN incrementation when a resynch is required by a USIM card
SQN_SYNCH_STEP = 2
# PLMN restriction for returning 4G vectors
# PLMN restriction for returning 4G and 5G vectors
# provide a list of allowed PLMN, or None for disabling the filter
#PLMN_FILTER = ['20869']
PLMN_FILTER = None
# SIDF ECIES private keys
SIDF_ECIES_K = {
# private keys for ECIES profile A (protection scheme index 1),
# each key indexed by an uint8
1 : {},
# private keys for ECIES profile B (protection scheme index 2),
# each key indexed by an uint8
2 : {},
}
# ECIES profile indexes
_SIDF_ECIES_PROF = {
1 : 'A',
2 : 'B',
}
def __init__(self):
"""start the AuC
@ -110,7 +145,7 @@ class AuC:
parse it into self.db (dict), containing IMSI: (K, SQN [, OP])
IMSI: string of digits
K : 16 bytes buffer
ALG2: integer (0, 1, 2 or 3, identifies the 2G auth algorithm)
ALG : integer (0, 1, 2, 3 or 4, identifies the auth algorithm)
SQN : unsigned integer
OP : subscriber specific OP, distinct from self.OP, optional field
"""
@ -124,13 +159,13 @@ class AuC:
fields = line[:-1].split(';')
IMSI = str( fields[0] )
K = unhexlify( fields[1].encode('ascii') )
ALG2 = int( fields[2] )
ALG = int( fields[2] )
SQN = int( fields[3] )
if len(fields) > 4 and len(fields[4]) == 32:
OP = unhexlify( fields[4].encode('ascii') )
else:
OP = None
self.db[IMSI] = [ K, ALG2, SQN, OP ]
self.db[IMSI] = [ K, ALG, SQN, OP ]
self._log('INF', 'AuC.db file opened: %i record(s) found' % len(self.db))
# close the file
db_fd.close()
@ -139,10 +174,28 @@ class AuC:
raise(err)
self._save_required = False
#
# initiatlize the Milenage algo with the AuC-defined OP
# initialize the Milenage algo with the AuC-defined OP
self.Milenage = Milenage(self.OP)
# initialize the TUAK algo with the AuC-defined TOP
self.TUAK = TUAK(self.TOP)
# initialize the SIDF function
self._init_sidf()
#
self._log('DBG', 'AuC started')
self._log('DBG', 'AuC / ARPF / SIDF started')
def _init_sidf(self):
self.SIDF_ECIES = {}
for prof in (1, 2):
# only support std ECIES profile A and B
if prof not in self.SIDF_ECIES_K:
continue
self.SIDF_ECIES[prof] = {}
for ind, privk in self.SIDF_ECIES_K[prof].items():
# it will raise in case the key is invalid
self.SIDF_ECIES[prof][ind] = ECIES_HN(
privk,
self._SIDF_ECIES_PROF[prof]
)
def _log(self, logtype='DBG', msg=''):
if logtype in self.DEBUG:
@ -182,14 +235,14 @@ class AuC:
indexes = list(self.db.keys())
indexes.sort()
for IMSI in indexes:
K, ALG2, SQN, OP = self.db[IMSI]
K, ALG, SQN, OP = self.db[IMSI]
if OP is not None:
# OP additional parameter
file_db.write('%s;%s;%i;%i;%s;\n'\
% (IMSI, hexlify(K).decode('ascii'), ALG2, SQN, hexlify(OP).decode('ascii')))
% (IMSI, hexlify(K).decode('ascii'), ALG, SQN, hexlify(OP).decode('ascii')))
else:
file_db.write('%s;%s;%i;%i;\n'\
% (IMSI, hexlify(K).decode('ascii'), ALG2, SQN))
% (IMSI, hexlify(K).decode('ascii'), ALG, SQN))
file_db.close()
self._log('INF', 'current db saved to AuC.db file')
@ -199,34 +252,50 @@ class AuC:
"""
return a 2G authentication vector "triplet":
RAND [16 bytes], RES [4 bytes], Kc [8 bytes]
or None if the IMSI is not defined in the db or ALG2 is invalid
or None if the IMSI is not defined in the db or ALG is invalid
RAND can be passed as argument
"""
# lookup db for authentication Key and algorithm id for IMSI
try:
K, ALG2, SQN, OP = self.db[IMSI]
K_ALG_SQN_OP = self.db[IMSI]
except KeyError:
self._log('WNG', '[make_2g_vector] IMSI %s not present in AuC.db' % IMSI)
return None
if len(K_ALG_SQN_OP) == 4:
K, ALG, SQN, OP = K_ALG_SQN_OP
else:
K, ALG, SQN = K_ALG_SQN_OP
OP = None
#
if not RAND:
RAND = genrand(16)
#
if ALG2 == 0:
if ALG == 0:
# Milenage, adapted to 2G
if OP is not None:
XRES, CK, IK, AK = self.Milenage.f2345(RAND, K, OP)
else:
XRES, CK, IK, AK = self.Milenage.f2345(RAND, K)
RES, Ck = conv_C2(XRES), conv_C3(CK, IK)
else:
if ALG2 == 1:
RES, Ck = comp128v1(K, RAND)
elif ALG2 == 2:
RES, Ck = comp128v2(K, RAND)
elif ALG2 == 3:
RES, Ck = comp128v3(K, RAND)
RES, Kc = conv_102_C2(XRES), conv_102_C3(CK, IK)
elif ALG == 4:
# TUAK, adapted to 2G
if OP is not None:
# which is actually TOP, for TUAK
XRES, CK, IK, AK = self.TUAK.f2345(RAND, K, OP)
else:
XRES, CK, IK, AK = self.TUAK.f2345(RAND, K)
RES, Kc = conv_102_C2(XRES), conv_102_C3(CK, IK)
else:
# COMP128
if ALG == 1:
RES, Kc = comp128v1(K, RAND)
elif ALG == 2:
RES, Kc = comp128v2(K, RAND)
elif ALG == 3:
RES, Kc = comp128v3(K, RAND)
else:
# invalid ALG
return None
#
# return auth vector
@ -239,45 +308,60 @@ class AuC:
'''
return a 3G authentication vector "quintuplet":
RAND [16 bytes], XRES [8 bytes], AUTN [16 bytes], CK [16 bytes], IK [16 bytes]
or None if the IMSI is not defined in the db or does not support Milenage
or None if the IMSI is not defined in the db or does not support Milenage or TUAK
RAND can be passed as argument
'''
# lookup db for authentication Key and counter for IMSI
try:
K_ALG2_SQN_OP = self.db[IMSI]
K_ALG_SQN_OP = self.db[IMSI]
except Exception:
self._log('WNG', '[make_3g_vector] IMSI %s not present in AuC.db' % IMSI)
return None
#
K, ALG2, SQN, OP = K_ALG2_SQN_OP
if len(K_ALG_SQN_OP) == 4:
K, ALG, SQN, OP = K_ALG_SQN_OP
else:
K, ALG, SQN = K_ALG_SQN_OP
OP = None
#
if SQN == -1:
# Milenage not supported
self._log('WNG', '[make_3g_vector] IMSI %s does not support Milenage' % IMSI)
# Milenage / TUAK not supported
self._log('WNG', '[make_3g_vector] IMSI %s does not support Milenage / TUAK' % IMSI)
return None
#
# increment SQN counter in the db
K_ALG2_SQN_OP[2] += 1
K_ALG_SQN_OP[2] += 1
self._save_required = True
#
# pack SQN from integer to a 48-bit buffer
SQNb = b'\0\0' + pack('>I', SQN)
SQNb = pack('>Q', SQN)[2:]
#
# generate challenge if necessary
if RAND is None:
RAND = genrand(16)
# compute Milenage functions
if OP is not None:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP )
AUTN = xor_buf( SQNb, AK ) + AMF + MAC_A
#
if ALG == 0:
# compute Milenage functions
if OP is not None:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP )
else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF )
elif ALG == 4:
# compute TUAK functions
if OP is not None:
XRES, CK, IK, AK = self.TUAK.f2345( K, RAND, OP )
MAC_A = self.TUAK.f1( K, RAND, SQNb, AMF, OP )
else:
XRES, CK, IK, AK = self.TUAK.f2345( K, RAND )
MAC_A = self.TUAK.f1( K, RAND, SQNb, AMF )
else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF )
AUTN = xor_buf( SQNb, AK ) + AMF + MAC_A
# invalid ALG
return None
#
AUTN = xor_buf( SQNb, AK ) + AMF + MAC_A
#
# return auth vector
self._log('DBG', '[make_3g_vector] IMSI %s, SQN %i: RAND %s, XRES %s, AUTN %s, CK %s, IK %s'\
% (IMSI, SQN, hexlify(RAND).decode('ascii'), hexlify(XRES).decode('ascii'),
@ -289,7 +373,7 @@ class AuC:
"""
return a 4G authentication vector "quadruplet":
RAND [16 bytes], XRES [8 bytes], AUTN [16 bytes], KASME [32 bytes]
or None if the IMSI is not defined in the db or does not support Milenage
or None if the IMSI is not defined in the db or does not support Milenage or TUAK
or SN_ID is invalid or not allowed
SN_ID is the serving network identity, bcd-encoded buffer
@ -304,44 +388,55 @@ class AuC:
#
# lookup db for authentication Key and counter for IMSI
try:
K_ALG2_SQN_OP = self.db[IMSI]
K_ALG_SQN_OP = self.db[IMSI]
except Exception:
self._log('WNG', '[make_4g_vector] IMSI %s not present in AuC.db' % IMSI)
return None
if len(K_ALG_SQN_OP) == 4:
K, ALG, SQN, OP = K_ALG_SQN_OP
else:
K, ALG, SQN = K_ALG_SQN_OP
OP = None
#
K, ALG2, SQN, OP = K_ALG2_SQN_OP
#
if SQN == -1:
# Milenage not supported
self._log('WNG', '[make_4g_vector] IMSI %s does not support Milenage' % IMSI)
if ALG not in (0, 4):
# Milenage / TUAK not supported
self._log('WNG', '[make_4g_vector] IMSI %s does not support Milenage or TUAK' % IMSI)
return None
#
# increment SQN counter in the db
K_ALG2_SQN_OP[2] += 1
self._save_required = True
if SQN >= 0:
K_ALG_SQN_OP[2] += 1
self._save_required = True
#
# pack SQN from integer to a 48-bit buffer
SQNb = b'\0\0' + pack('>I', SQN)
SQNb = pack('>Q', SQN)[2:]
#
# generate challenge
if RAND is None:
RAND = genrand(16)
# compute Milenage functions
if OP is not None:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP )
SQN_X_AK = xor_buf( SQNb, AK )
AUTN = SQN_X_AK + AMF + MAC_A
#
if ALG == 0:
# compute Milenage functions
if OP is not None:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP )
else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF )
else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF )
SQN_X_AK = xor_buf( SQNb, AK )
AUTN = SQN_X_AK + AMF + MAC_A
# ALG == 4, compute TUAK functions
if OP is not None:
XRES, CK, IK, AK = self.TUAK.f2345( K, RAND, OP )
MAC_A = self.TUAK.f1( K, RAND, SQNb, AMF, OP )
else:
XRES, CK, IK, AK = self.TUAK.f2345( K, RAND )
MAC_A = self.TUAK.f1( K, RAND, SQNb, AMF )
#
SQN_X_AK = xor_buf( SQNb, AK )
AUTN = SQN_X_AK + AMF + MAC_A
# convert to LTE master key
KASME = conv_A2(CK, IK, SN_ID, SQN_X_AK)
KASME = conv_401_A2(CK, IK, SN_ID, SQN_X_AK)
#
# return auth vector
self._log('DBG', '[make_4g_vector] IMSI %s, SQN %i, SN_ID %s: RAND %s, XRES %s, AUTN %s, KASME %s'\
% (IMSI, SQN, hexlify(SN_ID).decode('ascii'), hexlify(RAND).decode('ascii'),
@ -349,6 +444,86 @@ class AuC:
hexlify(KASME).decode('ascii')))
return RAND, XRES, AUTN, KASME
def make_5g_vector(self, IMSI, SNName, AMF=b'\x80\x00', RAND=None):
"""
return a 5G authentication vector "quadruplet":
RAND [16 bytes], XRES* [8 bytes], AUTN [16 bytes], KAUSF [32 bytes]
or None if the IMSI is not defined in the db or does not support Milenage or TUAK
or SNName is invalid or not allowed
SNName is the serving network name, ascii-encoded bytes buffer
RAND can be passed as argument
"""
if not isinstance(SNName, bytes_types) or not 32 <= len(SNName) <= 255:
self._log('WNG', '[make_5g_vector] SNName invalid, %s' % SNName.decode('ascii'))
return None
elif self.PLMN_FILTER is not None:
# extract MCC, MNC from SNName (e.g. "5G:mnc012.mcc345.3gppnetwork.org")
snname_parts = SNName.split(':')[1].split('.')
mcc, mnc = snname_parts[1][3:], snname_parts[0][3:]
if mcc + mnc not in self.PLMN_FILTER:
self._log('WNG', '[make_5g_vector] SNName not allowed, %s' % SNName.decode('ascii'))
return None
#
# lookup db for authentication Key and counter for IMSI
try:
K_ALG_SQN_OP = self.db[IMSI]
except Exception:
self._log('WNG', '[make_5g_vector] IMSI %s not present in AuC.db' % IMSI)
return None
if len(K_ALG_SQN_OP) == 4:
K, ALG, SQN, OP = K_ALG_SQN_OP
else:
K, ALG, SQN = K_ALG_SQN_OP
OP = None
#
if ALG not in (0, 4):
# Milenage / TUAK not supported
self._log('WNG', '[make_4g_vector] IMSI %s does not support Milenage or TUAK' % IMSI)
return None
#
# increment SQN counter in the db
if SQN >= 0:
K_ALG_SQN_OP[2] += 1
self._save_required = True
#
# pack SQN from integer to a 48-bit buffer
SQNb = pack('>Q', SQN)[2:]
#
# generate challenge
if RAND is None:
RAND = genrand(16)
#
if ALG == 0:
# compute Milenage functions
if OP is not None:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP )
else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF )
else:
# ALG == 4, compute TUAK functions
if OP is not None:
XRES, CK, IK, AK = self.TUAK.f2345( K, RAND, OP )
MAC_A = self.TUAK.f1( K, RAND, SQNb, AMF, OP )
else:
XRES, CK, IK, AK = self.TUAK.f2345( K, RAND )
MAC_A = self.TUAK.f1( K, RAND, SQNb, AMF )
#
SQN_X_AK = xor_buf( SQNb, AK )
AUTN = SQN_X_AK + AMF + MAC_A
# convert to AUSF master key
KAUSF = conv_501_A2(CK, IK, SNName, SQN_X_AK)
XRESstar = conv_501_A4(CK, IK, SNName, RAND, XRES)
#
# return auth vector
self._log('DBG', '[make_4g_vector] IMSI %s, SQN %i, SNName %s: RAND %s, XRES* %s, AUTN %s, KASME %s'\
% (IMSI, SQN, hexlify(SNName).decode('ascii'), hexlify(RAND).decode('ascii'),
hexlify(XRESstar).decode('ascii'), hexlify(AUTN).decode('ascii'),
hexlify(KAUSF).decode('ascii')))
return RAND, XRESstar, AUTN, KAUSF
def synch_sqn(self, IMSI, RAND, AUTS):
"""
synchronize the local counter SQN with AUTS provided by the USIM
@ -359,44 +534,191 @@ class AuC:
"""
# lookup db for authentication Key and counter for IMSI
try:
K_ALG2_SQN_OP = self.db[IMSI]
K_ALG_SQN_OP = self.db[IMSI]
except Exception:
self._log('WNG', '[synch_sqn] IMSI %s not present in AuC.db' % IMSI)
return None
if len(K_ALG_SQN_OP) == 4:
K, ALG, SQN, OP = K_ALG_SQN_OP
else:
K, ALG, SQN = K_ALG_SQN_OP
OP = None
#
K, ALG2, SQN, OP = K_ALG2_SQN_OP
#
if K_ALG2_SQN_OP[2] == -1:
if ALG not in (0, 4):
# Milenage not supported
self._log('WNG', '[make_3g_vector] IMSI %s does not support Milenage' % IMSI)
self._log('WNG', '[make_3g_vector] IMSI %s does not support Milenage or TUAK' % IMSI)
return None
#
# 33.102, section 6.3.3, for resynch, AMF is always null (0x0000)
AMF = b'\0\0'
#
# compute Milenage functions and unmask SQN
if OP is not None:
AK = self.Milenage.f5star( K, RAND, OP )
SQN_MS = xor_buf( AUTS[0:6], AK )
MAC_S = self.Milenage.f1star( K, RAND, SQN_MS, AMF, OP )
SQN_MSi = unpack('>Q', b'\0\0' + SQN_MS)[0]
if ALG == 0:
# compute Milenage functions
if OP is not None:
AK = self.Milenage.f5star( K, RAND, OP )
SQN_MS = xor_buf( AUTS[0:6], AK )
MAC_S = self.Milenage.f1star( K, RAND, SQN_MS, AMF, OP )
else:
AK = self.Milenage.f5star( K, RAND )
SQN_MS = xor_buf( AUTS[0:6], AK )
MAC_S = self.Milenage.f1star( K, RAND, SQN_MS, AMF )
else:
AK = self.Milenage.f5star( K, RAND )
SQN_MS = xor_buf( AUTS[0:6], AK )
MAC_S = self.Milenage.f1star( K, RAND, SQN_MS, AMF )
SQN_MSi = unpack('>Q', b'\0\0' + SQN_MS)[0]
# ALG == 4, compute TUAK functions
if OP is not None:
AK = self.TUAK.f5star( K, RAND, OP )
SQN_MS = xor_buf( AUTS[0:6], AK )
MAC_S = self.TUAK.f1star( K, RAND, SQN_MS, AMF, OP )
else:
AK = self.TUAK.f5star( K, RAND )
SQN_MS = xor_buf( AUTS[0:6], AK )
MAC_S = self.TUAK.f1star( K, RAND, SQN_MS, AMF )
#
# unmask SQN
SQN_MSi = unpack('>Q', b'\0\0' + SQN_MS)[0]
#
self._log('DBG', '[synch_sqn] USIM resynchronization, SQN_MS %i, MAC_S %s'\
% (SQN_MSi, hexlify(MAC_S).decode('ascii')))
#
# authenticate the USIM
if MAC_S != AUTS[6:14]:
self._log('WNG', '[synch_sqn] IMSI %s, USIM authentication failure' % IMSI)
return 1
#
# resynchronize local SQN value
K_ALG2_SQN_OP[2] = SQN_MSi + self.SQN_SYNCH_STEP
K_ALG_SQN_OP[2] = SQN_MSi + self.SQN_SYNCH_STEP
self._save_required = True
self._log('DBG', '[synch_sqn] IMSI %s, SQN resynchronized to %i' % (IMSI, K_ALG2_SQN_OP[2]))
self._log('DBG', '[synch_sqn] IMSI %s, SQN resynchronized to %i' % (IMSI, K_ALG_SQN_OP[2]))
return 0
def sidf_unconceal(self, prof, hnprivkid, ephpubk, cipht, mac):
"""
unconceal the cipher text `cipht` according to ECIES `prof` 1 (profile A)
or 2 (profile B), home network private key index `hnprivkid`, ephemeral
public key `ephpubk`, after verifying the `mac`.
return None on error or the unconceal clear-text value bytes buffer (i.e.
the clear-text 5G subscriber identity)
"""
if prof not in (1, 2) or hnprivkid not in self.SIDF_ECIES[prof] \
or not 32 <= len(ephpubk) <= 33 or len(mac) != 8:
self._log('WNG', '[sidf_unconceal] invalid parameter')
return None
#
ecies_hn = self.SIDF_ECIES[prof][hnprivkid]
return ecies_hn.unprotect(ephpubk, cipht, mac)
def test():
AuC.OP = b'ffffffffffffffff'
AuC.TOP = b'ffffffffffffffffffffffffffffffff'
auc = AuC()
imsi = '001010000000001'
rand = 16 * b'\x00'
K = unhexlify('0123456789abcdef0123456789abcdef')
#
# comp128-1
auc.db[imsi] = [K, 1, -1]
assert(
auc.make_2g_vector(imsi, rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'2\xae\x086',
b'5\x934\x86\x18\xa0\x94\x00')
)
#
# comp128-2
auc.db[imsi] = [K, 2, -1]
assert(
auc.make_2g_vector(imsi, rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'2a!)',
b'a]\xe3+\xe7\xdd \x00')
)
#
# comp128-3
auc.db[imsi] = [K, 3, -1]
assert(
auc.make_2g_vector(imsi, rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'2a!)',
b'a]\xe3+\xe7\xdd#\xba')
)
auc.db[imsi] = [K, 0, 0]
#
# milenage
assert(
auc.make_2g_vector(imsi, rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x80\x9a\x82\x0e',
b'd\xc1\xeb\x0c\xebRz\x13')
)
assert(
auc.make_3g_vector(imsi, b'\x00\x00', rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'@*\x8f\xc0\x81\xcd8f',
b"\x97e\x98\xdb\xc9\x01\x00\x00\xcd\x16Nu\xb6'GH",
b"/\xff\x18\xbc\x1d'\x12\x12\xa8\x80K\x1e\xbf\xe5O\xed",
b'U\xd6\t\x00\x9eu\xfa\xdbS-U\xc8\x99<G@')
)
assert(
auc.make_4g_vector(imsi, b'\x00\xf1\x10', b'\x80\x00', rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'@*\x8f\xc0\x81\xcd8f',
b'\x97e\x98\xdb\xc9\x00\x80\x00:\x07~5\xf9\x0f\xd5\xda',
b'@\xf0\x07"H\x96\x88\xe4\x8d\\\x86f,\xaaJQc\xc1/\x99[\xd8\xaa\xd4i\xd6{\xf8\x1c\xfc\x8d9')
)
assert(
auc.make_5g_vector(imsi, b'5G:mnc001.mcc001.3gppnetwork.org', b'\x80\x00', rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x02YS4d\xdb\xbc\xcfa\xab\x9c\x8a\xda\xcf\xacR',
b'\x97e\x98\xdb\xc9\x03\x80\x00\xb2\x9c\n\xf4\xe5\x8a\x10x',
b'\xaal4h\x00W\x82\xbd\x1f\xf6\x91\x83\xd2\xcc\x03\x8d\xddk7\xf6\xce\xf8i\xd8\xd3\xa9\x1b\x8c\xba\xc9\r\xe9')
)
assert(
auc.synch_sqn(imsi, rand, b'Qx\xcb4\xa3Tf^\xa2\xaeN5;$') == 0
)
#
# TUAK
auc.db[imsi] = [K, 4, 0]
assert(
auc.make_2g_vector(imsi, rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x96\xb5c\xa1',
b'\x9bk\x97\xb5\xfd\xe5\xc1}')
)
assert(
auc.make_3g_vector(imsi, b'\x00\x00', rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x12p#\x8aKHQ]',
b'o\x1b6:\x9e\xeb\x00\x00K\x9c#\x9f\x80\xaf\xaaC',
b'J\x91\xf8\xc7\xd9x;\xe1\xa6\x10b~o\xe9U\x8d',
b'_U\xd5\xf1\xaa\x95\xcf\x86\xea(\xb5\xad\xb5\x96e\xd8')
)
assert(
auc.make_4g_vector(imsi, b'\x00\xf1\x10', b'\x80\x00', rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x12p#\x8aKHQ]',
b'o\x1b6:\x9e\xea\x80\x00\x8c\xf4\xcfz\xe3\x91\xd7W',
b"q\x83Z\xbel\x96%^\x81f\xcf\xab)\x07B\x93\nsl\xae'oAULu\x15\xb1\x12\x9f\x1ap")
)
assert(
auc.make_5g_vector(imsi, b'5G:mnc001.mcc001.3gppnetwork.org', b'\x80\x00', rand) == (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x0bzY,\xd60\xd0X\xfe\xac\xf2DQ\xb07<',
b'o\x1b6:\x9e\xe9\x80\x00V\x92\xae\x87\xab@\xbe\x8b',
b'\x87\x8d\x03\x95\xe4\x8b\x17\xef%CM_U4\x05`\xc6\xac\xa4w\xc4\xfaH\xdfS\x84\x01\xa3-9\xf1\x91')
)
assert(
auc.synch_sqn(imsi, rand, b'h\xe4\xf2T\xc7\xa9v\xe1Gy\xcf,#\x9f') == 0
)
#
# SIDF
# set a HN private key for profile A
AuC.SIDF_ECIES_K[1][0] = b'0\xfd\xa5\x0321y\xbe\xb2#\x8d\xbb\x85\x84\xe4\xb3\xffb\xb9\xdd\x85\xf3\x18N\x89!7\x15\xd3\x7f2X'
auc = AuC()
assert(
auc.sidf_unconceal(1, 0,
b'\x82\xcb\xb7\xb5\x00u\xc5/\xbd\xd8\xa4.\xbc|\x9ad,\x17\xa8(\xfdu\xd1\x7f\x01[::\xea\x97\xfay',
b'\xb8\xc3c{\xe4',
b'\xac\xeeXw\xca!\x04\xc6') == b'\x00\x00\x00\x00\x10'
)

View File

@ -54,8 +54,7 @@ except ImportError as err:
# conversion function for security context
try:
from CryptoMobile.Milenage import conv_C2, conv_C3, conv_C4, conv_C5, \
conv_A2, conv_A3, conv_A4, conv_A7
from CryptoMobile.conv import *
except ImportError as err:
print('CryptoMobile library required for CorenetServer')
print('check on github: https://github.com/P1sec/CryptoMobile')