Esempio n. 1
0
 def __init__(self, ADM, serial_number='000'):
     # prepare data to write into the card
     if not len(serial_number) == 3 or not serial_number.isdigit():
         raise (Exception('serial: 3-digits required'))
     self.ICCID = ICCID_pre + serial_number
     self.ICCID += str(compute_luhn(self.ICCID))
     self.IMSI = IMSI_pre + serial_number
     self.K = Ki_pre + serial_number
     self.Milenage = Milenage(OP)
     self.OPc = make_OPc(self.K, OP)
     # verify parameters
     if len(self.K) != 16 or len(self.OPc) != 16:
         raise (Exception('K / OPc: 16-bytes buffer required'))
     #
     # write data on the card
     u = UICC()
     program_files(u, ADM, self.ICCID, self.IMSI, self.K, self.OPc)
     u.disconnect()
     #
     if self.test_identification() != 0:
         return
     #
     self._auth = 0
     if self.test_authentication() != 0:
         return
     #
     # and print results
     print(
         '[+] sysmoUSIM-SJS1 card personalization done and tested successfully:'
     )
     print('ICCID ; IMSI ; K ; OPc')
     print('%s;%s;0x%s;0x%s' %
           (self.ICCID, self.IMSI, hexlify(self.K), hexlify(self.OPc)))
Esempio n. 2
0
 def __init__(self, ADM, serial_number='000'):
     # prepare data to write into the card
     if not len(serial_number) == 3 or not serial_number.isdigit():
         raise(Exception('serial: 3-digits required'))
     self.ICCID      = ICCID_pre + serial_number
     self.ICCID     += str(compute_luhn(self.ICCID))
     self.IMSI       = IMSI_pre + serial_number
     self.K          = Ki_pre + serial_number
     self.Milenage   = Milenage(OP)
     self.OPc        = make_OPc(self.K, OP)
     # verify parameters
     if len(self.K) != 16 or len(self.OPc) != 16:
         raise(Exception('K / OPc: 16-bytes buffer required'))
     #
     # write data on the card
     u = UICC()
     program_files(u, ADM, self.ICCID, self.IMSI, self.K, self.OPc)
     u.disconnect()
     #
     if self.test_identification() != 0:
         return
     #
     self._auth = 0
     if self.test_authentication() != 0:
         return
     #
     # and print results
     print('[+] sysmoUSIM-SJS1 card personalization done and tested successfully:')
     print('ICCID ; IMSI ; K ; OPc')
     print('%s;%s;0x%s;0x%s' % (self.ICCID, self.IMSI, hexlify(self.K), hexlify(self.OPc)))
Esempio n. 3
0
 def __init__(self):
     """start the AuC
     
     open AuC.db file
     parse it into self.db (dict), containing IMSI: (K, SQN [, OP])
         IMSI: string of digits
         K   : 16 bytes buffer
         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
     """
     self.db = {}
     try:
         # get 3G authentication database AuC.db
         db_fd = open('%sAuC.db' % self.AUC_DB_PATH, 'r')
         # parse it into a dict object with IMSI as key
         for line in db_fd.readlines():
             if line[0] != '#' and line.count(';') >= 3:
                 fields = line[:-1].split(';')
                 IMSI   = str( fields[0] )
                 K      = unhexlify( fields[1].encode('ascii') )
                 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, ALG, SQN, OP ]
         self._log('INF', 'AuC.db file opened: %i record(s) found' % len(self.db))
         # close the file
         db_fd.close()
     except Exception as err:
         self._log('ERR', 'unable to read AuC.db, path: %s' % self.AUC_DB_PATH)
         raise(err)
     self._save_required = False
     #
     # 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 / ARPF / SIDF started')
Esempio n. 4
0
def milenage_testset_4():
    K = b'\x9eYD\xae\xa9K\x81\x16\\\x82\xfb\xf9\xf3-\xb7Q'
    RAND = b"\xce\x83\xdb\xc5J\xc0'J\x15|\x17\xf8\r\x01{\xd6"
    SQN = b'\x0b`J\x81\xec\xa8'
    AMF = b'\x9e\t'
    OP = b'"0\x14\xc5\x80f\x94\xc0\x07\xca\x1e\xee\xf5\x7f\x00O'
    return make_OPc(K, OP) == b'\xa6JPz\xe1\xa2\xa9\x8b\xb8\x8e\xb4!\x015\xdc\x87' and \
    Milenage(OP).f1(K, RAND, SQN, AMF) == Milenage(OPnull).f1(K, RAND, SQN, AMF, OP) == b't\xa5\x82 \xcb\xa8LI' and \
    Milenage(OP).f1star(K, RAND, SQN, AMF) == Milenage(OPnull).f1star(K, RAND, SQN, AMF, OP) == b'\xac,\xc7J\x96\x87\x187' and \
    Milenage(OP).f2345(K, RAND) == Milenage(OPnull).f2345(K, RAND, OP) == (b'\xf3e\xcdh<\xd9.\x96',
    b'\xe2\x03\xed\xb3\x97\x15t\xf5\xa9K\ra\xb8\x164]', b'\x0cE$\xad\xea\xc0A\xc4\xdd\x83\r \x85O\xc4k', b'\xf0\xb9\xc0\x8a\xd0.') and \
    Milenage(OP).f5star(K, RAND) == Milenage(OPnull).f5star(K, RAND, OP) == b'`\x85\xa8loc'
