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)
Ejemplo n.º 3
0
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)