class CA(Logger): def __init__(self, caLoc=os.path.expanduser('~'), csca=None, cscaKey=None, opensslLocation=""): """ Initiate the CA infrastructure. @caLoc: The location where the openssl config files will be stored @param csca: An existing CSCA Certificate in PEM @param cscaKey: The private key of the CSCA in PEM @param opensslLocation: The openssl executable location """ Logger.__init__(self, "CA") self._csca = csca self._cscaKey = cscaKey self._loc = os.path.normpath(caLoc) self._loc = os.path.join(self._loc, 'ca') self._configFile = os.path.join(self._loc, 'openssl.cfg') try: os.mkdir(self._loc) except: pass self._openssl = OpenSSL('"' + self._configFile + '"') self._openssl.register(self._traceOpenSSl) def createCSCA(self, size=1024, days=720, dn=DistinguishedName(C="BE", O="Gouv", CN="CSCA-BELGIUM")): """ Create a Country Signing Certificate Authority. Return a couple with the x509 as first item and the private key as second item The default distinguished name for the CSCA is: C=BE O=Gouv CN=CSCA-BELGIUM @param size: The RSA key size in bits @param days: The validity period of the certificate @param dn: The distinguised name of the certificate @return: (x509, privateKey) both in PEM """ self._cscaKey = self._openssl.genRSAprKey(size) self._csca = self._genRootHelper(self.cscaKey, days, dn) return (self.csca, self.cscaKey) def _genRootHelper(self, cscaKey, days, dn): try: return self._openssl.genRootX509(cscaKey, days, dn) except OpenSSLException, msg: msg = str(msg) self._errorHandler(msg) return self._openssl.genRootX509(cscaKey, days, dn)
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 CA(Logger): def __init__(self, caLoc=os.path.expanduser('~'), csca=None, cscaKey=None, opensslLocation=""): """ Initiate the CA infrastructure. @caLoc: The location where the openssl config files will be stored @param csca: An existing CSCA Certificate in PEM @param cscaKey: The private key of the CSCA in PEM @param opensslLocation: The openssl executable location """ Logger.__init__(self, "CA") self._csca = csca self._cscaKey = cscaKey self._loc = os.path.normpath(caLoc) self._loc = os.path.join(self._loc, 'ca') self._configFile = os.path.join(self._loc, 'openssl.cfg') try: os.mkdir(self._loc) except: pass self._openssl = OpenSSL('"' + self._configFile + '"') self._openssl.register(self._traceOpenSSl) def createCSCA(self, size=1024, days=720, dn=DistinguishedName(C="BE", O="Gouv", CN="CSCA-BELGIUM")): """ Create a Country Signing Certificate Authority. Return a couple with the x509 as first item and the private key as second item The default distinguished name for the CSCA is: C=BE O=Gouv CN=CSCA-BELGIUM @param size: The RSA key size in bits @param days: The validity period of the certificate @param dn: The distinguised name of the certificate @return: (x509, privateKey) both in PEM """ self._cscaKey = self._openssl.genRSAprKey(size) self._csca = self._genRootHelper(self.cscaKey, days, dn) return (self.csca, self.cscaKey) def _genRootHelper(self, cscaKey, days, dn): try: return self._openssl.genRootX509(cscaKey, days, dn) except OpenSSLException as msg: msg = str(msg) self._errorHandler(msg) return self._openssl.genRootX509(cscaKey, days, dn) def createDS(self, size=1024, days=365, dn=DistinguishedName(C="BE", O="Gouv", CN="Document-Signer-BELGIUM")): """ Create a Document Signer Certificate. Return a couple with the x509 as first item and the private key as second item The default distinguished name for the DS is: C=BE O=Gouv CN=Document Signer BELGIUM @param size: The RSA key size in bits @param days: The validity period of the certificate @param dn: The distinguised name of the certificate @return: (x509, privateKey) both in PEM """ self._testinit() dsKey = self._openssl.genRSAprKey(size) dsReq = self._openssl.genX509Req(dsKey, dn) ds = self._signX509Helper(dsReq, days) return (ds, dsKey) def _signX509Helper(self, dsReq, days): try: return self._openssl.signX509Req(dsReq, self.csca, self.cscaKey, days) except OpenSSLException as msg: msg = str(msg) self._errorHandler(msg) return self._signX509Helper(dsReq, days) def revoke(self, x509): """ Revoke the certificate. Return the CRL in PEM. @param x509: A x509 certificate @return: The CRL in PEM @rtype: A string """ self._testinit() self._openssl.revokeX509(x509, self.csca, self.cscaKey) def _traceOpenSSl(self, name, msg): self.log(msg, name) def getCrl(self): if not os.path.isfile(os.path.join(self._loc, 'crlnumber')): self._openssl._toDisk(os.path.join(self._loc, 'crlnumber'), "01") self.log("echo '01' > ca/cerlnumber") try: crl = self._openssl.genCRL(self.csca, self.cscaKey) except OpenSSLException as msg: msg = str(msg) self._errorHandler(msg) self.getCrl() return self._openssl.crlToDER(crl) def _errorHandler(self, msg): if msg.find('newcerts') > 0: os.makedirs(os.path.join(self._loc, 'newcerts')) self.log("mkdir ca/newcerts") elif msg.find('ca/index.txt') > 0: self._openssl._toDisk(os.path.join(self._loc, 'index.txt')) self.log("touch ca/index.txt") self._openssl._toDisk(os.path.join(self._loc, 'index.txt.attr'), "unique_subject = no") self.log("echo 'unique_subject = no' > ca/index.txt.attr") elif msg.find('ca/serial') > 0: self._openssl._toDisk(os.path.join(self._loc, 'serial'), "01") self.log("echo '01' > ca/serial") elif msg.find('openssl.cfg') > 0: self._openssl._toDisk(self._configFile, self._getConfigFile(self._loc)) self.log("Creation of ppenssl.cfg") else: raise OpenSSLException(msg) self.log(msg) def resetConfig(self): try: shutil.rmtree(self._loc) except: pass os.makedirs(os.path.join(self._loc, 'newcerts')) self._openssl._toDisk(os.path.join(self._loc, 'index.txt')) self._openssl._toDisk(os.path.join(self._loc, 'index.txt.attr'), "unique_subject = no") self._openssl._toDisk(os.path.join(self._loc, 'serial'), "01") self._openssl._toDisk(os.path.join(self._loc, 'crlnumber'), "01") self._openssl._toDisk(self._configFile, self._getConfigFile(self._loc)) def _testinit(self): if not ((self.csca != None) and (self.cscaKey != None)): raise OpenSSLException("The root CSCA Certificate is not set.") def printCrl(self, crl): return self._openssl.printCrl(crl) def getCsca(self): return self._csca def getCscaKey(self): return self._cscaKey def setCsca(self, value): self._csca = value def setCscaKey(self, value): self._cscaKey = value csca = property(getCsca, setCsca, None, None) cscaKey = property(getCscaKey, setCscaKey, None, None) def _getConfigFile(self, path): altsep = os.altsep if not altsep: altsep = os.path.sep return """# pour signer un certificat CA intermediaire [ ca ] default_ca = CA_default # The default ca section [ CA_default ] database = """ + os.path.join(self._loc, "index.txt").replace( os.path.sep, altsep) + """ # database index file. new_certs_dir = """ + os.path.join(self._loc, "newcerts").replace( os.path.sep, altsep) + """ # default place for new certs. crlnumber = """ + os.path.join(self._loc, "crlnumber").replace( os.path.sep, altsep) + """ serial = """ + os.path.join(self._loc, "serial").replace( os.path.sep, altsep) + """ # The current serial number