Esempio n. 5
0
def milenage_testset_3():
    K = b'\xfe\xc8k\xa6\xebp~\xd0\x89\x05u{\x1b\xb4K\x8f'
    RAND = b'\x9f|\x8d\x02\x1a\xcc\xf4\xdb!<\xcf\xf0\xc7\xf7\x1aj'
    SQN = b'\x9d\x02wY_\xfc'
    AMF = b'r\\'
    OP = b'\xdb\xc5\x9a\xdc\xb6\xf9\xa0\xefsTw\xb7\xfa\xdf\x83t'
    return make_OPc(K, OP) == b'\x10\x06\x02\x0f\nG\x8b\xf6\xb6\x99\xf1\\\x06.B\xb3' and \
    Milenage(OP).f1(K, RAND, SQN, AMF) == Milenage(OPnull).f1(K, RAND, SQN, AMF, OP) == b'\x9c\xab\xc3\xe9\x9b\xafr\x81' and \
    Milenage(OP).f1star(K, RAND, SQN, AMF) == Milenage(OPnull).f1star(K, RAND, SQN, AMF, OP) == b'\x95\x81K\xa2\xb3\x04C$' and \
    Milenage(OP).f2345(K, RAND) == Milenage(OPnull).f2345(K, RAND, OP) == (b'\x80\x11\xc4\x8c\x0c!N\xd2',
    b']\xbd\xbb)T\xe8\xf3\xcd\xe6e\xb0F\x17\x9aP\x98', b'Y\xa9-;Gj\x04CHpU\xcf\x88\xb20{', b'3HM\xc2\x13k') and \
    Milenage(OP).f5star(K, RAND) == Milenage(OPnull).f5star(K, RAND, OP) == b'\xde\xac\xdd\x84\x8c\xc6'
Esempio n. 6
0
def milenage_testset_2():
    K = b'\x03\x96\xeb1{m\x1c6\xf1\x9c\x1c\x84\xcdo\xfd\x16'
    RAND = b'\xc0\r`1\x03\xdc\xeeR\xc4G\x81\x19IB\x02\xe8'
    SQN = b'\xfd\x8e\xef@\xdf}'
    AMF = b'\xaf\x17'
    OP = b'\xffS\xba\xde\x17\xdf]Ny0s\xce\x9duy\xfa'
    return make_OPc(K, OP) == b'S\xc1Vq\xc6\nKs\x1cU\xb4\xa4A\xc0\xbd\xe2' and \
    Milenage(OP).f1(K, RAND, SQN, AMF) == Milenage(OPnull).f1(K, RAND, SQN, AMF, OP) == b']\xf5\xb3\x18\x07\xe2X\xb0' and \
    Milenage(OP).f1star(K, RAND, SQN, AMF) == Milenage(OPnull).f1star(K, RAND, SQN, AMF, OP) == b'\xa8\xc0\x16\xe5\x1e\xf4\xa3C' and \
    Milenage(OP).f2345(K, RAND) == Milenage(OPnull).f2345(K, RAND, OP) == (b'\xd3\xa6(\xed\x98\x86 \xf0',
    b'X\xc43\xffzp\x82\xac\xd4$"\x0f+g\xc5V', b'!\xa8\xc1\xf9)p*\xdb>s\x84\x88\xb9\xf5\xc5\xda', b'\xc4w\x83\x99_r') and \
    Milenage(OP).f5star(K, RAND) == Milenage(OPnull).f5star(K, RAND, OP) == b'0\xf1\x19pa\xc1'
Esempio n. 7
0
def milenage_testset_6():
    K = b'l8\xa1\x16\xac(\x0cEOY3.\xe3\\\x8cO'
    RAND = b'\xeedf\xbc\x96 ,ZUz\xbb\xef\xf8\xba\xbfc'
    SQN = b'AK\x98"!\x81'
    AMF = b'Dd'
    OP = b'\x1b\xa0\n\x1a|g\x00\xac\x8c?\xf3\xe9j\xd0\x87%'
    return make_OPc(K, OP) == b'8\x03\xefSc\xb9G\xc6\xaa\xa2%\xe5\x8f\xae94' and \
    Milenage(OP).f1(K, RAND, SQN, AMF) == Milenage(OPnull).f1(K, RAND, SQN, AMF, OP) == b'\x07\x8a\xdf\xb4\x88$\x1aW' and \
    Milenage(OP).f1star(K, RAND, SQN, AMF) == Milenage(OPnull).f1star(K, RAND, SQN, AMF, OP) == b'\x80$k\x8d\x01\x86\xbc\xf1' and \
    Milenage(OP).f2345(K, RAND) == Milenage(OPnull).f2345(K, RAND, OP) == (b'\x16\xc8#?\x05\xa0\xac(',
    b'?\x8cu\x87\xfe\x8eK#:\xf6v\xae\xde0\xba;', b'\xa7Fl\xc1\xe6\xb2\xa13}I\xd3\xb6n\x95\xd7\xb4', b'E\xb0\xf6\x9a\xb0l') and \
    Milenage(OP).f5star(K, RAND) == Milenage(OPnull).f5star(K, RAND, OP) == b'\x1fS\xcd+\x11\x13'
