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 # file used by ServerAuC.py
# #
# csv'style file, with the following fields: # csv'style file, with the following fields:
# IMSI (digits); # - IMSI (digits);
# K (hex, 16-bytes authentication key); # - K (hex), subscriber authentication key,
# 2G algorithm id (int, 0 for Milenage, 1, 2 and 3 for comp128v1, 2 and 3) # 16-bytes when using Milenage or comp128
# SQN (int, Milenage authentication counter, -1 if Milenage is not supported) # 16 or 32-bytes when using TUAK;
# OP (hex, optional, subscriber-specific OP parameter) # - 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: # examples:
# Milenage:
#001010000000001;0123456789abcdef0123456789abcdef;0;1; #001010000000001;0123456789abcdef0123456789abcdef;0;1;
# Milenage with subscriber-specific OP:
#001010000000002;0123456789abcdef0123456789abcdef;0;1;00112233445566778899aabbccddeeff; #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'): 2) To use the AuC (in case your IMSI is '001010000000001'):
>>> MyAuc = AuC() >>> MyAuc = AuC()
>>> vec2g = MyAuc.make_2g_vector('001010000000001') >>> vec2g = MyAuc.make_2g_vector('001010000000001')
>>> vec3g = MyAuc.make_3g_vector('001010000000001', AMF='\x00\x00') >>> vec3g = MyAuc.make_3g_vector('001010000000001', AMF=b'\x00\x00')
>>> vec4g = MyAuc.make_4g_vector('001010000000001', SN_ID='\x00\xf1\x10', AMF='\x80\x00') >>> vec4g = MyAuc.make_4g_vector('001010000000001', SN_ID=b'\x00\xf1\x10', AMF=b'\x80\x00')
>>> MyAuc.synchronize('001010000000001', RAND=16*'\0', AMF='\0\0', AUTS=14*'\0') >>> 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 ! 3) That's all !
""" """
@ -48,12 +49,14 @@ HOWTO:
# filtering exports # filtering exports
__all__ = ['AuC'] __all__ = ['AuC']
import os import os
import time as timemod import time as timemod
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from struct import pack, unpack from struct import pack, unpack
from time import sleep from time import sleep
# random generator
try: try:
from os import urandom as genrand from os import urandom as genrand
except ImportError: except ImportError:
@ -62,46 +65,78 @@ except ImportError:
_rand = SystemRandom() _rand = SystemRandom()
genrand = lambda n: uint_to_bytes(_rand.getrandbits(8*n), 8*n) genrand = lambda n: uint_to_bytes(_rand.getrandbits(8*n), 8*n)
# CryptoMobile imports
try: try:
from CryptoMobile.Milenage import Milenage, xor_buf, conv_C2, conv_C3, conv_A2 from CryptoMobile.Milenage import Milenage
# other available conversion functions are: from CryptoMobile.TUAK import TUAK
# conv_C4, conv_C5, conv_A2, conv_A3, conv_A4, conv_A7 from pycomp128 import comp128v1, comp128v2, comp128v3
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: 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) 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 * from .utils import *
class AuC: 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 Use the AuC.db file with (IMSI, K, SQN[, OP]) records to then produce 2G, 3G,
4G auth vectors, and resynchronize SQN 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 # verbosity level: list of log types to be displayed
DEBUG = ('ERR', 'WNG', 'INF', 'DBG') 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 = 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 # when rewriting the AuC.db, do a back-up of the last version of the file
DO_BACKUP = True DO_BACKUP = True
# MNO OP diversification parameter # MNO OP (Milenage) and TOP (TUAK) diversification parameter
# The AuC supports also a per-subscriber OP, to be set optionally in the AuC.db database # The AuC supports also a per-subscriber OP / TOP, to be set optionally in the AuC.db database
OP = b'ffffffffffffffff' OP = b'ffffffffffffffff'
TOP = b'ffffffffffffffffffffffffffffffff'
# SQN incrementation when a resynch is required by a USIM card # SQN incrementation when a resynch is required by a USIM card
SQN_SYNCH_STEP = 2 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 # provide a list of allowed PLMN, or None for disabling the filter
#PLMN_FILTER = ['20869'] #PLMN_FILTER = ['20869']
PLMN_FILTER = None 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): def __init__(self):
"""start the AuC """start the AuC
@ -110,7 +145,7 @@ class AuC:
parse it into self.db (dict), containing IMSI: (K, SQN [, OP]) parse it into self.db (dict), containing IMSI: (K, SQN [, OP])
IMSI: string of digits IMSI: string of digits
K : 16 bytes buffer 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 SQN : unsigned integer
OP : subscriber specific OP, distinct from self.OP, optional field OP : subscriber specific OP, distinct from self.OP, optional field
""" """
@ -124,13 +159,13 @@ class AuC:
fields = line[:-1].split(';') fields = line[:-1].split(';')
IMSI = str( fields[0] ) IMSI = str( fields[0] )
K = unhexlify( fields[1].encode('ascii') ) K = unhexlify( fields[1].encode('ascii') )
ALG2 = int( fields[2] ) ALG = int( fields[2] )
SQN = int( fields[3] ) SQN = int( fields[3] )
if len(fields) > 4 and len(fields[4]) == 32: if len(fields) > 4 and len(fields[4]) == 32:
OP = unhexlify( fields[4].encode('ascii') ) OP = unhexlify( fields[4].encode('ascii') )
else: else:
OP = None 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)) self._log('INF', 'AuC.db file opened: %i record(s) found' % len(self.db))
# close the file # close the file
db_fd.close() db_fd.close()
@ -139,10 +174,28 @@ class AuC:
raise(err) raise(err)
self._save_required = False 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) 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=''): def _log(self, logtype='DBG', msg=''):
if logtype in self.DEBUG: if logtype in self.DEBUG:
@ -182,14 +235,14 @@ class AuC:
indexes = list(self.db.keys()) indexes = list(self.db.keys())
indexes.sort() indexes.sort()
for IMSI in indexes: for IMSI in indexes:
K, ALG2, SQN, OP = self.db[IMSI] K, ALG, SQN, OP = self.db[IMSI]
if OP is not None: if OP is not None:
# OP additional parameter # OP additional parameter
file_db.write('%s;%s;%i;%i;%s;\n'\ 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: else:
file_db.write('%s;%s;%i;%i;\n'\ 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() file_db.close()
self._log('INF', 'current db saved to AuC.db file') self._log('INF', 'current db saved to AuC.db file')
@ -199,34 +252,50 @@ class AuC:
""" """
return a 2G authentication vector "triplet": return a 2G authentication vector "triplet":
RAND [16 bytes], RES [4 bytes], Kc [8 bytes] 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 RAND can be passed as argument
""" """
# lookup db for authentication Key and algorithm id for IMSI # lookup db for authentication Key and algorithm id for IMSI
try: try:
K, ALG2, SQN, OP = self.db[IMSI] K_ALG_SQN_OP = self.db[IMSI]
except KeyError: except KeyError:
self._log('WNG', '[make_2g_vector] IMSI %s not present in AuC.db' % IMSI) self._log('WNG', '[make_2g_vector] IMSI %s not present in AuC.db' % IMSI)
return None 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: if not RAND:
RAND = genrand(16) RAND = genrand(16)
# #
if ALG2 == 0: if ALG == 0:
# Milenage, adapted to 2G
if OP is not None: if OP is not None:
XRES, CK, IK, AK = self.Milenage.f2345(RAND, K, OP) XRES, CK, IK, AK = self.Milenage.f2345(RAND, K, OP)
else: else:
XRES, CK, IK, AK = self.Milenage.f2345(RAND, K) XRES, CK, IK, AK = self.Milenage.f2345(RAND, K)
RES, Ck = conv_C2(XRES), conv_C3(CK, IK) RES, Kc = conv_102_C2(XRES), conv_102_C3(CK, IK)
else: elif ALG == 4:
if ALG2 == 1: # TUAK, adapted to 2G
RES, Ck = comp128v1(K, RAND) if OP is not None:
elif ALG2 == 2: # which is actually TOP, for TUAK
RES, Ck = comp128v2(K, RAND) XRES, CK, IK, AK = self.TUAK.f2345(RAND, K, OP)
elif ALG2 == 3:
RES, Ck = comp128v3(K, RAND)
else: 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 None
# #
# return auth vector # return auth vector
@ -239,45 +308,60 @@ class AuC:
''' '''
return a 3G authentication vector "quintuplet": return a 3G authentication vector "quintuplet":
RAND [16 bytes], XRES [8 bytes], AUTN [16 bytes], CK [16 bytes], IK [16 bytes] 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 RAND can be passed as argument
''' '''
# lookup db for authentication Key and counter for IMSI # lookup db for authentication Key and counter for IMSI
try: try:
K_ALG2_SQN_OP = self.db[IMSI] K_ALG_SQN_OP = self.db[IMSI]
except Exception: except Exception:
self._log('WNG', '[make_3g_vector] IMSI %s not present in AuC.db' % IMSI) self._log('WNG', '[make_3g_vector] IMSI %s not present in AuC.db' % IMSI)
return None return None
# if len(K_ALG_SQN_OP) == 4:
K, ALG2, SQN, OP = K_ALG2_SQN_OP K, ALG, SQN, OP = K_ALG_SQN_OP
else:
K, ALG, SQN = K_ALG_SQN_OP
OP = None
# #
if SQN == -1: if SQN == -1:
# Milenage not supported # Milenage / TUAK 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 / TUAK' % IMSI)
return None return None
# #
# increment SQN counter in the db # increment SQN counter in the db
K_ALG2_SQN_OP[2] += 1 K_ALG_SQN_OP[2] += 1
self._save_required = True self._save_required = True
#
# pack SQN from integer to a 48-bit buffer # 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 # generate challenge if necessary
if RAND is None: if RAND is None:
RAND = genrand(16) RAND = genrand(16)
#
# compute Milenage functions if ALG == 0:
if OP is not None: # compute Milenage functions
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP ) if OP is not None:
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP ) XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP )
AUTN = xor_buf( SQNb, AK ) + AMF + MAC_A 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: else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND ) # invalid ALG
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF ) return None
AUTN = xor_buf( SQNb, AK ) + AMF + MAC_A #
AUTN = xor_buf( SQNb, AK ) + AMF + MAC_A
#
# return auth vector # return auth vector
self._log('DBG', '[make_3g_vector] IMSI %s, SQN %i: RAND %s, XRES %s, AUTN %s, CK %s, IK %s'\ 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'), % (IMSI, SQN, hexlify(RAND).decode('ascii'), hexlify(XRES).decode('ascii'),
@ -289,7 +373,7 @@ class AuC:
""" """
return a 4G authentication vector "quadruplet": return a 4G authentication vector "quadruplet":
RAND [16 bytes], XRES [8 bytes], AUTN [16 bytes], KASME [32 bytes] 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 or SN_ID is invalid or not allowed
SN_ID is the serving network identity, bcd-encoded buffer 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 # lookup db for authentication Key and counter for IMSI
try: try:
K_ALG2_SQN_OP = self.db[IMSI] K_ALG_SQN_OP = self.db[IMSI]
except Exception: except Exception:
self._log('WNG', '[make_4g_vector] IMSI %s not present in AuC.db' % IMSI) self._log('WNG', '[make_4g_vector] IMSI %s not present in AuC.db' % IMSI)
return None 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 ALG not in (0, 4):
# # Milenage / TUAK not supported
if SQN == -1: self._log('WNG', '[make_4g_vector] IMSI %s does not support Milenage or TUAK' % IMSI)
# Milenage not supported
self._log('WNG', '[make_4g_vector] IMSI %s does not support Milenage' % IMSI)
return None return None
# #
# increment SQN counter in the db # increment SQN counter in the db
K_ALG2_SQN_OP[2] += 1 if SQN >= 0:
self._save_required = True K_ALG_SQN_OP[2] += 1
self._save_required = True
#
# pack SQN from integer to a 48-bit buffer # pack SQN from integer to a 48-bit buffer
SQNb = b'\0\0' + pack('>I', SQN) SQNb = pack('>Q', SQN)[2:]
# #
# generate challenge # generate challenge
if RAND is None: if RAND is None:
RAND = genrand(16) RAND = genrand(16)
#
# compute Milenage functions if ALG == 0:
if OP is not None: # compute Milenage functions
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP ) if OP is not None:
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP ) XRES, CK, IK, AK = self.Milenage.f2345( K, RAND, OP )
SQN_X_AK = xor_buf( SQNb, AK ) MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF, OP )
AUTN = SQN_X_AK + AMF + MAC_A else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND )
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF )
else: else:
XRES, CK, IK, AK = self.Milenage.f2345( K, RAND ) # ALG == 4, compute TUAK functions
MAC_A = self.Milenage.f1( K, RAND, SQNb, AMF ) if OP is not None:
SQN_X_AK = xor_buf( SQNb, AK ) XRES, CK, IK, AK = self.TUAK.f2345( K, RAND, OP )
AUTN = SQN_X_AK + AMF + MAC_A 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 # 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 # return auth vector
self._log('DBG', '[make_4g_vector] IMSI %s, SQN %i, SN_ID %s: RAND %s, XRES %s, AUTN %s, KASME %s'\ 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'), % (IMSI, SQN, hexlify(SN_ID).decode('ascii'), hexlify(RAND).decode('ascii'),
@ -349,6 +444,86 @@ class AuC:
hexlify(KASME).decode('ascii'))) hexlify(KASME).decode('ascii')))
return RAND, XRES, AUTN, KASME 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): def synch_sqn(self, IMSI, RAND, AUTS):
""" """
synchronize the local counter SQN with AUTS provided by the USIM 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 # lookup db for authentication Key and counter for IMSI
try: try:
K_ALG2_SQN_OP = self.db[IMSI] K_ALG_SQN_OP = self.db[IMSI]
except Exception: except Exception:
self._log('WNG', '[synch_sqn] IMSI %s not present in AuC.db' % IMSI) self._log('WNG', '[synch_sqn] IMSI %s not present in AuC.db' % IMSI)
return None 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 ALG not in (0, 4):
#
if K_ALG2_SQN_OP[2] == -1:
# Milenage not supported # 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 return None
# #
# 33.102, section 6.3.3, for resynch, AMF is always null (0x0000) # 33.102, section 6.3.3, for resynch, AMF is always null (0x0000)
AMF = b'\0\0' AMF = b'\0\0'
# #
# compute Milenage functions and unmask SQN if ALG == 0:
if OP is not None: # compute Milenage functions
AK = self.Milenage.f5star( K, RAND, OP ) if OP is not None:
SQN_MS = xor_buf( AUTS[0:6], AK ) AK = self.Milenage.f5star( K, RAND, OP )
MAC_S = self.Milenage.f1star( K, RAND, SQN_MS, AMF, OP ) SQN_MS = xor_buf( AUTS[0:6], AK )
SQN_MSi = unpack('>Q', b'\0\0' + SQN_MS)[0] 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: else:
AK = self.Milenage.f5star( K, RAND ) # ALG == 4, compute TUAK functions
SQN_MS = xor_buf( AUTS[0:6], AK ) if OP is not None:
MAC_S = self.Milenage.f1star( K, RAND, SQN_MS, AMF ) AK = self.TUAK.f5star( K, RAND, OP )
SQN_MSi = unpack('>Q', b'\0\0' + SQN_MS)[0] 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'\ self._log('DBG', '[synch_sqn] USIM resynchronization, SQN_MS %i, MAC_S %s'\
% (SQN_MSi, hexlify(MAC_S).decode('ascii'))) % (SQN_MSi, hexlify(MAC_S).decode('ascii')))
#
# authenticate the USIM # authenticate the USIM
if MAC_S != AUTS[6:14]: if MAC_S != AUTS[6:14]:
self._log('WNG', '[synch_sqn] IMSI %s, USIM authentication failure' % IMSI) self._log('WNG', '[synch_sqn] IMSI %s, USIM authentication failure' % IMSI)
return 1 return 1
#
# resynchronize local SQN value # 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._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 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 # conversion function for security context
try: try:
from CryptoMobile.Milenage import conv_C2, conv_C3, conv_C4, conv_C5, \ from CryptoMobile.conv import *
conv_A2, conv_A3, conv_A4, conv_A7
except ImportError as err: except ImportError as err:
print('CryptoMobile library required for CorenetServer') print('CryptoMobile library required for CorenetServer')
print('check on github: https://github.com/P1sec/CryptoMobile') print('check on github: https://github.com/P1sec/CryptoMobile')