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 |