| OLD | NEW |
| (Empty) |
| 1 """Class representing an X.509 certificate chain.""" | |
| 2 | |
| 3 from utils import cryptomath | |
| 4 from X509 import X509 | |
| 5 | |
| 6 class X509CertChain: | |
| 7 """This class represents a chain of X.509 certificates. | |
| 8 | |
| 9 @type x509List: list | |
| 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. | |
| 13 """ | |
| 14 | |
| 15 def __init__(self, x509List=None): | |
| 16 """Create a new X509CertChain. | |
| 17 | |
| 18 @type x509List: list | |
| 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. | |
| 22 """ | |
| 23 if x509List: | |
| 24 self.x509List = x509List | |
| 25 else: | |
| 26 self.x509List = [] | |
| 27 | |
| 28 def parseChain(self, s): | |
| 29 """Parse a PEM-encoded X.509 certificate file chain file. | |
| 30 | |
| 31 @type s: str | |
| 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. | |
| 36 """ | |
| 37 | |
| 38 class PEMIterator(object): | |
| 39 """Simple iterator over PEM-encoded certificates within a string. | |
| 40 | |
| 41 @type data: 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. | |
| 46 | |
| 47 @type index: integer | |
| 48 @ivar index: The current offset within data to begin iterating from. | |
| 49 """ | |
| 50 | |
| 51 _CERTIFICATE_HEADER = "-----BEGIN CERTIFICATE-----" | |
| 52 """The PEM encoding block header for X.509 certificates.""" | |
| 53 | |
| 54 _CERTIFICATE_FOOTER = "-----END CERTIFICATE-----" | |
| 55 """The PEM encoding block footer for X.509 certificates.""" | |
| 56 | |
| 57 def __init__(self, s): | |
| 58 self.data = s | |
| 59 self.index = 0 | |
| 60 | |
| 61 def __iter__(self): | |
| 62 return self | |
| 63 | |
| 64 def next(self): | |
| 65 """Iterates and returns the next L{tlslite.X509.X509} | |
| 66 certificate in data. | |
| 67 | |
| 68 @rtype tlslite.X509.X509 | |
| 69 """ | |
| 70 | |
| 71 self.index = self.data.find(self._CERTIFICATE_HEADER, | |
| 72 self.index) | |
| 73 if self.index == -1: | |
| 74 raise StopIteration | |
| 75 end = self.data.find(self._CERTIFICATE_FOOTER, self.index) | |
| 76 if end == -1: | |
| 77 raise StopIteration | |
| 78 | |
| 79 certStr = self.data[self.index+len(self._CERTIFICATE_HEADER) : | |
| 80 end] | |
| 81 self.index = end + len(self._CERTIFICATE_FOOTER) | |
| 82 bytes = cryptomath.base64ToBytes(certStr) | |
| 83 return X509().parseBinary(bytes) | |
| 84 | |
| 85 self.x509List = list(PEMIterator(s)) | |
| 86 return self | |
| 87 | |
| 88 def getNumCerts(self): | |
| 89 """Get the number of certificates in this chain. | |
| 90 | |
| 91 @rtype: int | |
| 92 """ | |
| 93 return len(self.x509List) | |
| 94 | |
| 95 def getEndEntityPublicKey(self): | |
| 96 """Get the public key from the end-entity certificate. | |
| 97 | |
| 98 @rtype: L{tlslite.utils.RSAKey.RSAKey} | |
| 99 """ | |
| 100 if self.getNumCerts() == 0: | |
| 101 raise AssertionError() | |
| 102 return self.x509List[0].publicKey | |
| 103 | |
| 104 def getFingerprint(self): | |
| 105 """Get the hex-encoded fingerprint of the end-entity certificate. | |
| 106 | |
| 107 @rtype: str | |
| 108 @return: A hex-encoded fingerprint. | |
| 109 """ | |
| 110 if self.getNumCerts() == 0: | |
| 111 raise AssertionError() | |
| 112 return self.x509List[0].getFingerprint() | |
| 113 | |
| 114 def getCommonName(self): | |
| 115 """Get the Subject's Common Name from the end-entity certificate. | |
| 116 | |
| 117 The cryptlib_py module must be installed in order to use this | |
| 118 function. | |
| 119 | |
| 120 @rtype: str or None | |
| 121 @return: The CN component of the certificate's subject DN, if | |
| 122 present. | |
| 123 """ | |
| 124 if self.getNumCerts() == 0: | |
| 125 raise AssertionError() | |
| 126 return self.x509List[0].getCommonName() | |
| 127 | |
| 128 def validate(self, x509TrustList): | |
| 129 """Check the validity of the certificate chain. | |
| 130 | |
| 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. | |
| 134 | |
| 135 The cryptlib_py module must be installed in order to use this | |
| 136 function. | |
| 137 | |
| 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 | |
| 141 be considered valid. | |
| 142 """ | |
| 143 | |
| 144 import cryptlib_py | |
| 145 c1 = None | |
| 146 c2 = None | |
| 147 lastC = None | |
| 148 rootC = None | |
| 149 | |
| 150 try: | |
| 151 rootFingerprints = [c.getFingerprint() for c in x509TrustList] | |
| 152 | |
| 153 #Check that every certificate in the chain validates with the | |
| 154 #next one | |
| 155 for cert1, cert2 in zip(self.x509List, self.x509List[1:]): | |
| 156 | |
| 157 #If we come upon a root certificate, we're done. | |
| 158 if cert1.getFingerprint() in rootFingerprints: | |
| 159 return True | |
| 160 | |
| 161 c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(), | |
| 162 cryptlib_py.CRYPT_UNUSED) | |
| 163 c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(), | |
| 164 cryptlib_py.CRYPT_UNUSED) | |
| 165 try: | |
| 166 cryptlib_py.cryptCheckCert(c1, c2) | |
| 167 except: | |
| 168 return False | |
| 169 cryptlib_py.cryptDestroyCert(c1) | |
| 170 c1 = None | |
| 171 cryptlib_py.cryptDestroyCert(c2) | |
| 172 c2 = None | |
| 173 | |
| 174 #If the last certificate is one of the root certificates, we're | |
| 175 #done. | |
| 176 if self.x509List[-1].getFingerprint() in rootFingerprints: | |
| 177 return True | |
| 178 | |
| 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): | |
| 187 try: | |
| 188 cryptlib_py.cryptCheckCert(lastC, rootC) | |
| 189 return True | |
| 190 except: | |
| 191 return False | |
| 192 return False | |
| 193 finally: | |
| 194 if not (c1 is None): | |
| 195 cryptlib_py.cryptDestroyCert(c1) | |
| 196 if not (c2 is None): | |
| 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) | |
| 202 | |
| 203 | |
| 204 | |
| 205 def _checkChaining(self, lastC, rootC): | |
| 206 import cryptlib_py | |
| 207 import array | |
| 208 def compareNames(name): | |
| 209 try: | |
| 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: | |
| 216 lastName = None | |
| 217 try: | |
| 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: | |
| 224 rootName = None | |
| 225 | |
| 226 return lastName == rootName | |
| 227 | |
| 228 cryptlib_py.cryptSetAttribute(lastC, | |
| 229 cryptlib_py.CRYPT_CERTINFO_ISSUERNAME, | |
| 230 cryptlib_py.CRYPT_UNUSED) | |
| 231 | |
| 232 if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME): | |
| 233 return False | |
| 234 if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME): | |
| 235 return False | |
| 236 if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME): | |
| 237 return False | |
| 238 if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME): | |
| 239 return False | |
| 240 if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME): | |
| 241 return False | |
| 242 return True | |
| OLD | NEW |