1 """Class representing an X.509 certificate chain."""
3 from utils import cryptomath
7 """This class represents a chain of X.509 certificates.
10 @ivar x509List: A list of L{tlslite.X509.X509} instances,
11 starting with the end-entity certificate and with every
12 subsequent certificate certifying the previous.
15 def __init__(self, x509List=None):
16 """Create a new X509CertChain.
19 @param x509List: A list of L{tlslite.X509.X509} instances,
20 starting with the end-entity certificate and with every
21 subsequent certificate certifying the previous.
24 self.x509List = x509List
28 def parseChain(self, s):
29 """Parse a PEM-encoded X.509 certificate file chain file.
32 @param s: A PEM-encoded (eg: Base64) X.509 certificate file, with every
33 certificate wrapped within "-----BEGIN CERTIFICATE-----" and
34 "-----END CERTIFICATE-----" tags). Extraneous data outside such tags,
35 such as human readable representations, will be ignored.
38 class PEMIterator(object):
39 """Simple iterator over PEM-encoded certificates within a string.
42 @ivar data: A string containing PEM-encoded (Base64) certificates,
43 with every certificate wrapped within "-----BEGIN CERTIFICATE-----"
44 and "-----END CERTIFICATE-----" tags). Extraneous data outside such
45 tags, such as human readable representations, will be ignored.
48 @ivar index: The current offset within data to begin iterating from.
51 _CERTIFICATE_HEADER = "-----BEGIN CERTIFICATE-----"
52 """The PEM encoding block header for X.509 certificates."""
54 _CERTIFICATE_FOOTER = "-----END CERTIFICATE-----"
55 """The PEM encoding block footer for X.509 certificates."""
57 def __init__(self, s):
65 """Iterates and returns the next L{tlslite.X509.X509}
68 @rtype tlslite.X509.X509
71 self.index = self.data.find(self._CERTIFICATE_HEADER,
75 end = self.data.find(self._CERTIFICATE_FOOTER, self.index)
79 certStr = self.data[self.index+len(self._CERTIFICATE_HEADER) :
81 self.index = end + len(self._CERTIFICATE_FOOTER)
82 bytes = cryptomath.base64ToBytes(certStr)
83 return X509().parseBinary(bytes)
85 self.x509List = list(PEMIterator(s))
88 def getNumCerts(self):
89 """Get the number of certificates in this chain.
93 return len(self.x509List)
95 def getEndEntityPublicKey(self):
96 """Get the public key from the end-entity certificate.
98 @rtype: L{tlslite.utils.RSAKey.RSAKey}
100 if self.getNumCerts() == 0:
101 raise AssertionError()
102 return self.x509List[0].publicKey
104 def getFingerprint(self):
105 """Get the hex-encoded fingerprint of the end-entity certificate.
108 @return: A hex-encoded fingerprint.
110 if self.getNumCerts() == 0:
111 raise AssertionError()
112 return self.x509List[0].getFingerprint()
114 def getCommonName(self):
115 """Get the Subject's Common Name from the end-entity certificate.
117 The cryptlib_py module must be installed in order to use this
121 @return: The CN component of the certificate's subject DN, if
124 if self.getNumCerts() == 0:
125 raise AssertionError()
126 return self.x509List[0].getCommonName()
128 def validate(self, x509TrustList):
129 """Check the validity of the certificate chain.
131 This checks that every certificate in the chain validates with
132 the subsequent one, until some certificate validates with (or
133 is identical to) one of the passed-in root certificates.
135 The cryptlib_py module must be installed in order to use this
138 @type x509TrustList: list of L{tlslite.X509.X509}
139 @param x509TrustList: A list of trusted root certificates. The
140 certificate chain must extend to one of these certificates to
151 rootFingerprints = [c.getFingerprint() for c in x509TrustList]
153 #Check that every certificate in the chain validates with the
155 for cert1, cert2 in zip(self.x509List, self.x509List[1:]):
157 #If we come upon a root certificate, we're done.
158 if cert1.getFingerprint() in rootFingerprints:
161 c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(),
162 cryptlib_py.CRYPT_UNUSED)
163 c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(),
164 cryptlib_py.CRYPT_UNUSED)
166 cryptlib_py.cryptCheckCert(c1, c2)
169 cryptlib_py.cryptDestroyCert(c1)
171 cryptlib_py.cryptDestroyCert(c2)
174 #If the last certificate is one of the root certificates, we're
176 if self.x509List[-1].getFingerprint() in rootFingerprints:
179 #Otherwise, find a root certificate that the last certificate
180 #chains to, and validate them.
181 lastC = cryptlib_py.cryptImportCert(self.x509List[-1].writeBytes(),
182 cryptlib_py.CRYPT_UNUSED)
183 for rootCert in x509TrustList:
184 rootC = cryptlib_py.cryptImportCert(rootCert.writeBytes(),
185 cryptlib_py.CRYPT_UNUSED)
186 if self._checkChaining(lastC, rootC):
188 cryptlib_py.cryptCheckCert(lastC, rootC)
195 cryptlib_py.cryptDestroyCert(c1)
197 cryptlib_py.cryptDestroyCert(c2)
198 if not (lastC is None):
199 cryptlib_py.cryptDestroyCert(lastC)
200 if not (rootC is None):
201 cryptlib_py.cryptDestroyCert(rootC)
205 def _checkChaining(self, lastC, rootC):
208 def compareNames(name):
210 length = cryptlib_py.cryptGetAttributeString(lastC, name, None)
211 lastName = array.array('B', [0] * length)
212 cryptlib_py.cryptGetAttributeString(lastC, name, lastName)
213 lastName = lastName.tostring()
214 except cryptlib_py.CryptException, e:
215 if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND:
218 length = cryptlib_py.cryptGetAttributeString(rootC, name, None)
219 rootName = array.array('B', [0] * length)
220 cryptlib_py.cryptGetAttributeString(rootC, name, rootName)
221 rootName = rootName.tostring()
222 except cryptlib_py.CryptException, e:
223 if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND:
226 return lastName == rootName
228 cryptlib_py.cryptSetAttribute(lastC,
229 cryptlib_py.CRYPT_CERTINFO_ISSUERNAME,
230 cryptlib_py.CRYPT_UNUSED)
232 if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME):
234 if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME):
236 if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME):
238 if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME):
240 if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME):