diff --git a/pycrate_corenet/AuC.db b/pycrate_corenet/AuC.db index af1da6c..460360f 100644 --- a/pycrate_corenet/AuC.db +++ b/pycrate_corenet/AuC.db @@ -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; diff --git a/pycrate_corenet/ServerAuC.py b/pycrate_corenet/ServerAuC.py index 65dda56..6886658 100644 --- a/pycrate_corenet/ServerAuC.py +++ b/pycrate_corenet/ServerAuC.py @@ -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