Show More
@@ -10,6 +10,7 b'' | |||||
10 | from __future__ import absolute_import |
|
10 | from __future__ import absolute_import | |
11 |
|
11 | |||
12 | import os |
|
12 | import os | |
|
13 | import re | |||
13 | import ssl |
|
14 | import ssl | |
14 | import sys |
|
15 | import sys | |
15 |
|
16 | |||
@@ -167,6 +168,57 b' def wrapsocket(sock, keyfile, certfile, ' | |||||
167 | raise error.Abort(_('ssl connection failed')) |
|
168 | raise error.Abort(_('ssl connection failed')) | |
168 | return sslsocket |
|
169 | return sslsocket | |
169 |
|
170 | |||
|
171 | class wildcarderror(Exception): | |||
|
172 | """Represents an error parsing wildcards in DNS name.""" | |||
|
173 | ||||
|
174 | def _dnsnamematch(dn, hostname, maxwildcards=1): | |||
|
175 | """Match DNS names according RFC 6125 section 6.4.3. | |||
|
176 | ||||
|
177 | This code is effectively copied from CPython's ssl._dnsname_match. | |||
|
178 | ||||
|
179 | Returns a bool indicating whether the expected hostname matches | |||
|
180 | the value in ``dn``. | |||
|
181 | """ | |||
|
182 | pats = [] | |||
|
183 | if not dn: | |||
|
184 | return False | |||
|
185 | ||||
|
186 | pieces = dn.split(r'.') | |||
|
187 | leftmost = pieces[0] | |||
|
188 | remainder = pieces[1:] | |||
|
189 | wildcards = leftmost.count('*') | |||
|
190 | if wildcards > maxwildcards: | |||
|
191 | raise wildcarderror( | |||
|
192 | _('too many wildcards in certificate DNS name: %s') % dn) | |||
|
193 | ||||
|
194 | # speed up common case w/o wildcards | |||
|
195 | if not wildcards: | |||
|
196 | return dn.lower() == hostname.lower() | |||
|
197 | ||||
|
198 | # RFC 6125, section 6.4.3, subitem 1. | |||
|
199 | # The client SHOULD NOT attempt to match a presented identifier in which | |||
|
200 | # the wildcard character comprises a label other than the left-most label. | |||
|
201 | if leftmost == '*': | |||
|
202 | # When '*' is a fragment by itself, it matches a non-empty dotless | |||
|
203 | # fragment. | |||
|
204 | pats.append('[^.]+') | |||
|
205 | elif leftmost.startswith('xn--') or hostname.startswith('xn--'): | |||
|
206 | # RFC 6125, section 6.4.3, subitem 3. | |||
|
207 | # The client SHOULD NOT attempt to match a presented identifier | |||
|
208 | # where the wildcard character is embedded within an A-label or | |||
|
209 | # U-label of an internationalized domain name. | |||
|
210 | pats.append(re.escape(leftmost)) | |||
|
211 | else: | |||
|
212 | # Otherwise, '*' matches any dotless string, e.g. www* | |||
|
213 | pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) | |||
|
214 | ||||
|
215 | # add the remaining fragments, ignore any wildcards | |||
|
216 | for frag in remainder: | |||
|
217 | pats.append(re.escape(frag)) | |||
|
218 | ||||
|
219 | pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) | |||
|
220 | return pat.match(hostname) is not None | |||
|
221 | ||||
170 | def _verifycert(cert, hostname): |
|
222 | def _verifycert(cert, hostname): | |
171 | '''Verify that cert (in socket.getpeercert() format) matches hostname. |
|
223 | '''Verify that cert (in socket.getpeercert() format) matches hostname. | |
172 | CRLs is not handled. |
|
224 | CRLs is not handled. | |
@@ -175,33 +227,46 b' def _verifycert(cert, hostname):' | |||||
175 | ''' |
|
227 | ''' | |
176 | if not cert: |
|
228 | if not cert: | |
177 | return _('no certificate received') |
|
229 | return _('no certificate received') | |
178 | dnsname = hostname.lower() |
|
|||
179 | def matchdnsname(certname): |
|
|||
180 | return (certname == dnsname or |
|
|||
181 | '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]) |
|
|||
182 |
|
230 | |||
|
231 | dnsnames = [] | |||
183 | san = cert.get('subjectAltName', []) |
|
232 | san = cert.get('subjectAltName', []) | |
184 | if san: |
|
233 | for key, value in san: | |
185 | certnames = [value.lower() for key, value in san if key == 'DNS'] |
|
234 | if key == 'DNS': | |
186 | for name in certnames: |
|
235 | try: | |
187 |
if |
|
236 | if _dnsnamematch(value, hostname): | |
188 |
return |
|
237 | return | |
189 | if certnames: |
|
238 | except wildcarderror as e: | |
190 | return _('certificate is for %s') % ', '.join(certnames) |
|
239 | return e.message | |
|
240 | ||||
|
241 | dnsnames.append(value) | |||
191 |
|
242 | |||
192 | # subject is only checked when subjectAltName is empty |
|
243 | if not dnsnames: | |
193 | for s in cert.get('subject', []): |
|
244 | # The subject is only checked when there is no DNS in subjectAltName. | |
194 | key, value = s[0] |
|
245 | for sub in cert.get('subject', []): | |
195 | if key == 'commonName': |
|
246 | for key, value in sub: | |
196 | try: |
|
247 | # According to RFC 2818 the most specific Common Name must | |
197 |
# |
|
248 | # be used. | |
198 | certname = value.lower().encode('ascii') |
|
249 | if key == 'commonName': | |
199 | except UnicodeEncodeError: |
|
250 | # 'subject' entries are unicide. | |
200 | return _('IDN in certificate not supported') |
|
251 | try: | |
201 | if matchdnsname(certname): |
|
252 | value = value.encode('ascii') | |
202 | return None |
|
253 | except UnicodeEncodeError: | |
203 |
return _('certificate |
|
254 | return _('IDN in certificate not supported') | |
204 | return _('no commonName or subjectAltName found in certificate') |
|
255 | ||
|
256 | try: | |||
|
257 | if _dnsnamematch(value, hostname): | |||
|
258 | return | |||
|
259 | except wildcarderror as e: | |||
|
260 | return e.message | |||
|
261 | ||||
|
262 | dnsnames.append(value) | |||
|
263 | ||||
|
264 | if len(dnsnames) > 1: | |||
|
265 | return _('certificate is for %s') % ', '.join(dnsnames) | |||
|
266 | elif len(dnsnames) == 1: | |||
|
267 | return _('certificate is for %s') % dnsnames[0] | |||
|
268 | else: | |||
|
269 | return _('no commonName or subjectAltName found in certificate') | |||
205 |
|
270 | |||
206 |
|
271 | |||
207 | # CERT_REQUIRED means fetch the cert from the server all the time AND |
|
272 | # CERT_REQUIRED means fetch the cert from the server all the time AND |
@@ -51,8 +51,7 b" check(_verifycert(san_cert, 'example.com" | |||||
51 | # Avoid some pitfalls |
|
51 | # Avoid some pitfalls | |
52 | check(_verifycert(cert('*.foo'), 'foo'), |
|
52 | check(_verifycert(cert('*.foo'), 'foo'), | |
53 | 'certificate is for *.foo') |
|
53 | 'certificate is for *.foo') | |
54 | check(_verifycert(cert('*o'), 'foo'), |
|
54 | check(_verifycert(cert('*o'), 'foo'), None) | |
55 | 'certificate is for *o') |
|
|||
56 |
|
55 | |||
57 | check(_verifycert({'subject': ()}, |
|
56 | check(_verifycert({'subject': ()}, | |
58 | 'example.com'), |
|
57 | 'example.com'), | |
@@ -82,13 +81,12 b" check(_verifycert(cert('*.a.com'), 'a.co" | |||||
82 | 'certificate is for *.a.com') |
|
81 | 'certificate is for *.a.com') | |
83 | check(_verifycert(cert('*.a.com'), 'Xa.com'), |
|
82 | check(_verifycert(cert('*.a.com'), 'Xa.com'), | |
84 | 'certificate is for *.a.com') |
|
83 | 'certificate is for *.a.com') | |
85 |
check(_verifycert(cert('*.a.com'), '.a.com'), |
|
84 | check(_verifycert(cert('*.a.com'), '.a.com'), | |
|
85 | 'certificate is for *.a.com') | |||
86 |
|
86 | |||
87 | # only match one left-most wildcard |
|
87 | # only match one left-most wildcard | |
88 | check(_verifycert(cert('f*.com'), 'foo.com'), |
|
88 | check(_verifycert(cert('f*.com'), 'foo.com'), None) | |
89 | 'certificate is for f*.com') |
|
89 | check(_verifycert(cert('f*.com'), 'f.com'), None) | |
90 | check(_verifycert(cert('f*.com'), 'f.com'), |
|
|||
91 | 'certificate is for f*.com') |
|
|||
92 | check(_verifycert(cert('f*.com'), 'bar.com'), |
|
90 | check(_verifycert(cert('f*.com'), 'bar.com'), | |
93 | 'certificate is for f*.com') |
|
91 | 'certificate is for f*.com') | |
94 | check(_verifycert(cert('f*.com'), 'foo.a.com'), |
|
92 | check(_verifycert(cert('f*.com'), 'foo.a.com'), | |
@@ -136,10 +134,10 b" check(_verifycert(cert('xn--p*.python.or" | |||||
136 | idna = u'www*.pythön.org'.encode('idna').decode('ascii') |
|
134 | idna = u'www*.pythön.org'.encode('idna').decode('ascii') | |
137 | check(_verifycert(cert(idna), |
|
135 | check(_verifycert(cert(idna), | |
138 | u'www.pythön.org'.encode('idna').decode('ascii')), |
|
136 | u'www.pythön.org'.encode('idna').decode('ascii')), | |
139 | 'certificate is for www*.xn--pythn-mua.org') |
|
137 | None) | |
140 | check(_verifycert(cert(idna), |
|
138 | check(_verifycert(cert(idna), | |
141 | u'www1.pythön.org'.encode('idna').decode('ascii')), |
|
139 | u'www1.pythön.org'.encode('idna').decode('ascii')), | |
142 | 'certificate is for www*.xn--pythn-mua.org') |
|
140 | None) | |
143 | check(_verifycert(cert(idna), |
|
141 | check(_verifycert(cert(idna), | |
144 | u'ftp.pythön.org'.encode('idna').decode('ascii')), |
|
142 | u'ftp.pythön.org'.encode('idna').decode('ascii')), | |
145 | 'certificate is for www*.xn--pythn-mua.org') |
|
143 | 'certificate is for www*.xn--pythn-mua.org') | |
@@ -229,11 +227,12 b" check(_verifycert({}, 'example.com'), 'n" | |||||
229 | # avoid denials of service by refusing more than one |
|
227 | # avoid denials of service by refusing more than one | |
230 | # wildcard per fragment. |
|
228 | # wildcard per fragment. | |
231 | check(_verifycert({'subject': (((u'commonName', u'a*b.com'),),)}, |
|
229 | check(_verifycert({'subject': (((u'commonName', u'a*b.com'),),)}, | |
232 |
'axxb.com'), |
|
230 | 'axxb.com'), None) | |
233 | check(_verifycert({'subject': (((u'commonName', u'a*b.co*'),),)}, |
|
231 | check(_verifycert({'subject': (((u'commonName', u'a*b.co*'),),)}, | |
234 | 'axxb.com'), 'certificate is for a*b.co*') |
|
232 | 'axxb.com'), 'certificate is for a*b.co*') | |
235 | check(_verifycert({'subject': (((u'commonName', u'a*b*.com'),),)}, |
|
233 | check(_verifycert({'subject': (((u'commonName', u'a*b*.com'),),)}, | |
236 |
'axxbxxc.com'), |
|
234 | 'axxbxxc.com'), | |
|
235 | 'too many wildcards in certificate DNS name: a*b*.com') | |||
237 |
|
236 | |||
238 | def test_url(): |
|
237 | def test_url(): | |
239 | """ |
|
238 | """ |
General Comments 0
You need to be logged in to leave comments.
Login now