Esempio n. 8
0
def milenage_testset_5():
    K = b'J\xb1\xde\xb0\\\xa6\xce\xb0Q\xfc\x98\xe7}\x02j\x84'
    RAND = b't\xb0\xcd`1\xa1\xc83\x9b+l\xe2\xb8\xc4\xa1\x86'
    SQN = b'\xe8\x80\xa1\xb5\x80\xb6'
    AMF = b'\x9f\x07'
    OP = b'-\x16\xc5\xcd\x1f\xdfk"85\x84\xe3\xbe\xf2\xa8\xd8'
    return make_OPc(K, OP) == b'\xdc\xf0|\xbdQ\x85R\x90\xb9*\x07\xa9\x89\x1eR>' and \
    Milenage(OP).f1(K, RAND, SQN, AMF) == Milenage(OPnull).f1(K, RAND, SQN, AMF, OP) == b'I\xe7\x85\xdd\x12bn\xf2' and \
    Milenage(OP).f1star(K, RAND, SQN, AMF) == Milenage(OPnull).f1star(K, RAND, SQN, AMF, OP) == b'\x9e\x85y\x036\xbb?\xa2' and \
    Milenage(OP).f2345(K, RAND) == Milenage(OPnull).f2345(K, RAND, OP) == (b'X`\xfc\x1b\xce5\x1e~',
    b'vWvk7=\x1c!8\xf3\x07\xe3\xde\x92B\xf9', b"\x1cB\xe9`\xd8\x9b\x8f\xa9\x9f'D\xe0p\x8c\xcbS", b'1\xe1\x1a`\x91\x18') and \
    Milenage(OP).f5star(K, RAND) == Milenage(OPnull).f5star(K, RAND, OP) == b'\xfe%U\xe5J\xa9'
Esempio n. 9
0
def milenage_testset_1():
    K = b'F[\\\xe8\xb1\x99\xb4\x9f\xaa_\n.\xe28\xa6\xbc'
    RAND = b'#U<\xbe\x967\xa8\x9d!\x8a\xe6M\xaeG\xbf5'
    SQN = b'\xff\x9b\xb4\xd0\xb6\x07'
    AMF = b'\xb9\xb9'
    OP = b'\xcd\xc2\x02\xd5\x12> \xf6+mgj\xc7,\xb3\x18'
    #
    return make_OPc(K, OP) == b'\xcdc\xcbq\x95J\x9fNH\xa5\x99N7\xa0+\xaf' and \
    Milenage(OP).f1(K, RAND, SQN, AMF) == Milenage(OPnull).f1(K, RAND, SQN, AMF, OP) == b'J\x9f\xfa\xc3T\xdf\xaf\xb3' and \
    Milenage(OP).f1star(K, RAND, SQN, AMF) == Milenage(OPnull).f1star(K, RAND, SQN, AMF, OP) == b'\x01\xcf\xaf\x9e\xc4\xe8q\xe9' and \
    Milenage(OP).f2345(K, RAND) == Milenage(OPnull).f2345(K, RAND, OP) == (b'\xa5B\x11\xd5\xe3\xbaP\xbf',
    b'\xb4\x0b\xa9\xa3\xc5\x8b*\x05\xbb\xf0\xd9\x87\xb2\x1b\xf8\xcb', b'\xf7i\xbc\xd7Q\x04F\x04\x12vrq\x1cm4A', b'\xaah\x9cd\x83p') and \
    Milenage(OP).f5star(K, RAND) == Milenage(OPnull).f5star(K, RAND, OP) == b'E\x1e\x8b\xec\xa4;'
Esempio n. 10
0
def milenage_generate(opc: bytes, amf: bytes, k: bytes, sqn: bytes,
                      rand: bytes) -> Dict[str, bytes]:
    """Generate an MILENAGE Authentication Tuple."""
    m = Milenage(None)
    m.set_opc(opc)
    mac_a = m.f1(k, rand, sqn, amf)
    res, ck, ik, ak = m.f2345(k, rand)

    # AUTN = (SQN ^ AK) || AMF || MAC
    sqn_ak = xor_buf(sqn, ak)
    autn = b''.join([sqn_ak, amf, mac_a])

    return {'res': res, 'ck': ck, 'ik': ik, 'autn': autn}
Esempio n. 11
0
def milenage_auts(opc: bytes, k: bytes, rand: bytes,
                  auts: bytes) -> Optional[bytes]:
    """Validate AUTS. If successful, returns SQN_MS"""
    amf = b'\x00\x00'  # TS 33.102 Section 6.3.3
    m = Milenage(None)
    m.set_opc(opc)
    ak = m.f5star(k, rand)

    sqn_ak = auts[:6]
    sqn = xor_buf(sqn_ak, ak[:6])

    mac_s = m.f1star(k, rand, sqn, amf)
    if mac_s == auts[6:14]:
        return sqn
    else:
        return False
Esempio n. 12
0
class AuC:
    """3GPP Authentication Centre
    
    use the AuC.db file with (IMSI, K, SQN[, OP]) records to produce 2G, 3G or
    4G auth vectors, and resynchronize SQN
    """

    # verbosity level: list of log types to be displayed
    DEBUG = ('ERR', 'WNG', 'INF', 'DBG')

    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'

    # SQN incrementation when a resynch is required by a USIM card
    SQN_SYNCH_STEP = 2

    # PLMN restriction for returning 4G vectors
    # provide a list of allowed PLMN, or None for disabling the filter
    #PLMN_FILTER = ['20869']
    PLMN_FILTER = None

    def __init__(self):
        """start the AuC
        
        open AuC.db file
        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)
            SQN : unsigned integer
            OP  : subscriber specific OP, distinct from self.OP, optional field
        """
        self.db = {}
        try:
            # get 3G authentication database AuC.db
            db_fd = open('%sAuC.db' % self.AUC_DB_PATH, 'r')
            # parse it into a dict object with IMSI as key
            for line in db_fd.readlines():
                if line[0] != '#' and line.count(';') >= 3:
                    fields = line[:-1].split(';')
                    IMSI = str(fields[0])
                    K = unhexlify(fields[1].encode('ascii'))
                    ALG2 = int(fields[2])
                    SQN = int(fields[3])
                    if len(fields) > 4 and len(fields) == 32:
                        OP = unhexlify(fields[4].encode('ascii'))
                    else:
                        OP = None
                    self.db[IMSI] = [K, ALG2, SQN, OP]
            self._log('INF',
                      'AuC.db file opened: %i record(s) found' % len(self.db))
            # close the file
            db_fd.close()
        except Exception as err:
            self._log('ERR',
                      'unable to read AuC.db, path: %s' % self.AUC_DB_PATH)
            raise (err)
        self._save_required = False
        #
        # initiatlize the Milenage algo with the AuC-defined OP
        self.Milenage = Milenage(self.OP)
        #
        self._log('DBG', 'AuC started')

    def _log(self, logtype='DBG', msg=''):
        if logtype in self.DEBUG:
            log('[%s] [AuC] %s' % (logtype, msg))

    def save(self):
        """
        optionally save old AuC.db with timestamp suffix (if self.DO_BACKUP is set)
        write the current content of self.db dict into AuC.db, with updated SQN
        values
        """
        if not self._save_required:
            return

        T = timemod.strftime('20%y%m%d_%H%M', timemod.gmtime())

        # get header from original file AuC.db
        header = []
        file_db = open('%sAuC.db' % self.AUC_DB_PATH)
        for line in file_db:
            if line[0] == '#':
                header.append(line)
            else:
                break
        header = ''.join(header) + '\n'
        file_db.close()

        if self.DO_BACKUP:
            # save the last current version of AuC.db
            os.rename('%sAuC.db' % self.AUC_DB_PATH,
                      '%sAuC.%s.db' % (self.AUC_DB_PATH, T))
            self._log('DBG', 'old AuC.db saved with timestamp')

        # save the current self.db into a new AuC.db file
        file_db = open('%s/AuC.db' % self.AUC_DB_PATH, 'w')
        file_db.write(header)
        indexes = list(self.db.keys())
        indexes.sort()
        for IMSI in indexes:
            K, ALG2, 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')))
            else:
                file_db.write('%s;%s;%i;%i;\n'\
                    % (IMSI, hexlify(K).decode('ascii'), ALG2, SQN))
        file_db.close()
        self._log('INF', 'current db saved to AuC.db file')

    stop = save

    def make_2g_vector(self, IMSI, RAND=None):
        """
        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
        
        RAND can be passed as argument
        """
        # lookup db for authentication Key and algorithm id for IMSI
        try:
            K, ALG2, SQN, OP = self.db[IMSI]
        except KeyError:
            self._log('WNG',
                      '[make_2g_vector] IMSI %s not present in AuC.db' % IMSI)
            return None
        #
        if not RAND:
            RAND = genrand(16)
        #
        if ALG2 == 0:
            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)
            else:
                return None
        #
        # return auth vector
        self._log('DBG', '[make_2g_vector] IMSI %s: RAND %s, RES %s, Kc %s'\
                  % (IMSI, hexlify(RAND).decode('ascii'), hexlify(RES).decode('ascii'),
                     hexlify(Kc).decode('ascii')))
        return RAND, RES, Kc

    def make_3g_vector(self, IMSI, AMF=b'\0\0', RAND=None):
        '''
        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
        
        RAND can be passed as argument
        '''
        # lookup db for authentication Key and counter for IMSI
        try:
            K_ALG2_SQN_OP = self.db[IMSI]
        except:
            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 SQN == -1:
            # Milenage not supported
            self._log(
                'WNG',
                '[make_3g_vector] IMSI %s does not support Milenage' % IMSI)
            return None
        #
        # increment SQN counter in the db
        K_ALG2_SQN_OP[2] += 1
        self._save_required = True

        # pack SQN from integer to a 48-bit buffer
        SQNb = b'\0\0' + pack('>I', SQN)

        # 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
        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

        # 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'),
                     hexlify(AUTN).decode('ascii'), hexlify(CK).decode('ascii'),
                     hexlify(IK).decode('ascii')))
        return RAND, XRES, AUTN, CK, IK

    def make_4g_vector(self, IMSI, SN_ID, AMF=b'\x80\x00', RAND=None):
        """
        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 SN_ID is invalid or not allowed
        
        SN_ID is the serving network identity, bcd-encoded buffer
        RAND can be passed as argument
        """
        if not isinstance(SN_ID, bytes_types) or len(SN_ID) != 3:
            self._log(
                'WNG', '[make_4g_vector] SN_ID invalid, %s' %
                hexlify(SN_ID).decode('ascii'))
            return None
        elif self.PLMN_FILTER is not None and SN_ID not in self.PLMN_FILTER:
            self._log(
                'WNG', '[make_4g_vector] SN_ID not allowed, %s' %
                hexlify(SN_ID).decode('ascii'))
            return None
        #
        # lookup db for authentication Key and counter for IMSI
        try:
            K_ALG2_SQN_OP = self.db[IMSI]
        except:
            self._log('WNG',
                      '[make_4g_vector] IMSI %s not present in AuC.db' % IMSI)
            return 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)
            return None
        #
        # increment SQN counter in the db
        K_ALG2_SQN_OP[2] += 1
        self._save_required = True

        # pack SQN from integer to a 48-bit buffer
        SQNb = b'\0\0' + pack('>I', SQN)
        #
        # 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
        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

        # convert to LTE master key
        KASME = conv_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'),
                     hexlify(XRES).decode('ascii'), hexlify(AUTN).decode('ascii'),
                     hexlify(KASME).decode('ascii')))
        return RAND, XRES, AUTN, KASME

    def synch_sqn(self, IMSI, RAND, AUTS):
        """
        synchronize the local counter SQN with AUTS provided by the USIM
        in response to a given 3G or 4G authentication challenge (RAND, AMF)
        
        return 0 on successful synch, 1 on unsuccessful synch due to invalid AUTS
        or None if the IMSI is not defined in the db
        """
        # lookup db for authentication Key and counter for IMSI
        try:
            K_ALG2_SQN_OP = self.db[IMSI]
        except:
            self._log('WNG',
                      '[synch_sqn] IMSI %s not present in AuC.db' % IMSI)
            return None
        #
        K, ALG2, SQN, OP = K_ALG2_SQN_OP
        #
        if K_ALG2_SQN_OP[2] == -1:
            # Milenage not supported
            self._log(
                'WNG',
                '[make_3g_vector] IMSI %s does not support Milenage' % 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]
        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]

        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
        self._save_required = True
        self._log(
            'DBG', '[synch_sqn] IMSI %s, SQN resynchronized to %i' %
            (IMSI, K_ALG2_SQN_OP[2]))
        return 0
Esempio n. 13
0
class personalize(object):
    '''
    Class to program sysmo-USIM-SJS1 card
    takes the ADM code of the card (str of digits)
    and	a 3 digit serial number as argument to personalize the USIM card.
    
    Makes use of the fixed parameters in this file header:
    ICCID_pre, IMSI_pre, Ki_pre, OP,
    HPLMN, PLMNsel, SPN
    '''
    def __init__(self, ADM, serial_number='000'):
        # prepare data to write into the card
        if not len(serial_number) == 3 or not serial_number.isdigit():
            raise (Exception('serial: 3-digits required'))
        self.ICCID = ICCID_pre + serial_number
        self.ICCID += str(compute_luhn(self.ICCID))
        self.IMSI = IMSI_pre + serial_number
        self.K = Ki_pre + serial_number
        self.Milenage = Milenage(OP)
        self.OPc = make_OPc(self.K, OP)
        # verify parameters
        if len(self.K) != 16 or len(self.OPc) != 16:
            raise (Exception('K / OPc: 16-bytes buffer required'))
        #
        # write data on the card
        u = UICC()
        program_files(u, ADM, self.ICCID, self.IMSI, self.K, self.OPc)
        u.disconnect()
        #
        if self.test_identification() != 0:
            return
        #
        self._auth = 0
        if self.test_authentication() != 0:
            return
        #
        # and print results
        print(
            '[+] sysmoUSIM-SJS1 card personalization done and tested successfully:'
        )
        print('ICCID ; IMSI ; K ; OPc')
        print('%s;%s;0x%s;0x%s' %
              (self.ICCID, self.IMSI, hexlify(self.K), hexlify(self.OPc)))

    def test_identification(self):
        u = UICC()
        iccid = u.get_ICCID()
        u.disconnect()
        u = USIM()
        imsi = u.get_imsi()
        u.disconnect()
        #
        if not iccid or not imsi:
            raise (Exception('identification test error'))
            return 1
        else:
            print('[+] USIM identification:\nICCID: %s\nIMSI: %s' %
                  (iccid, imsi))
            return 0

    def test_authentication(self):
        if self._auth > 2:
            return 1
        #
        # prepare dummy 128 bits auth challenge
        if not hasattr(self, 'RAND'):
            self.RAND = 16 * b'\x44'
        if not hasattr(self, 'SQN'):
            # default SQN is 0, coded on 48 bits
            self.SQN = 0
        # management field, unneeded, left blank
        AMF = b'\0\0'
        #
        # compute Milenage functions
        XRES, CK, IK, AK = self.Milenage.f2345(self.K, self.RAND)
        MAC_A = self.Milenage.f1(self.K, self.RAND, sqn_to_str(self.SQN), AMF)
        AUTN = xor_buf(sqn_to_str(self.SQN), AK) + AMF + MAC_A
        #
        # run auth data on the USIM
        self.U = USIM()
        ret = self.U.authenticate(stringToByte(self.RAND), stringToByte(AUTN),
                                  '3G')
        self.U.disconnect()
        self._auth += 1
        #
        # check results (and pray)
        if ret == None:
            print('[-] authenticate() failed, something wrong happened')
            del self.RAND
            return 1
        #
        elif len(ret) == 1:
            print(
                '[-] sync failure during authenticate() with SQN %i, unmasking counter'
                % self.SQN)
            auts = byteToString(ret[0])
            ak = self.Milenage.f5star(self.K, self.RAND)
            self.SQN = str_to_sqn(xor_buf(auts, ak)[:6])
            print('[+] SQN counter value in USIM: %i' % self.SQN)
            self.SQN += 1 << 5
            print('[+] retrying authenticate() with SQN: %i' % self.SQN)
            del self.RAND
            return self.test_authentication()
        #
        elif len(ret) in (3, 4):
            # RES, CK, IK(, Kc)
            if ret[0:3] == map(stringToByte, [XRES, CK, IK]):
                print(
                    '[+] 3G auth successful with SQN: %i\nincrement it from now'
                    % self.SQN)
                print('[+] USIM secrets:\nOPc: %s\nK: %s' %
                      (hexlify(self.OPc), hexlify(self.K)))
            else:
                print(
                    '[-] 3G auth accepted on the USIM, but not matching auth vector generated: strange!'
                )
                print('card returned:\n%s' % ret)
            del self.RAND
            return 0
        #
        else:
            print('[-] undefined auth error')
            del self.RAND
            return 1
Esempio n. 14
0
class personalize(object):
    '''
    Class to program sysmo-USIM-SJS1 card
    takes the ADM code of the card (str of digits)
    and	a 3 digit serial number as argument to personalize the USIM card.
    
    Makes use of the fixed parameters in this file header:
    ICCID_pre, IMSI_pre, Ki_pre, OP,
    HPLMN, PLMNsel, SPN
    '''
    
    def __init__(self, ADM, serial_number='000'):
        # prepare data to write into the card
        if not len(serial_number) == 3 or not serial_number.isdigit():
            raise(Exception('serial: 3-digits required'))
        self.ICCID      = ICCID_pre + serial_number
        self.ICCID     += str(compute_luhn(self.ICCID))
        self.IMSI       = IMSI_pre + serial_number
        self.K          = Ki_pre + serial_number
        self.Milenage   = Milenage(OP)
        self.OPc        = make_OPc(self.K, OP)
        # verify parameters
        if len(self.K) != 16 or len(self.OPc) != 16:
            raise(Exception('K / OPc: 16-bytes buffer required'))
        #
        # write data on the card
        u = UICC()
        program_files(u, ADM, self.ICCID, self.IMSI, self.K, self.OPc)
        u.disconnect()
        #
        if self.test_identification() != 0:
            return
        #
        self._auth = 0
        if self.test_authentication() != 0:
            return
        #
        # and print results
        print('[+] sysmoUSIM-SJS1 card personalization done and tested successfully:')
        print('ICCID ; IMSI ; K ; OPc')
        print('%s;%s;0x%s;0x%s' % (self.ICCID, self.IMSI, hexlify(self.K), hexlify(self.OPc)))
    
    def test_identification(self):
        u = UICC()
        iccid = u.get_ICCID()
        u.disconnect()
        u = USIM()
        imsi = u.get_imsi()
        u.disconnect()
        #
        if not iccid or not imsi:
            raise(Exception('identification test error'))
            return 1
        else:
            print('[+] USIM identification:\nICCID: %s\nIMSI: %s' % (iccid, imsi))
            return 0
    
    def test_authentication(self):
        if self._auth > 2:
            return 1
        #
        # prepare dummy 128 bits auth challenge
        if not hasattr(self, 'RAND'):
            self.RAND = 16*b'\x44'
        if not hasattr(self, 'SQN'):
            # default SQN is 0, coded on 48 bits
            self.SQN = 0
        # management field, unneeded, left blank
        AMF = b'\0\0'
        #
        # compute Milenage functions
        XRES, CK, IK, AK = self.Milenage.f2345( self.K, self.RAND )
        MAC_A = self.Milenage.f1(self.K, self.RAND, sqn_to_str(self.SQN), AMF)
        AUTN = xor_buf(sqn_to_str(self.SQN), AK) + AMF + MAC_A
        #
        # run auth data on the USIM
        self.U = USIM()
        ret = self.U.authenticate(stringToByte(self.RAND), stringToByte(AUTN), '3G')
        self.U.disconnect()
        self._auth += 1
        #
        # check results (and pray)
        if ret == None:
            print('[-] authenticate() failed, something wrong happened')
            del self.RAND
            return 1
        #
        elif len(ret) == 1:
            print('[-] sync failure during authenticate() with SQN %i, unmasking counter' % self.SQN)
            auts = byteToString(ret[0])
            ak = self.Milenage.f5star(self.K, self.RAND)
            self.SQN = str_to_sqn(xor_buf(auts, ak)[:6])
            print('[+] SQN counter value in USIM: %i' % self.SQN)
            self.SQN += 1<<5
            print('[+] retrying authenticate() with SQN: %i' % self.SQN)
            del self.RAND
            return self.test_authentication()
        #
        elif len(ret) in (3, 4):
            # RES, CK, IK(, Kc)
            if ret[0:3] == map(stringToByte, [XRES, CK, IK]):
                print('[+] 3G auth successful with SQN: %i\nincrement it from now' % self.SQN)
                print('[+] USIM secrets:\nOPc: %s\nK: %s' % (hexlify(self.OPc), hexlify(self.K)))
            else:
                print('[-] 3G auth accepted on the USIM, but not matching auth vector generated: strange!')
                print('card returned:\n%s' % ret)
            del self.RAND
            return 0
        #
        else:
            print('[-] undefined auth error')
            del self.RAND
            return 1
