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 |