summaryrefslogtreecommitdiffstats
path: root/src/lib/tlslite/X509CertChain.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/tlslite/X509CertChain.py')
-rwxr-xr-xsrc/lib/tlslite/X509CertChain.py181
1 files changed, 181 insertions, 0 deletions
diff --git a/src/lib/tlslite/X509CertChain.py b/src/lib/tlslite/X509CertChain.py
new file mode 100755
index 000000000..d5f0b4d42
--- /dev/null
+++ b/src/lib/tlslite/X509CertChain.py
@@ -0,0 +1,181 @@
+"""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 \ No newline at end of file