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