Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/sslutil.py @ 29459:fd93b15b5c30
merge with stable
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Fri, 01 Jul 2016 16:02:56 -0500 |
parents | 5b71a8d7f7ff 26a5d605b868 |
children | 4e72995f6c9c |
comparison
equal
deleted
inserted
replaced
29458:59058549a611 | 29459:fd93b15b5c30 |
---|---|
9 | 9 |
10 from __future__ import absolute_import | 10 from __future__ import absolute_import |
11 | 11 |
12 import hashlib | 12 import hashlib |
13 import os | 13 import os |
14 import re | |
14 import ssl | 15 import ssl |
15 import sys | 16 import sys |
16 | 17 |
17 from .i18n import _ | 18 from .i18n import _ |
18 from . import ( | 19 from . import ( |
313 'ui': ui, | 314 'ui': ui, |
314 } | 315 } |
315 | 316 |
316 return sslsocket | 317 return sslsocket |
317 | 318 |
319 class wildcarderror(Exception): | |
320 """Represents an error parsing wildcards in DNS name.""" | |
321 | |
322 def _dnsnamematch(dn, hostname, maxwildcards=1): | |
323 """Match DNS names according RFC 6125 section 6.4.3. | |
324 | |
325 This code is effectively copied from CPython's ssl._dnsname_match. | |
326 | |
327 Returns a bool indicating whether the expected hostname matches | |
328 the value in ``dn``. | |
329 """ | |
330 pats = [] | |
331 if not dn: | |
332 return False | |
333 | |
334 pieces = dn.split(r'.') | |
335 leftmost = pieces[0] | |
336 remainder = pieces[1:] | |
337 wildcards = leftmost.count('*') | |
338 if wildcards > maxwildcards: | |
339 raise wildcarderror( | |
340 _('too many wildcards in certificate DNS name: %s') % dn) | |
341 | |
342 # speed up common case w/o wildcards | |
343 if not wildcards: | |
344 return dn.lower() == hostname.lower() | |
345 | |
346 # RFC 6125, section 6.4.3, subitem 1. | |
347 # The client SHOULD NOT attempt to match a presented identifier in which | |
348 # the wildcard character comprises a label other than the left-most label. | |
349 if leftmost == '*': | |
350 # When '*' is a fragment by itself, it matches a non-empty dotless | |
351 # fragment. | |
352 pats.append('[^.]+') | |
353 elif leftmost.startswith('xn--') or hostname.startswith('xn--'): | |
354 # RFC 6125, section 6.4.3, subitem 3. | |
355 # The client SHOULD NOT attempt to match a presented identifier | |
356 # where the wildcard character is embedded within an A-label or | |
357 # U-label of an internationalized domain name. | |
358 pats.append(re.escape(leftmost)) | |
359 else: | |
360 # Otherwise, '*' matches any dotless string, e.g. www* | |
361 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) | |
362 | |
363 # add the remaining fragments, ignore any wildcards | |
364 for frag in remainder: | |
365 pats.append(re.escape(frag)) | |
366 | |
367 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) | |
368 return pat.match(hostname) is not None | |
369 | |
318 def _verifycert(cert, hostname): | 370 def _verifycert(cert, hostname): |
319 '''Verify that cert (in socket.getpeercert() format) matches hostname. | 371 '''Verify that cert (in socket.getpeercert() format) matches hostname. |
320 CRLs is not handled. | 372 CRLs is not handled. |
321 | 373 |
322 Returns error message if any problems are found and None on success. | 374 Returns error message if any problems are found and None on success. |
323 ''' | 375 ''' |
324 if not cert: | 376 if not cert: |
325 return _('no certificate received') | 377 return _('no certificate received') |
326 dnsname = hostname.lower() | 378 |
327 def matchdnsname(certname): | 379 dnsnames = [] |
328 return (certname == dnsname or | |
329 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]) | |
330 | |
331 san = cert.get('subjectAltName', []) | 380 san = cert.get('subjectAltName', []) |
332 if san: | 381 for key, value in san: |
333 certnames = [value.lower() for key, value in san if key == 'DNS'] | 382 if key == 'DNS': |
334 for name in certnames: | |
335 if matchdnsname(name): | |
336 return None | |
337 if certnames: | |
338 return _('certificate is for %s') % ', '.join(certnames) | |
339 | |
340 # subject is only checked when subjectAltName is empty | |
341 for s in cert.get('subject', []): | |
342 key, value = s[0] | |
343 if key == 'commonName': | |
344 try: | 383 try: |
345 # 'subject' entries are unicode | 384 if _dnsnamematch(value, hostname): |
346 certname = value.lower().encode('ascii') | 385 return |
347 except UnicodeEncodeError: | 386 except wildcarderror as e: |
348 return _('IDN in certificate not supported') | 387 return e.message |
349 if matchdnsname(certname): | 388 |
350 return None | 389 dnsnames.append(value) |
351 return _('certificate is for %s') % certname | 390 |
352 return _('no commonName or subjectAltName found in certificate') | 391 if not dnsnames: |
392 # The subject is only checked when there is no DNS in subjectAltName. | |
393 for sub in cert.get('subject', []): | |
394 for key, value in sub: | |
395 # According to RFC 2818 the most specific Common Name must | |
396 # be used. | |
397 if key == 'commonName': | |
398 # 'subject' entries are unicide. | |
399 try: | |
400 value = value.encode('ascii') | |
401 except UnicodeEncodeError: | |
402 return _('IDN in certificate not supported') | |
403 | |
404 try: | |
405 if _dnsnamematch(value, hostname): | |
406 return | |
407 except wildcarderror as e: | |
408 return e.message | |
409 | |
410 dnsnames.append(value) | |
411 | |
412 if len(dnsnames) > 1: | |
413 return _('certificate is for %s') % ', '.join(dnsnames) | |
414 elif len(dnsnames) == 1: | |
415 return _('certificate is for %s') % dnsnames[0] | |
416 else: | |
417 return _('no commonName or subjectAltName found in certificate') | |
353 | 418 |
354 def _plainapplepython(): | 419 def _plainapplepython(): |
355 """return true if this seems to be a pure Apple Python that | 420 """return true if this seems to be a pure Apple Python that |
356 * is unfrozen and presumably has the whole mercurial module in the file | 421 * is unfrozen and presumably has the whole mercurial module in the file |
357 system | 422 system |