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 encipher(self, p1, p2, data): padded = vsCrypto.append_padding(self.cct.blocklength, data) cipher = eac.EAC_encrypt(self.eac_ctx, padded) if not cipher: eac.print_ossl_err() raise SwError(SW["ERR_NOINFO69"]) return cipher
def encipher(self, p1, p2, data): """ Encipher data using key, algorithm, IV and Padding specified by the current Security environment. :returns: raw data (no TLV coding). """ algo = self.ct.algorithm key = self.ct.key if key == None or algo == None: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) else: padded = vsCrypto.append_padding(vsCrypto.get_cipher_blocklen(algo), data) crypted = vsCrypto.encrypt(algo, key, padded, self.ct.iv) return crypted
def protect_response(self, sw, result): """ This method protects a response APDU using secure messaging mechanisms :returns: the protected data and the SW bytes """ return_data = "" # if sw == SW["NORMAL"]: # sw = inttostring(sw) # length = len(sw) # tag = SM_Class["PLAIN_PROCESSING_STATUS"] # tlv_sw = pack([(tag,length,sw)]) # return_data += tlv_sw if result != "": # Encrypt the data included in the RAPDU encrypted = self.encipher(0x82, 0x80, result) encrypted = "\x01" + encrypted encrypted_tlv = pack([( SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"], len(encrypted), encrypted)]) return_data += encrypted_tlv if sw == SW["NORMAL"]: if self.cct.algorithm is None: raise SwError(SW["CONDITIONSNOTSATISFIED"]) elif self.cct.algorithm == "CC": tag = SM_Class["CHECKSUM"] padded = vsCrypto.append_padding(self.cct.blocklength, return_data) auth = self.compute_cryptographic_checksum(0x8E, 0x80, padded) length = len(auth) return_data += pack([(tag, length, auth)]) elif self.cct.algorithm == "SIGNATURE": tag = SM_Class["DIGITAL_SIGNATURE"] hash = self.hash(0x90, 0x80, return_data) auth = self.compute_digital_signature(0x9E, 0x9A, hash) length = len(auth) return_data += pack([(tag, length, auth)]) return sw, return_data
def external_authenticate(self, p1, p2, data): """ Authenticate the terminal to the card. Check whether Terminal correctly encrypted the given challenge or not """ if self.last_challenge is None: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) key = self._get_referenced_key(p1, p2) if p1 == 0x00: #No information given cipher = get_referenced_cipher(self.cipher) else: cipher = get_referenced_cipher(p1) reference = vsCrypto.append_padding(cipher, self.last_challenge) reference = vsCrypto.encrypt(cipher, key, reference) if(reference == data): #Invalidate last challenge self.last_challenge = None return SW["NORMAL"], "" else: raise SwError(SW["WARN_NOINFO63"])
def external_authenticate(self, p1, p2, data): """ Authenticate the terminal to the card. Check whether Terminal correctly encrypted the given challenge or not """ if self.last_challenge is None: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) key = self._get_referenced_key(p1, p2) if p1 == 0x00: #No information given cipher = get_referenced_cipher(self.cipher) else: cipher = get_referenced_cipher(p1) blocklen = vsCrypto.get_cipher_blocklen(cipher) reference = vsCrypto.append_padding(blocklen, self.last_challenge) reference = vsCrypto.encrypt(cipher, key, reference) if (reference == data): #Invalidate last challenge self.last_challenge = None return SW["NORMAL"], "" else: raise SwError(SW["WARN_NOINFO63"])
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
if __name__ == "__main__": password = "******" MyCard = SAM("1234", "1234567890") try: print(MyCard.verify(0x00, 0x00, "5678")) except SwError as e: print(e.message) print("Counter = " + str(MyCard.counter)) print(MyCard.verify(0x00, 0x00, "1234")) print("Counter = " + str(MyCard.counter)) sw, challenge = MyCard.get_challenge(0x00, 0x00, "") print("Before encryption: " + challenge) padded = vsCrypto.append_padding("DES3-ECB", challenge) sw, result_data = MyCard.internal_authenticate(0x00, 0x00, padded) print("Internal Authenticate status code: %x" % sw) try: sw, res = MyCard.external_authenticate(0x00, 0x00, result_data) except SwError as e: print(e.message) sw = e.sw print("Decryption Status code: %x" % sw) #SE = Security_Environment(None) #testvektor = "foobar" #print "Testvektor = %s" % testvektor #sw, hash = SE.hash(0x90,0x80,testvektor) #print "SW after hashing = %s" % sw
if __name__ == "__main__": password = "******" MyCard = SAM("1234", "1234567890") try: print(MyCard.verify(0x00, 0x00, "5678")) except SwError as e: print(e.message) print("Counter = " + str(MyCard.counter)) print(MyCard.verify(0x00, 0x00, "1234")) print("Counter = " + str(MyCard.counter)) sw, challenge = MyCard.get_challenge(0x00, 0x00, "") print("Before encryption: " + challenge) padded = vsCrypto.append_padding("DES3-ECB", challenge) sw, result_data = MyCard.internal_authenticate(0x00, 0x00, padded) print("Internal Authenticate status code: %x" % sw) try: sw, res = MyCard.external_authenticate(0x00, 0x00, result_data) except SwError as e: print(e.message) sw = e.sw print("Decryption Status code: %x" % sw) #SE = Security_Environment(None) #testvektor = "foobar" #print "Testvektor = %s" % testvektor #sw, hash = SE.hash(0x90,0x80,testvektor) #print "SW after hashing = %s" % sw