"""Class representing an X.509 certificate chain.""" | |
from utils import cryptomath | |
class X509CertChain: | |
"""This class represents a chain of X.509 certificates. | |
@type x509List: list | |
@ivar x509List: A list of L{tlslite.X509.X509} instances, | |
starting with the end-entity certificate and with every | |
subsequent certificate certifying the previous. | |
""" | |
def __init__(self, x509List=None): | |
"""Create a new X509CertChain. | |
@type x509List: list | |
@param x509List: A list of L{tlslite.X509.X509} instances, | |
starting with the end-entity certificate and with every | |
subsequent certificate certifying the previous. | |
""" | |
if x509List: | |
self.x509List = x509List | |
else: | |
self.x509List = [] | |
def getNumCerts(self): | |
"""Get the number of certificates in this chain. | |
@rtype: int | |
""" | |
return len(self.x509List) | |
def getEndEntityPublicKey(self): | |
"""Get the public key from the end-entity certificate. | |
@rtype: L{tlslite.utils.RSAKey.RSAKey} | |
""" | |
if self.getNumCerts() == 0: | |
raise AssertionError() | |
return self.x509List[0].publicKey | |
def getFingerprint(self): | |
"""Get the hex-encoded fingerprint of the end-entity certificate. | |
@rtype: str | |
@return: A hex-encoded fingerprint. | |
""" | |
if self.getNumCerts() == 0: | |
raise AssertionError() | |
return self.x509List[0].getFingerprint() | |
def getCommonName(self): | |
"""Get the Subject's Common Name from the end-entity certificate. | |
The cryptlib_py module must be installed in order to use this | |
function. | |
@rtype: str or None | |
@return: The CN component of the certificate's subject DN, if | |
present. | |
""" | |
if self.getNumCerts() == 0: | |
raise AssertionError() | |
return self.x509List[0].getCommonName() | |
def validate(self, x509TrustList): | |
"""Check the validity of the certificate chain. | |
This checks that every certificate in the chain validates with | |
the subsequent one, until some certificate validates with (or | |
is identical to) one of the passed-in root certificates. | |
The cryptlib_py module must be installed in order to use this | |
function. | |
@type x509TrustList: list of L{tlslite.X509.X509} | |
@param x509TrustList: A list of trusted root certificates. The | |
certificate chain must extend to one of these certificates to | |
be considered valid. | |
""" | |
import cryptlib_py | |
c1 = None | |
c2 = None | |
lastC = None | |
rootC = None | |
try: | |
rootFingerprints = [c.getFingerprint() for c in x509TrustList] | |
#Check that every certificate in the chain validates with the | |
#next one | |
for cert1, cert2 in zip(self.x509List, self.x509List[1:]): | |
#If we come upon a root certificate, we're done. | |
if cert1.getFingerprint() in rootFingerprints: | |
return True | |
c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(), | |
cryptlib_py.CRYPT_UNUSED) | |
c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(), | |
cryptlib_py.CRYPT_UNUSED) | |
try: | |
cryptlib_py.cryptCheckCert(c1, c2) | |
except: | |
return False | |
cryptlib_py.cryptDestroyCert(c1) | |
c1 = None | |
cryptlib_py.cryptDestroyCert(c2) | |
c2 = None | |
#If the last certificate is one of the root certificates, we're | |
#done. | |
if self.x509List[-1].getFingerprint() in rootFingerprints: | |
return True | |
#Otherwise, find a root certificate that the last certificate | |
#chains to, and validate them. | |
lastC = cryptlib_py.cryptImportCert(self.x509List[-1].writeBytes(), | |
cryptlib_py.CRYPT_UNUSED) | |
for rootCert in x509TrustList: | |
rootC = cryptlib_py.cryptImportCert(rootCert.writeBytes(), | |
cryptlib_py.CRYPT_UNUSED) | |
if self._checkChaining(lastC, rootC): | |
try: | |
cryptlib_py.cryptCheckCert(lastC, rootC) | |
return True | |
except: | |
return False | |
return False | |
finally: | |
if not (c1 is None): | |
cryptlib_py.cryptDestroyCert(c1) | |
if not (c2 is None): | |
cryptlib_py.cryptDestroyCert(c2) | |
if not (lastC is None): | |
cryptlib_py.cryptDestroyCert(lastC) | |
if not (rootC is None): | |
cryptlib_py.cryptDestroyCert(rootC) | |
def _checkChaining(self, lastC, rootC): | |
import cryptlib_py | |
import array | |
def compareNames(name): | |
try: | |
length = cryptlib_py.cryptGetAttributeString(lastC, name, None) | |
lastName = array.array('B', [0] * length) | |
cryptlib_py.cryptGetAttributeString(lastC, name, lastName) | |
lastName = lastName.tostring() | |
except cryptlib_py.CryptException, e: | |
if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: | |
lastName = None | |
try: | |
length = cryptlib_py.cryptGetAttributeString(rootC, name, None) | |
rootName = array.array('B', [0] * length) | |
cryptlib_py.cryptGetAttributeString(rootC, name, rootName) | |
rootName = rootName.tostring() | |
except cryptlib_py.CryptException, e: | |
if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: | |
rootName = None | |
return lastName == rootName | |
cryptlib_py.cryptSetAttribute(lastC, | |
cryptlib_py.CRYPT_CERTINFO_ISSUERNAME, | |
cryptlib_py.CRYPT_UNUSED) | |
if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME): | |
return False | |
if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME): | |
return False | |
if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME): | |
return False | |
if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME): | |
return False | |
if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME): | |
return False | |
return True |