corenet: extend AuC to support TUAK and a 5G SIDF
This commit is contained in:
parent
c8868b3050
commit
d107081f87
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue