def external_authenticate(self, p1, p2, resp_data): """Performs the basic access control protocol as defined in the ICAO MRTD standard""" rnd_icc = self.last_challenge # Receive Mutual Authenticate APDU from terminal # Decrypt data and check MAC Eifd = resp_data[:-8] padded_Eifd = vsCrypto.append_padding(self.current_SE.cct.blocklength, Eifd) Mifd = vsCrypto.crypto_checksum("CC", self.KMac, padded_Eifd) # Check the MAC if not Mifd == resp_data[-8:]: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) # Decrypt the data plain = vsCrypto.decrypt("DES3-CBC", self.KEnc, resp_data[:-8]) if plain[8:16] != rnd_icc: raise SwError(SW["WARN_NOINFO63"]) # Extract keying material from IFD, generate ICC keying material Kifd = plain[16:] rnd_ifd = plain[:8] Kicc = urandom(16) # Generate Answer data = plain[8:16] + plain[:8] + Kicc Eicc = vsCrypto.encrypt("DES3-CBC", self.KEnc, data) padded_Eicc = vsCrypto.append_padding(self.current_SE.cct.blocklength, Eicc) Micc = vsCrypto.crypto_checksum("CC", self.KMac, padded_Eicc) # Derive the final keys and set the current SE KSseed = vsCrypto.operation_on_string(Kicc, Kifd, lambda a, b: a ^ b) self.current_SE.ct.key = self.derive_key(KSseed, 1) self.current_SE.cct.key = self.derive_key(KSseed, 2) self.current_SE.ssc = stringtoint(rnd_icc[-4:] + rnd_ifd[-4:]) return SW["NORMAL"], Eicc + Micc
def __replace_tag(self, tag, data): """ Adjust the config string using a given tag, value combination. If the config string already contains a tag, value pair for the given tag, replace it. Otherwise append tag, length and value to the config string. """ position = 0 while position < len(self.__config_string) and \ self.__config_string[position] != tag: length = stringtoint(self.__config_string[position + 1]) position += length + 3 if position < len(self.__config_string): #Replace Tag length = stringtoint(self.__config_string[position + 1]) self.__config_string = self.__config_string[:position] +\ chr(tag) + inttostring(len(data)) + data +\ self.__config_string[position+2+length:] else: #Add new tag self.__config_string += chr(tag) + inttostring(len(data)) + data
def __replace_tag(self, tag, data): """ Adjust the config string using a given tag, value combination. If the config string already contains a tag, value pair for the given tag, replace it. Otherwise append tag, length and value to the config string. """ position = 0 while position < len(self.__config_string) and \ self.__config_string[position] != tag: length = stringtoint(self.__config_string[position+1]) position += length + 3 if position < len(self.__config_string): #Replace Tag length = stringtoint(self.__config_string[position+1]) self.__config_string = self.__config_string[:position] +\ chr(tag) + inttostring(len(data)) + data +\ self.__config_string[position+2+length:] else: #Add new tag self.__config_string += chr(tag) + inttostring(len(data)) + data
def create(p1, p2, data): if data[0:2] != "\xff\xff": raise SwError(SW["ERR_INCORRECTPARAMETERS"]) args = { "parent": None, "filedescriptor": 0, "fid": stringtoint(data[4:6]), } if data[6] == "\x01": args["data"] = bytes(0)*stringtoint(data[2:4]) args["filedescriptor"] = FDB["EFSTRUCTURE_TRANSPARENT"] new_file = TransparentStructureEF(**args) elif data[6] == "\x02": if len(data) > 16: args["maxrecordsize"] = stringtoint(data[16]) elif p2: # if given a number of records args["maxrecordsize"] = (stringtoint(data[2:4]) / p2) args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_FIXED_" "NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x03": args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_VARIABLE_" "NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x04": args["filedescriptor"] = FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x38": if data[12] != "\x03": raise SwError(SW["ERR_INCORRECTPARAMETERS"]) new_file = DF(**args) else: logging.error("unknown type: 0x%x" % ord(data[6])) raise SwError(SW["ERR_INCORRECTPARAMETERS"]) return [new_file]
def create(p1, p2, data): if data[0:2] != "\xff\xff": raise SwError(SW["ERR_INCORRECTPARAMETERS"]) args = { "parent": None, "filedescriptor": 0, "fid": stringtoint(data[4:6]), } if data[6] == "\x01": args["data"] = chr(0) * stringtoint(data[2:4]) args["filedescriptor"] = FDB["EFSTRUCTURE_TRANSPARENT"] new_file = TransparentStructureEF(**args) elif data[6] == "\x02": if len(data) > 16: args["maxrecordsize"] = stringtoint(data[16]) elif p2: # if given a number of records args["maxrecordsize"] = (stringtoint(data[2:4]) / p2) args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_FIXED_" "NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x03": args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_VARIABLE_" "NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x04": args["filedescriptor"] = FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x38": if data[12] != "\x03": raise SwError(SW["ERR_INCORRECTPARAMETERS"]) new_file = DF(**args) else: logging.error("unknown type: 0x%x" % ord(data[6])) raise SwError(SW["ERR_INCORRECTPARAMETERS"]) return [new_file]
def _get_referenced_key(self, p1, p2): """ This method returns the key specified by the p2 parameter. The key may be stored on the cards filesystem. :param p1: Specifies the algorithm to use. Needed to know the keylength. :param p2: Specifies a reference to the key to be used for encryption == == == == == == == == ============================================= b8 b7 b6 b5 b4 b3 b2 b1 Meaning == == == == == == == == ============================================= 0 0 0 0 0 0 0 0 No information is given 0 - - - - - - - Global reference data(e.g. MF specific key) 1 - - - - - - - Specific reference data(e.g. DF specific key) - - - x x x x x Number of the secret == == == == == == == == ============================================= Any other value RFU """ key = None qualifier = p2 & 0x1F algo = get_referenced_cipher(p1) keylength = vsCrypto.get_cipher_keylen(algo) if (p2 == 0x00): #No information given, use the global card key key = self.cardSecret #We treat global and specific reference data alike #elif ((p2 >> 7) == 0x01 or (p2 >> 7) == 0x00): else: #Interpret qualifier as an short fid (try to read the key from FS) if self.mf == None: raise SwError(SW["ERR_REFNOTUSABLE"]) df = self.mf.currentDF() fid = df.select("fid", stringtoint(qualifier)) key = fid.readbinary(keylength) if key != None: return key else: raise SwError(SW["ERR_REFNOTUSABLE"])
def decodeOffsetDataObjects(tlv_data): offsets = [] for (tag, length, newvalue) in tlv_find_tag(tlv_data, TAG["OFFSET_DATA"]): offsets.append(stringtoint(newvalue)) return offsets
def parse_SM_CAPDU(self, CAPDU, authenticate_header): """ This methods parses a data field including Secure Messaging objects. SM_header indicates whether or not the header of the message shall be authenticated. It returns an unprotected command APDU :param CAPDU: The protected CAPDU to be parsed :param authenticate_header: Whether or not the header should be included in authentication mechanisms :returns: Unprotected command APDU """ structure = unpack(CAPDU.data) return_data = [ "", ] cla = None ins = None p1 = None p2 = None le = None if authenticate_header: to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins)+\ inttostring(CAPDU.p1) + inttostring(CAPDU.p2) to_authenticate = vsCrypto.append_padding(self.cct.blocklength, to_authenticate) else: to_authenticate = "" for tlv in structure: tag, length, value = tlv if tag % 2 == 1: #Include object in checksum calculation to_authenticate += bertlv_pack([[tag, length, value]]) #SM data objects for encapsulating plain values if tag in (SM_Class["PLAIN_VALUE_NO_TLV"], SM_Class["PLAIN_VALUE_NO_TLV_ODD"]): return_data.append(value) #FIXME: Need TLV coding? #Encapsulated SM objects. Parse them #FIXME: Need to pack value into a dummy CAPDU elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"], SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]): return_data.append( self.parse_SM_CAPDU(value, authenticate_header)) #Encapsulated plaintext BER-TLV objects elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"], SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]): return_data.append(value) elif tag in (SM_Class["Ne"], SM_Class["Ne_ODD"]): le = value elif tag == SM_Class["PLAIN_COMMAND_HEADER"]: if len(value) != 8: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) else: cla = value[:2] ins = value[2:4] p1 = value[4:6] p2 = value[6:8] #SM data objects for confidentiality if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"], SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]): #The cryptogram includes SM objects. #We decrypt them and parse the objects. plain = self.decipher(tag, 0x80, value) #TODO: Need Le = length return_data.append( self.parse_SM_CAPDU(plain, authenticate_header)) elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"], SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]): #The cryptogram includes BER-TLV encoded plaintext. #We decrypt them and return the objects. plain = self.decipher(tag, 0x80, value) return_data.append(plain) elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"], SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]): #The first byte of the data field indicates the padding to use: """ Value Meaning '00' No further indication '01' Padding as specified in 6.2.3.1 '02' No padding '1X' One to four secret keys for enciphering information, not keys ('X' is a bitmap with any value from '0' to 'F') '11' indicates the first key (e.g., an "even" control word in a pay TV system) '12' indicates the second key (e.g., an "odd" control word in a pay TV system) '13' indicates the first key followed by the second key (e.g., a pair of control words in a pay TV system) '2X' Secret key for enciphering keys, not information ('X' is a reference with any value from '0' to 'F') (e.g., in a pay TV system, either an operational key for enciphering control words, or a management key for enciphering operational keys) '3X' Private key of an asymmetric key pair ('X' is a reference with any value from '0' to 'F') '4X' Password ('X' is a reference with any value from '0' to 'F') '80' to '8E' Proprietary """ padding_indicator = stringtoint(value[0]) plain = self.decipher(tag, 0x80, value[1:]) plain = vsCrypto.strip_padding(self.ct.blocklength, plain, padding_indicator) return_data.append(plain) #SM data objects for authentication if tag == SM_Class["CHECKSUM"]: auth = vsCrypto.append_padding(self.cct.blocklength, to_authenticate) checksum = self.compute_cryptographic_checksum( 0x8E, 0x80, auth) if checksum != value: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) elif tag == SM_Class["DIGITAL_SIGNATURE"]: auth = to_authenticate #FIXME: Need padding? signature = self.compute_digital_signature(0x9E, 0x9A, auth) if signature != value: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) elif tag in (SM_Class["HASH_CODE"], SM_Class["HASH_CODE_ODD"]): hash = self.hash(p1, p2, to_authenticate) if hash != value: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) #Form unprotected CAPDU if cla == None: cla = CAPDU.cla if ins == None: ins = CAPDU.ins if p1 == None: p1 = CAPDU.p1 if p2 == None: p2 = CAPDU.p2 # FIXME #if expected != "": #raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) if isinstance(le, str): # FIXME C_APDU only handles le with strings of length 1. Better patch utils.py to support extended length apdus le_int = stringtoint(le) if le_int == 0 and len(le) > 1: le_int = MAX_EXTENDED_LE le = le_int c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le, data="".join(return_data)) return c
def parse_SM_CAPDU(self, CAPDU, authenticate_header): """ This methods parses a data field including Secure Messaging objects. SM_header indicates whether or not the header of the message shall be authenticated. It returns an unprotected command APDU :param CAPDU: The protected CAPDU to be parsed :param authenticate_header: Whether or not the header should be included in authentication mechanisms :returns: Unprotected command APDU """ structure = unpack(CAPDU.data) return_data = ["",] cla = None ins = None p1 = None p2 = None le = None if authenticate_header: to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins)+\ inttostring(CAPDU.p1) + inttostring(CAPDU.p2) to_authenticate = vsCrypto.append_padding(self.cct.blocklength, to_authenticate) else: to_authenticate = "" for tlv in structure: tag, length, value = tlv if tag % 2 == 1: #Include object in checksum calculation to_authenticate += bertlv_pack([[tag, length, value]]) #SM data objects for encapsulating plain values if tag in (SM_Class["PLAIN_VALUE_NO_TLV"], SM_Class["PLAIN_VALUE_NO_TLV_ODD"]): return_data.append(value) #FIXME: Need TLV coding? #Encapsulated SM objects. Parse them #FIXME: Need to pack value into a dummy CAPDU elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"], SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]): return_data.append(self.parse_SM_CAPDU(value, authenticate_header)) #Encapsulated plaintext BER-TLV objects elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"], SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]): return_data.append(value) elif tag in (SM_Class["Ne"], SM_Class["Ne_ODD"]): le = value elif tag == SM_Class["PLAIN_COMMAND_HEADER"]: if len(value) != 8: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) else: cla = value[:2] ins = value[2:4] p1 = value[4:6] p2 = value[6:8] #SM data objects for confidentiality if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"], SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]): #The cryptogram includes SM objects. #We decrypt them and parse the objects. plain = self.decipher(tag, 0x80, value) #TODO: Need Le = length return_data.append(self.parse_SM_CAPDU(plain, authenticate_header)) elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"], SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]): #The cryptogram includes BER-TLV encoded plaintext. #We decrypt them and return the objects. plain = self.decipher(tag, 0x80, value) return_data.append(plain) elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"], SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]): #The first byte of the data field indicates the padding to use: """ Value Meaning '00' No further indication '01' Padding as specified in 6.2.3.1 '02' No padding '1X' One to four secret keys for enciphering information, not keys ('X' is a bitmap with any value from '0' to 'F') '11' indicates the first key (e.g., an "even" control word in a pay TV system) '12' indicates the second key (e.g., an "odd" control word in a pay TV system) '13' indicates the first key followed by the second key (e.g., a pair of control words in a pay TV system) '2X' Secret key for enciphering keys, not information ('X' is a reference with any value from '0' to 'F') (e.g., in a pay TV system, either an operational key for enciphering control words, or a management key for enciphering operational keys) '3X' Private key of an asymmetric key pair ('X' is a reference with any value from '0' to 'F') '4X' Password ('X' is a reference with any value from '0' to 'F') '80' to '8E' Proprietary """ padding_indicator = stringtoint(value[0]) plain = self.decipher(tag, 0x80, value[1:]) plain = vsCrypto.strip_padding(self.ct.blocklength, plain, padding_indicator) return_data.append(plain) #SM data objects for authentication if tag == SM_Class["CHECKSUM"]: auth = vsCrypto.append_padding(self.cct.blocklength, to_authenticate) checksum = self.compute_cryptographic_checksum(0x8E, 0x80, auth) if checksum != value: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) elif tag == SM_Class["DIGITAL_SIGNATURE"]: auth = to_authenticate #FIXME: Need padding? signature = self.compute_digital_signature(0x9E, 0x9A, auth) if signature != value: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) elif tag in (SM_Class["HASH_CODE"], SM_Class["HASH_CODE_ODD"]): hash = self.hash(p1, p2, to_authenticate) if hash != value: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) #Form unprotected CAPDU if cla == None: cla = CAPDU.cla if ins == None: ins = CAPDU.ins if p1 == None: p1 = CAPDU.p1 if p2 == None: p2 = CAPDU.p2 if le == None: le = CAPDU.le # FIXME #if expected != "": #raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) if isinstance(le, str): # FIXME C_APDU only handles le with strings of length 1. Better patch utils.py to support extended length apdus le_int = stringtoint(le) if le_int == 0 and len(le) > 1: le_int = MAX_EXTENDED_LE le = le_int c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le, data="".join(return_data)) return c