Esempio n. 15
0
class AuC:
    """3GPP Authentication Centre (AuC), ARPF and SIDF
    
    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
    
    # when rewriting the AuC.db, do a back-up of the last version of the file
    DO_BACKUP = True
    
    # 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 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 dict, for decrypting SUCI
    # index: Home Network Public Key Identifier (0..255), according to TS 31.102, section 4.4.11.8
    # value: 2-tuple with Protection Scheme Identifier (profile 'A' or 'B') and 
    #        corresponding Home Network Private Key value
    # 
    # ECIES public / private keypairs must be generated according to the CryptoMobile.ECIES API
    #
    SIDF_ECIES_K = {
        #
        # X25519 example keypair (WARNING: use one you generated yourself):
        # pubkey: d6797fcf69c55e889e5bdf9fc4d300eff2aa5b539bb9e97efe14ca244727b029
        #0 : ('A', unhexlify('38859b29cbbdee43fda218968f8b96bb9a7326ec05b43343939220fa2ac1ec56')),
        #
        # secp256r1 example keypair (WARNING: use one you generated yourself):
        # pubkey: 02519c4707c3535eb5a86a66d056696a45537d4d76e8997375dcd7d30b1f37c6c5
        #1 : ('B', unhexlify('308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02'\
        #                    '01010420d633fa02b1808226c0a27ddf093e332751f10cb002e8236d3723bb44'\
        #                    '33a55d41a14403420004519c4707c3535eb5a86a66d056696a45537d4d76e899'\
        #                    '7375dcd7d30b1f37c6c50fb946aec017a332ff00e3993f35b54992004894f7d2'\
        #                    'fc1ee0df47fde0c91cf8')
        }
    
    def __init__(self):
        """start the AuC
        
        open AuC.db file
        parse it into self.db (dict), containing IMSI: (K, SQN [, OP])
            IMSI: string of digits
            K   : 16 bytes buffer
            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
        """
        self.db = {}
        try:
            # get 3G authentication database AuC.db
            db_fd = open('%sAuC.db' % self.AUC_DB_PATH, 'r')
            # parse it into a dict object with IMSI as key
            for line in db_fd.readlines():
                if line[0] != '#' and line.count(';') >= 3:
                    fields = line[:-1].split(';')
                    IMSI   = str( fields[0] )
                    K      = unhexlify( fields[1].encode('ascii') )
                    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, ALG, SQN, OP ]
            self._log('INF', 'AuC.db file opened: %i record(s) found' % len(self.db))
            # close the file
            db_fd.close()
        except Exception as err:
            self._log('ERR', 'unable to read AuC.db, path: %s' % self.AUC_DB_PATH)
            raise(err)
        self._save_required = False
        #
        # 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 / ARPF / SIDF started')
    
    def _init_sidf(self):
        self._SIDF_ECIES = {}
        for ind, (prof, key) in self.SIDF_ECIES_K.items():
            self._SIDF_ECIES[ind] = ECIES_HN(hn_priv_key=key, profile=prof)
    
    def _log(self, logtype='DBG', msg=''):
        if logtype in self.DEBUG:
            log('[%s] [AuC] %s' % (logtype, msg))
    
    def save(self):
        """
        optionally save old AuC.db with timestamp suffix (if self.DO_BACKUP is set)
        write the current content of self.db dict into AuC.db, with updated SQN
        values
        """
        if not self._save_required:
            return
        
        T = timemod.strftime( '20%y%m%d_%H%M', timemod.gmtime() )
        
        # get header from original file AuC.db
        header = []
        file_db = open('%sAuC.db' % self.AUC_DB_PATH)
        for line in file_db:
            if line[0] == '#':
                header.append( line )
            else:
                break
        header = ''.join(header) + '\n'
        file_db.close()
        
        if self.DO_BACKUP:
            # save the last current version of AuC.db
            os.rename( '%sAuC.db' % self.AUC_DB_PATH,
                       '%sAuC.%s.db' % (self.AUC_DB_PATH, T) )
            self._log('DBG', 'old AuC.db saved with timestamp')
        
        # save the current self.db into a new AuC.db file
        file_db = open('%s/AuC.db' % self.AUC_DB_PATH, 'w')
        file_db.write( header )
        indexes = list(self.db.keys())
        indexes.sort()
        for IMSI in indexes:
            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'), ALG, SQN, hexlify(OP).decode('ascii')))
            else:
                file_db.write('%s;%s;%i;%i;\n'\
                    % (IMSI, hexlify(K).decode('ascii'), ALG, SQN))
        file_db.close()
        self._log('INF', 'current db saved to AuC.db file')
    
    stop = save
    
    def make_2g_vector(self, IMSI, RAND=None):
        """
        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 ALG is invalid
        
        RAND can be passed as argument
        """
        # lookup db for authentication Key and algorithm id for IMSI
        try:
            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 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, 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
        self._log('DBG', '[make_2g_vector] IMSI %s: RAND %s, RES %s, Kc %s'\
                  % (IMSI, hexlify(RAND).decode('ascii'), hexlify(RES).decode('ascii'), 
                     hexlify(Kc).decode('ascii')))
        return RAND, RES, Kc
    
    def make_3g_vector(self, IMSI, AMF=b'\0\0', RAND=None):
        '''
        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 TUAK
        
        RAND can be passed as argument
        '''
        # lookup db for authentication Key and counter for IMSI
        try:
            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
        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 / 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_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 necessary
        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 )
        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:
            # 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'),
                     hexlify(AUTN).decode('ascii'), hexlify(CK).decode('ascii'), 
                     hexlify(IK).decode('ascii')))
        return RAND, XRES, AUTN, CK, IK
    
    def make_4g_vector(self, IMSI, SN_ID, AMF=b'\x80\x00', RAND=None):
        """
        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 TUAK
        or SN_ID is invalid or not allowed
        
        SN_ID is the serving network identity, bcd-encoded buffer
        RAND can be passed as argument
        """
        if not isinstance(SN_ID, bytes_types) or len(SN_ID) != 3:
            self._log('WNG', '[make_4g_vector] SN_ID invalid, %s' % hexlify(SN_ID).decode('ascii'))
            return None
        elif self.PLMN_FILTER is not None and SN_ID not in self.PLMN_FILTER:
            self._log('WNG', '[make_4g_vector] SN_ID not allowed, %s' % hexlify(SN_ID).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_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
        #
        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 LTE master key
        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'), 
                     hexlify(XRES).decode('ascii'), hexlify(AUTN).decode('ascii'), 
                     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
        in response to a given 3G or 4G authentication challenge (RAND, AMF)
        
        return 0 on successful synch, 1 on unsuccessful synch due to invalid AUTS
        or None if the IMSI is not defined in the db
        """
        # lookup db for authentication Key and counter for IMSI
        try:
            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
        #
        if ALG not in (0, 4):
            # Milenage not supported
            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'
        #
        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:
            # 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_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_ALG_SQN_OP[2]))
        return 0
    
    def sidf_unconceal(self, hnkid, ephpubk, cipht, mac):
        """
        unconceal the cipher text `cipht` according to ECIES profile A or B (which
        is implicitly depending on `hnkid`).
        Use the home network private key index `hnkid`, ephemeral public key 
        `ephpubk`, after verifying the `mac`. All parameters are part of the
        SUCI.
        
        return None on error or the unconceal clear-text value bytes buffer (i.e.
        the clear-text 5G subscriber identity)
        """
        if hnkid not in self._SIDF_ECIES or not 32 <= len(ephpubk) <= 33 or len(mac) != 8:
            self._log('WNG', '[sidf_unconceal] invalid parameter')
            return None
        #
        try:
            cleart = self._SIDF_ECIES[hnkid].unprotect(ephpubk, cipht, mac)
        except Exception as err:
            self._log('ERR', '[sidf_unconceal] EC processing error: %s' % err)
            return None
        else:
            self._log('DBG', '[sidf_unconceal] SUCI ciphertext %s decrypted to %s'\
                      % (hexlify(cipht).decode('ascii'), hexlify(cleart).decode('ascii')))
            return cleart