class ActiveAuthentication(Logger): """ This class implement the Active Authentication protocol. The main method is I{executeAA} that return True is the verification is ok or False. """ def __init__(self, iso7816, openssl=None): """ @param iso7816: a valid iso7816 object @type iso7816: doc9303 """ Logger.__init__(self, "AA") self._iso7816 = iso7816 if not openssl: self._openssl = OpenSSL() else: self._openssl = openssl self._openssl.register(self.log) self.RND_IFD = None self.F = None self.T = None self.decryptedSignature = None self.D = None self.D_ = None self.M1 = None self.M_ = None self._dg15 = None def executeAA(self, dg15, EC=False, hash=sha256): """ Perform the Active Authentication protocol. Work only with RSA, modulus length of 1024 and with SHA1. @param dg15: A initialized dataGroup15 object @type dg15: dataGroup15 @param EC: True if ECDSA is used y the passport @param hash: hash function (default: sha256) @return: True if the authentication succeed, else False. @rtype: Boolean @raise ActiveAuthenticationException: If the Active Authentication is not supported (The DG15 is not found or the hash algo is not supported). @raise ActiveAuthenticationException: If the parameter is not set or invalid. @raise ActiveAuthenticationException: If OpenSSL is not installed. @raise ActiveAuthenticationException: If the public key cannot be recovered from the DG15. @raise ActiveAuthenticationException: If the DG15 is invalid and the signature cannot be verified. """ self._dg15 = dg15 self.RND_IFD = self._genRandom(8) self.signature = self._getSignature(self.RND_IFD) if EC: return self._verifyECSignature(dg15.body, self.signature, self.RND_IFD, hash) self.F = self._decryptSignature(dg15.body, self.signature) (hash, hashSize, offset) = self._getHashAlgo(self.F) self.D = self._extractDigest(self.F, hashSize, offset) self.M1 = self._extractM1(self.F, hashSize, offset) self.M_ = self.M1 + self.RND_IFD self.log("Concatenate M1 with known M2") self.log("\tM*: " + binToHexRep(self.M_)) self.D_ = self._hash(hash, self.M_) self.log("Compare D and D*") self.log("\t" + str(self.D == self.D_)) return self.D == self.D_ def _genRandom(self, size): rnd_ifd = os.urandom(size) self.log("Generate an 8 byte random") self.log("\tRND.IFD: " + binToHexRep(rnd_ifd)) return rnd_ifd def _getSignature(self, rnd_ifd): return self._iso7816.internalAuthentication(rnd_ifd) def getPubKey(self, dg15): """ Retrieve the public key in PEM format from the dataGroup15 @return: A PEM reprensation of the public key @rtype: A string @raise ActiveAuthenticationException: I{The parameter type is not valid, must be a dataGroup15 object}: The parameter dg15 is not set or invalid. @raise ActiveAuthenticationException: I{The public key could not be recovered from the DG15}: Is open SSL installed? """ if type(dg15) != type(datagroup.DataGroup15(None)): raise ActiveAuthenticationException( "The parameter type is not valid, must be a dataGroup15 object" ) try: return self._openssl.retrieveRsaPubKey(dg15.body) except OpenSSLException: return self._openssl.retrieveECPubKey(dg15.body) def _verifyECSignature(self, pubK, signature, challenge, hash): # Generate hash of the challenge challenge = sha256(challenge).digest() # Signature is returned in plain format by the card (big endian) # It must be converted to ASN.1 to be usable by OpenSSL # See https://crypto.stackexchange.com/questions/57731/ecdsa-signature-rs-to-asn1-der-encoding-question sig_rec = ECSignatureRecord() l = int(len(signature) / 2) sig_rec['r'] = int.from_bytes(signature[:l], 'big') sig_rec['s'] = int.from_bytes(signature[-l:], 'big') sig_der = encoder.encode(sig_rec) status = self._openssl.verifyECSignature(pubK, sig_der, challenge) self.log("Verify the EC signature with the public key") self.log("\tStatus: " + str(status)) return status def _decryptSignature(self, pubK, signature): data = self._openssl.retrieveSignedData(pubK, signature) self.log("Decrypt the signature with the public key") self.log("\tF: " + binToHexRep(data)) return data def _hash(self, hash, data): digest = hash(data).digest() self.log("Calculate digest of M*") self.log("\tD*: " + binToHexRep(digest)) return digest def _getHashAlgo(self, sig): hash = None offset = None hashSize = None if sig[-1] == 0xBC: self.T = sig[-1] hash = sha1 offset = -1 elif sig[-1] == 0xCC: self.T = sig[-2] #hash = The algorithm corresponding to the algo designed by T offset = -2 else: raise ActiveAuthenticationException("Unknow hash algorithm") self.log("Determine hash algorithm by trailer T*") self.log("\tT: " + binToHexRep(self.T)) #Find out the hash size hashSize = len(hash(b"test").digest()) return (hash, hashSize, offset) def _extractDigest(self, sig, hashSize, offset): digest = sig[offset - hashSize:offset] self.log("Extract digest:") self.log("\tD: " + binToHexRep(digest)) return digest def _extractM1(self, sig, hashSize, offset): M1 = sig[1:offset - hashSize] self.log("Extract M1:") self.log("\tM1: " + binToHexRep(M1)) return M1 def __str__(self): spec = self._asn1Parse() return spec.prettyPrint() def algorithm(self, dg15): """ Return the algorithm name used to store the signature @return: A string from the OID dictionnary. @raise ActiveAuthenticationException: I{Unsupported algorithm}: The algorithm does not exist in the OID enumeration. @raise ActiveAuthenticationException: I{The parameter type is not valid, must be a dataGroup15 object}: The parameter dg15 is not set or invalid. """ if type(dg15) != type(datagroup.DataGroup15(None)): raise ActiveAuthenticationException( "The parameter type is not valid, must be a dataGroup15 object" ) algo = "" try: spec = self._asn1Parse() algo = spec.getComponentByName('algorithm').getComponentByName( 'algorithm').prettyPrint() return OID[algo] except KeyError: raise ActiveAuthenticationException("Unsupported algorithm: " + algo) except Exception as msg: raise ActiveAuthenticationException( "Active Authentication not supported: ", msg) def _asn1Parse(self): if self._dg15 != None: certType = SubjectPublicKeyInfo() return decoder.decode(self._dg15.body, asn1Spec=certType)[0] return ""
class ActiveAuthentication(Logger): """ This class implement the Active Authentication protocol. The main method is I{executeAA} that return True is the verification is ok or False. """ def __init__(self, iso7816, openssl=None): """ @param iso7816: a valid iso7816 object @type iso7816: doc9303 """ Logger.__init__(self, "AA") self._iso7816 = iso7816 if not openssl: self._openssl = OpenSSL() else: self._openssl = openssl self.RND_IFD = None self.F = None self.T = None self.decryptedSignature = None self.D = None self.D_ = None self.M1 = None self.M_ = None self._dg15 = None def executeAA(self, dg15): """ Perform the Active Authentication protocol. Work only with RSA, modulus length of 1024 and with SHA1. @param dg15: A initialized dataGroup15 object @type dg15: dataGroup15 @return: True if the authentication succeed, else False. @rtype: Boolean @raise ActiveAuthenticationException: If the Active Authentication is not supported (The DG15 is not found or the hash algo is not supported). @raise ActiveAuthenticationException: If the parameter is not set or invalid. @raise ActiveAuthenticationException: If OpenSSL is not installed. @raise ActiveAuthenticationException: If the public key cannot be recovered from the DG15. @raise ActiveAuthenticationException: If the DG15 is invalid and the signature cannot be verified. """ self._dg15 = dg15 self.RND_IFD = self._genRandom(8) self.signature = self._getSignature(self.RND_IFD) self.F = self._decryptSignature(dg15.body, self.signature) (hash, hashSize, offset) = self._getHashAlgo(self.F) self.D = self._extractDigest(self.F, hashSize, offset) self.M1 = self._extractM1(self.F, hashSize, offset) self.M_ = self.M1 + self.RND_IFD self.log("Concatenate M1 with known M2") self.log("\tM*: " + binToHexRep(self.M_)) self.D_ = self._hash(hash, self.M_) self.log("Compare D and D*") self.log("\t" + str(self.D == self.D_)) return self.D == self.D_ def _genRandom(self, size): rnd_ifd = os.urandom(size) self.log("Generate an 8 byte random") self.log("\tRND.IFD: " + binToHexRep(rnd_ifd)) return rnd_ifd def _getSignature(self, rnd_ifd): return self._iso7816.internalAuthentication(rnd_ifd) def getPubKey(self, dg15): """ Retrieve the public key in PEM format from the dataGroup15 @return: A PEM reprensation of the public key @rtype: A string @raise ActiveAuthenticationException: I{The parameter type is not valid, must be a dataGroup15 object}: The parameter dg15 is not set or invalid. @raise ActiveAuthenticationException: I{The public key could not be recovered from the DG15}: Is open SSL installed? """ if type(dg15) != type(datagroup.DataGroup15(None)): raise ActiveAuthenticationException( "The parameter type is not valid, must be a dataGroup15 object" ) return self._openssl.retrieveRsaPubKey(dg15.body) def _decryptSignature(self, pubK, signature): data = self._openssl.retrieveSignedData(pubK, signature) self.log("Decrypt the signature with the public key") self.log("\tF: " + binToHexRep(data)) return data def _hash(self, hash, data): digest = hash(data).digest() self.log("Calculate digest of M*") self.log("\tD*: " + binToHexRep(digest)) return digest def _getHashAlgo(self, sig): hash = None offset = None hashSize = None if sig[-1] == hexRepToBin("BC"): self.T = sig[-1] hash = sha1 offset = -1 elif sig[-1] == hexRepToBin("CC"): self.T = sig[-2] #hash = The algorithm corresponding to the algo designed by T offset = -2 else: raise ActiveAuthenticationException("Unknow hash algorithm") self.log("Determine hash algorithm by trailer T*") self.log("\tT: " + binToHexRep(self.T)) #Find out the hash size hashSize = len(hash("test").digest()) return (hash, hashSize, offset) def _extractDigest(self, sig, hashSize, offset): digest = sig[offset - hashSize:offset] self.log("Extract digest:") self.log("\tD: " + binToHexRep(digest)) return digest def _extractM1(self, sig, hashSize, offset): M1 = sig[1:offset - hashSize] self.log("Extract M1:") self.log("\tM1: " + binToHexRep(M1)) return M1 def __str__(self): spec = self._asn1Parse() return spec.prettyPrint() def algorithm(self, dg15): """ Return the algorithm name used to store the signature @return: A string from the OID dictionnary. @raise ActiveAuthenticationException: I{Unsupported algorithm}: The algorithm does not exist in the OID enumeration. @raise ActiveAuthenticationException: I{The parameter type is not valid, must be a dataGroup15 object}: The parameter dg15 is not set or invalid. """ if type(dg15) != type(datagroup.DataGroup15(None)): raise ActiveAuthenticationException( "The parameter type is not valid, must be a dataGroup15 object" ) algo = "" try: spec = self._asn1Parse() algo = spec.getComponentByName('algorithm').getComponentByName( 'algorithm').prettyPrint() return OID[algo] except KeyError: raise ActiveAuthenticationException("Unsupported algorithm: " + algo) except Exception, msg: raise ActiveAuthenticationException( "Active Authentication not supported: ", msg)
class SignEverything(Logger): """ This class allows a user to sign any 64bits message. The main method is I{sign} """ def __init__(self, iso7816): Logger.__init__(self, "SIGN EVERYTHING ATTACK") self._iso7816 = iso7816 if type(self._iso7816) != type(Iso7816(None)): raise SignEverythingException("The sublayer iso7816 is not available") self._iso7816.rstConnection() self._bac = bac.BAC(iso7816) self._openssl = OpenSSL() def sign(self, message_to_sign="1122334455667788", mrz_value=None): """ Get the signature of a 64bits message from the reader. In order to prevent ICC cloning, the passport implements an Active Authentication (AA) security. The passport signs the 64bits message sent by the reader thanks to its Private key stored in secured memory. This method lets the user decide the 64bits and checks (if MRZ set) with the public key if the message has been signed properly @params message_to_sign: 64bits message to sign @type message_to_sign: String (16 HEX values) @return: A set composed of (The signature, Boolean that states if the signature has been checked) """ validated = False if mrz_value: self.log("Validation required") self.log("MRZ: {0}".format(mrz_value)) public_key = self.getPubKey(self._bac, mrz_value) message = message_to_sign message_bin = hexRepToBin(message) signature = self._iso7816.internalAuthentication(message_bin) self.log("Signature: {0}".format(binToHexRep(signature))) if mrz_value: self.log("Check if the signature is correct regarding the public key:") data = self._openssl.retrieveSignedData(public_key, signature) data_hex = binToHexRep(data) header = data_hex[:2] self.log("\tHeader: {0}".format(header)) M1 = data_hex[2:214] self.log("\tM1: {0}".format(M1)) hash_M = data_hex[214:254] self.log("\tHash: {0}".format(hash_M)) trailer = data_hex[254:256] self.log("\tTrailer: {0}".format(trailer)) # If using SHA-1 if header=='6A' and trailer=='BC': M = hexRepToBin(M1 + message) new_hash = sha1(M).digest() hash_M_bin = hexRepToBin(hash_M) if new_hash==hash_M_bin: self.log("hash(M|message to sign) == Hash") validated = True return (binToHexRep(signature), validated) def getPubKey(self, bac_cp, mrz_value): """ It uses method from pypassport.doc9303.bac in order to authenticate and establish the session keys @param bac_cp: A BAC for the authentication and establishment of session keys @type bac_cp: A pypassport.doc9303.bac.BAC() object @param mrz_value: A MRZ @type mrz_value: String value ("PPPPPPPPPPcCCCYYMMDDcSYYMMDDc<<<<<<<<<<<<<<cd") @return: The public key (DG15) """ self.log("Reset conenction") self._iso7816.rstConnection() self.log("Generate the MRZ object") mrz_pass = mrz.MRZ(mrz_value) self.log("Check the MRZ") mrz_pass.checkMRZ() self.log("Authentication and establishment of session keys") (KSenc, KSmac, ssc) = bac_cp.authenticationAndEstablishmentOfSessionKeys(mrz_pass) self.log("Encryption key: {0}".format(binToHexRep(KSenc))) self.log("MAC key: {0}".format(binToHexRep(KSmac))) self.log("Send Sequence Counter: {0}".format(binToHexRep(ssc))) sm = SecureMessaging(KSenc, KSmac, ssc) self._iso7816.setCiphering(sm) dgReader = datagroup.DataGroupReaderFactory().create(self._iso7816) tag = converter.toTAG("DG15") dgFile = dgReader.readDG(tag) self.log("Get public key") dg15 = datagroup.DataGroupFactory().create(dgFile) self.log("Public key: {0}".format(binToHexRep(dg15.body))) return dg15.body
class ActiveAuthentication(Logger): """ This class implements the Active Authentication protocol. The main method is I{executeAA} that returns True if the verification is ok or False. """ def __init__(self, iso7816, openssl=None): """ @param iso7816: a valid iso7816 object @type iso7816: doc9303 """ Logger.__init__(self, "AA") self._iso7816 = iso7816 if not openssl: self._openssl = OpenSSL() else: self._openssl = openssl self.RND_IFD = None self.F = None self.T = None self.decryptedSignature = None self.D = None self.D_ = None self.M1 = None self.M_ = None self._dg15 = None def executeAA(self, dg15): """ Perform the Active Authentication protocol. Work only with RSA, modulus length of 1024 and with SHA1. @param dg15: An initialized dataGroup15 object @type dg15: dataGroup15 @return: True if the authentication succeeded, else False. @rtype: Boolean @raise ActiveAuthenticationException: If the Active Authentication is not supported (The DG15 is not found or the hash algo is not supported). @raise ActiveAuthenticationException: If the parameter is not set or is invalid. @raise ActiveAuthenticationException: If OpenSSL is not installed. @raise ActiveAuthenticationException: If the public key cannot be recovered from the DG15. @raise ActiveAuthenticationException: If the DG15 is invalid and the signature cannot be verified. """ self._dg15 = dg15 self.RND_IFD = self._genRandom(8) self.signature = self._getSignature(self.RND_IFD) self.F = self._decryptSignature(dg15.body, self.signature) (hash, hashSize, offset) = self._getHashAlgo(self.F) self.D = self._extractDigest(self.F, hashSize, offset) self.M1 = self._extractM1(self.F, hashSize, offset) self.M_ = self.M1 + self.RND_IFD self.log("Concatenate M1 with known M2") self.log("\tM*: " + binToHexRep(self.M_)) self.D_ = self._hash(hash, self.M_) self.log("Compare D and D*") self.log("\t" + str(self.D == self.D_)) return self.D == self.D_ def _genRandom(self, size): rnd_ifd = os.urandom(size) self.log("Generate an 8 byte random") self.log("\tRND.IFD: " + binToHexRep(rnd_ifd)) return rnd_ifd def _getSignature(self, rnd_ifd): return self._iso7816.internalAuthentication(rnd_ifd) def getPubKey(self, dg15): """ Retrieve the public key in PEM format from the dataGroup15 @return: A PEM representation of the public key @rtype: A string @raise ActiveAuthenticationException: I{The parameter type is not valid, must be a dataGroup15 object}: The parameter dg15 is not set or is invalid. @raise ActiveAuthenticationException: I{The public key could not be recovered from the DG15}: Is open SSL installed? """ if type(dg15) != type(datagroup.DataGroup15(None)): raise ActiveAuthenticationException("The parameter type is not valid, must be a dataGroup15 object") return self._openssl.retrieveRsaPubKey(dg15.body) def _decryptSignature(self, pubK, signature): data = self._openssl.retrieveSignedData(pubK, signature) self.log("Decrypt the signature with the public key") self.log("\tF: " + binToHexRep(data)) return data def _hash(self, hash, data): digest = hash(data).digest() self.log("Calculate digest of M*") self.log("\tD*: " + binToHexRep(digest)) return digest def _getHashAlgo(self, sig): hash = None offset = None hashSize = None if sig[-1] == hexRepToBin("BC"): self.T = sig[-1] hash = sha1 offset = -1 elif sig[-1] == hexRepToBin("CC"): self.T = sig[-2] #hash = The algorithm corresponding to the algo designed by T offset = -2 else: raise ActiveAuthenticationException("Unknow hash algorithm") self.log("Determine hash algorithm by trailer T*") self.log("\tT: " + binToHexRep(self.T)) #Find out the hash size hashSize = len(hash("test").digest()) return (hash, hashSize, offset) def _extractDigest(self, sig, hashSize, offset): digest = sig[offset - hashSize:offset] self.log("Extract digest:") self.log("\tD: " + binToHexRep(digest)) return digest def _extractM1(self, sig, hashSize, offset): M1 = sig[1:offset - hashSize] self.log("Extract M1:") self.log("\tM1: " + binToHexRep(M1)) return M1 def __str__(self): spec = self._asn1Parse() return spec.prettyPrint() def algorithm(self, dg15): """ Return the algorithm name used to store the signature @return: A string from the OID dictionary. @raise ActiveAuthenticationException: I{Unsupported algorithm}: The algorithm does not exist in the OID enumeration. @raise ActiveAuthenticationException: I{The parameter type is not valid, must be a dataGroup15 object}: The parameter dg15 is not set or is invalid. """ if type(dg15) != type(datagroup.DataGroup15(None)): raise ActiveAuthenticationException("The parameter type is not valid, must be a dataGroup15 object") algo = "" try: spec = self._asn1Parse() algo = spec.getComponentByName('algorithm').getComponentByName('algorithm').prettyPrint() return OID[algo] except KeyError: raise ActiveAuthenticationException("Unsupported algorithm: " + algo) except Exception, msg: raise ActiveAuthenticationException("Active Authentication not supported: ", msg)