def parse_SE_config(self, config): r = 0x9000 try: ControlReferenceTemplate.parse_SE_config(self, config) except SwError as e: structure = unpack(config) for tlv in structure: tag, length, value = tlv if tag == 0x7f4c: self.chat = CHAT(bertlv_pack([[tag, length, value]])) print(self.chat) elif tag == 0x67: self.auxiliary_data = bertlv_pack([[tag, length, value]]) elif tag == 0x80 or tag == 0x84 or tag == 0x83 or tag == 0x91: # handled by ControlReferenceTemplate.parse_SE_config pass else: raise SwError(SW["ERR_REFNOTUSABLE"]) structure = unpack(config) pin_ref_str = '%c'% self.PACE_PIN for tlv in structure: if [0x83, len(pin_ref_str), pin_ref_str] == tlv: if self.sam.counter <= 0: r = 0x63c0 elif self.sam.counter == 1: r = 0x63c1 elif self.sam.counter == 2: r = 0x63c2 return r, ""
def verify(self, p1, p2, data): if p1 == 0x80 and p2 == 0x00: if self.current_SE.eac_step == 6: # data should only contain exactly OID [(tag, _, value)] = structure = unpack(data) if tag == 6: mapped_algo = ALGO_MAPPING[value] eid = self.mf.select("dfname", "\xe8\x07\x04\x00\x7f\x00\x07\x03\x02") if mapped_algo == "DateOfExpiry": [(_, _, [(_, _, mine)])] = unpack(eid.select("fid", 0x0103).data) logging.info( "DateOfExpiry: " + str(mine) + "; reference: " + str(self.current_SE.at.DateOfExpiry) ) if self.current_SE.at.DateOfExpiry < mine: print ("Date of expiry verified") return SW["NORMAL"], "" else: print ("Date of expiry not verified (expired)") return SW["WARN_NOINFO63"], "" elif mapped_algo == "DateOfBirth": [(_, _, [(_, _, mine)])] = unpack(eid.select("fid", 0x0108).data) # case1: YYYYMMDD -> good # case2: YYYYMM -> mapped to last day of given month, i.e. YYYYMM31 ;-) # case3: YYYY -> mapped to YYYY-12-31 if len(str(mine)) == 6: mine = int(str(mine) + "31") elif len(str(mine)) == 4: mine = int(str(mine) + "1231") logging.info( "DateOfBirth: " + str(mine) + "; reference: " + str(self.current_SE.at.DateOfExpiry) ) if self.current_SE.at.DateOfBirth < mine: print ("Date of birth verified (old enough)") return SW["NORMAL"], "" else: print ("Date of birth not verified (too young)") return SW["WARN_NOINFO63"], "" elif mapped_algo == "CommunityID": [(_, _, [(_, _, mine)])] = unpack(eid.select("fid", 0x0112).data) mine = binascii.hexlify(mine) logging.info( "CommunityID: " + str(mine) + "; reference: " + str(self.current_SE.at.CommunityID) ) if mine.startswith(self.current_SE.at.CommunityID): print ("Community ID verified (living there)") return SW["NORMAL"], "" else: print ("Community ID not verified (not living there)") return SW["WARN_NOINFO63"], "" else: return SwError(SW["ERR_DATANOTFOUND"]) else: return SwError(SW["ERR_DATANOTFOUND"]) else: return SAM.verify(self, p1, p2, data)
def compute_digital_signature(self, p1, p2, data): """ Compute a digital signature for the given data. Algorithm and key are specified in the current SE :param p1: Must be 0x9E = Secure Messaging class for digital signatures :param p2: Must be one of 0x9A, 0xAC, 0xBC. Indicates what kind of data is included in the data field. """ if p1 != 0x9E or not p2 in (0x9A, 0xAC, 0xBC): raise SwError(SW["ERR_INCORRECTP1P2"]) if self.dst.key == None: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) to_sign = "" if p2 == 0x9A: #Data to be signed to_sign = data elif p2 == 0xAC: #Data objects, sign values to_sign = "" structure = unpack(data) for tag, length, value in structure: to_sign += value elif p2 == 0xBC: #Data objects to be signed pass signature = self.dst.key.sign(to_sign, "") return signature
def verify_cryptographic_checksum(self, p1, p2, data): """ Verify the cryptographic checksum contained in the data field. Data field must contain a cryptographic checksum (tag 0x8E) and a plain value (tag 0x80) """ plain = "" cct = "" algo = self.cct.algorithm key = self.cct.key iv = self.cct.iv if algo == None or key == None: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) structure = unpack(data) for tag, length, value in structure: if tag == 0x80: plain = value elif tag == 0x8E: cct = value if plain == "" or cct == "": raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) else: my_cct = vsCrypto.crypto_checksum(algo, key, plain, iv) if my_cct == cct: return "" else: raise SwError["ERR_SECMESSOBJECTSINCORRECT"]
def verify_digital_signature(self, p1, p2, data): """ Verify the digital signature contained in the data field. Data must contain a data to sign (tag 0x9A, 0xAC or 0xBC) and a digital signature (0x9E) """ key = self.dst.key to_sign = "" signature = "" if key == None: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) structure = unpack(data) for tag, length, value in structure: if tag == 0x9E: signature = value elif tag == 0x9A: #FIXME: Correct treatment of all possible tags to_sign = value elif tag == 0xAC: pass elif tag == 0xBC: pass if to_sign == "" or signature == "": raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) my_signature = key.sign(value) if my_signature == signature: return "" else: raise SwError(["ERR_SECMESSOBJECTSINCORRECT"])
def parse_SE_config(self, config): """ Parse a control reference template as given e.g. in an MSE APDU. :param config: a TLV string containing the configuration for the CRT. """ error = False structure = unpack(config) for tlv in structure: tag, length, value = tlv if tag == 0x80: self.__set_algo(value) elif tag in (0x81, 0x82, 0x83, 0x84): self.__set_key(tag, value) elif tag in range(0x85, 0x93): self.__set_iv(tag, length, value) elif tag == 0x95: self.usage_qualifier = value else: error = True if error: raise SwError(SW["ERR_REFNOTUSABLE"]) else: return SW["NORMAL"], ""
def __unpack_general_authenticate(data): data_structure = [] structure = unpack(data) for tlv in structure: tag, length, value = tlv if tag == 0x7c: data_structure = value else: raise SwError(SW["ERR_INCORRECTPARAMETERS"]) return data_structure
def parse_SE_config(self, config): r = 0x9000 try: ControlReferenceTemplate.parse_SE_config(self, config) except SwError as e: structure = unpack(config) for tlv in structure: tag, length, value = tlv if tag == 0x7f4c: self.chat = CHAT(bertlv_pack([[tag, length, value]])) print(self.chat) elif tag == 0x67: self.auxiliary_data = bertlv_pack([[tag, length, value]]) elif tag == 0x80 or tag == 0x84 or tag == 0x83 or tag == 0x91: # handled by ControlReferenceTemplate.parse_SE_config pass else: raise SwError(SW["ERR_REFNOTUSABLE"]) return r, ""
def verify(self, p1, p2, data): if (p1 != 0x80 or p2 != 0x00): raise SwError(SW["ERR_INCORRECTP1P2"]) if self.current_SE.eac_step == 6: structure = unpack(data) for tag, length, value in structure: if tag == 6 and ALGO_MAPPING[value] == "DateOfExpiry": # hell yes, this is a valid nPA # TODO actually check it... return SW["NORMAL"], "" if tag == 6 and ALGO_MAPPING[value] == "DateOfBirth": # hell yes, we are old enough # TODO actually check it... return SW["NORMAL"], "" if tag == 6 and ALGO_MAPPING[value] == "CommunityID": # well OK, we are living there # TODO actually check it... return SW["NORMAL"], "" raise SwError(SW["WARN_NOINFO63"])
def parse_SE_config(self, config): r = 0x9000 try: ControlReferenceTemplate.parse_SE_config(self, config) except SwError as e: structure = unpack(config) for tlv in structure: tag, length, value = tlv if tag == 0x7f4c: self.chat = CHAT(bertlv_pack([[tag, length, value]])) elif tag == 0x67: self.auxiliary_data = bertlv_pack([[tag, length, value]]) # extract reference values for verifying # DateOfBirth, DateOfExpiry and CommunityID for ddo in decodeDiscretionaryDataObjects(value): try: oidvalue = ddo[0][2] reference = ddo[1][2] mapped_algo = ALGO_MAPPING[oidvalue] if mapped_algo == "DateOfBirth": self.DateOfBirth = int(reference) logging.info("Found reference DateOfBirth: " + str(self.DateOfBirth)) elif mapped_algo == "DateOfExpiry": self.DateOfExpiry = int(reference) logging.info("Found reference DateOfExpiry: " + str(self.DateOfExpiry)) elif mapped_algo == "CommunityID": self.CommunityID = binascii.hexlify(reference) logging.info("Found reference CommunityID: " + str(self.CommunityID)) except: pass elif tag == 0x80 or tag == 0x84 or tag == 0x83 or tag == 0x91: # handled by ControlReferenceTemplate.parse_SE_config pass else: raise SwError(SW["ERR_REFNOTUSABLE"]) return r, ""
def parse_SE_config(self, config): r = 0x9000 try: ControlReferenceTemplate.parse_SE_config(self, config) except SwError as e: structure = unpack(config) for tlv in structure: tag, length, value = tlv if tag == 0x7f4c: self.chat = CHAT(bertlv_pack([[tag, length, value]])) elif tag == 0x67: self.auxiliary_data = bertlv_pack([[tag, length, value]]) # extract reference values for verifying # DateOfBirth, DateOfExpiry and CommunityID for ddo in decodeDiscretionaryDataObjects(value): try: oidvalue = ddo[0][2] reference = ddo[1][2] mapped_algo = ALGO_MAPPING[oidvalue] if mapped_algo == "DateOfBirth": self.DateOfBirth = int(reference) logging.info("Found reference DateOfBirth: " + str(self.DateOfBirth)) elif mapped_algo == "DateOfExpiry": self.DateOfExpiry = int(reference) logging.info("Found reference DateOfExpiry: " + str(self.DateOfExpiry)) elif mapped_algo == "CommunityID": self.CommunityID = binascii.hexlify(reference) logging.info("Found reference CommunityID: " + str(self.CommunityID)) except: pass elif tag == 0x80 or tag == 0x84 or tag == 0x83 or tag == 0x91: # handled by ControlReferenceTemplate.parse_SE_config pass else: raise SwError(SW["ERR_REFNOTUSABLE"]) return r, b""
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
def verify(self, p1, p2, data): if p1 == 0x80 and p2 == 0x00: if self.current_SE.eac_step == 6: # data should only contain exactly OID [(tag, _, value)] = structure = unpack(data) if tag == 6: mapped_algo = ALGO_MAPPING[value] eid = self.mf.select( 'dfname', b'\xe8\x07\x04\x00\x7f\x00' b'\x07\x03\x02') if mapped_algo == "DateOfExpiry": [(_, _, [(_, _, mine)])] = \ unpack(eid.select('fid', 0x0103).data) logging.info("DateOfExpiry: " + str(mine) + "; reference: " + str(self.current_SE.at.DateOfExpiry)) if self.current_SE.at.DateOfExpiry < mine: print("Date of expiry verified") return SW["NORMAL"], "" else: print("Date of expiry not verified (expired)") return SW["WARN_NOINFO63"], "" elif mapped_algo == "DateOfBirth": [(_, _, [(_, _, mine)])] = \ unpack(eid.select('fid', 0x0108).data) # case1: YYYYMMDD -> good # case2: YYYYMM -> mapped to last day of given month, # i.e. YYYYMM31 ;-) # case3: YYYY -> mapped to YYYY-12-31 if len(str(mine)) == 6: mine = int(str(mine) + "31") elif len(str(mine)) == 4: mine = int(str(mine) + "1231") logging.info("DateOfBirth: " + str(mine) + "; reference: " + str(self.current_SE.at.DateOfExpiry)) if self.current_SE.at.DateOfBirth < mine: print("Date of birth verified (old enough)") return SW["NORMAL"], "" else: print("Date of birth not verified (too young)") return SW["WARN_NOINFO63"], "" elif mapped_algo == "CommunityID": [(_, _, [(_, _, mine)])] = \ unpack(eid.select('fid', 0x0112).data) mine = binascii.hexlify(mine) logging.info("CommunityID: " + str(mine) + "; reference: " + str(self.current_SE.at.CommunityID)) if mine.startswith(self.current_SE.at.CommunityID): print("Community ID verified (living there)") return SW["NORMAL"], b"" else: print("Community ID not verified (not living" "there)") return SW["WARN_NOINFO63"], b"" else: return SwError(SW["ERR_DATANOTFOUND"]) else: return SwError(SW["ERR_DATANOTFOUND"]) else: return SAM.verify(self, p1, p2, data)