52f428733d27f50c20a7753f478d1320d8d75578
[sdc/sdc-distribution-client.git] /
1 """The match_hostname() function from Python 3.3.3, essential when using SSL."""
2
3 # Note: This file is under the PSF license as the code comes from the python
4 # stdlib.   http://docs.python.org/3/license.html
5
6 import re
7
8 __version__ = '3.4.0.2'
9
10 class CertificateError(ValueError):
11     pass
12
13
14 def _dnsname_match(dn, hostname, max_wildcards=1):
15     """Matching according to RFC 6125, section 6.4.3
16
17     http://tools.ietf.org/html/rfc6125#section-6.4.3
18     """
19     pats = []
20     if not dn:
21         return False
22
23     # Ported from python3-syntax:
24     # leftmost, *remainder = dn.split(r'.')
25     parts = dn.split(r'.')
26     leftmost = parts[0]
27     remainder = parts[1:]
28
29     wildcards = leftmost.count('*')
30     if wildcards > max_wildcards:
31         # Issue #17980: avoid denials of service by refusing more
32         # than one wildcard per fragment.  A survey of established
33         # policy among SSL implementations showed it to be a
34         # reasonable choice.
35         raise CertificateError(
36             "too many wildcards in certificate DNS name: " + repr(dn))
37
38     # speed up common case w/o wildcards
39     if not wildcards:
40         return dn.lower() == hostname.lower()
41
42     # RFC 6125, section 6.4.3, subitem 1.
43     # The client SHOULD NOT attempt to match a presented identifier in which
44     # the wildcard character comprises a label other than the left-most label.
45     if leftmost == '*':
46         # When '*' is a fragment by itself, it matches a non-empty dotless
47         # fragment.
48         pats.append('[^.]+')
49     elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
50         # RFC 6125, section 6.4.3, subitem 3.
51         # The client SHOULD NOT attempt to match a presented identifier
52         # where the wildcard character is embedded within an A-label or
53         # U-label of an internationalized domain name.
54         pats.append(re.escape(leftmost))
55     else:
56         # Otherwise, '*' matches any dotless string, e.g. www*
57         pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
58
59     # add the remaining fragments, ignore any wildcards
60     for frag in remainder:
61         pats.append(re.escape(frag))
62
63     pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
64     return pat.match(hostname)
65
66
67 def match_hostname(cert, hostname):
68     """Verify that *cert* (in decoded format as returned by
69     SSLSocket.getpeercert()) matches the *hostname*.  RFC 2818 and RFC 6125
70     rules are followed, but IP addresses are not accepted for *hostname*.
71
72     CertificateError is raised on failure. On success, the function
73     returns nothing.
74     """
75     if not cert:
76         raise ValueError("empty or no certificate")
77     dnsnames = []
78     san = cert.get('subjectAltName', ())
79     for key, value in san:
80         if key == 'DNS':
81             if _dnsname_match(value, hostname):
82                 return
83             dnsnames.append(value)
84     if not dnsnames:
85         # The subject is only checked when there is no dNSName entry
86         # in subjectAltName
87         for sub in cert.get('subject', ()):
88             for key, value in sub:
89                 # XXX according to RFC 2818, the most specific Common Name
90                 # must be used.
91                 if key == 'commonName':
92                     if _dnsname_match(value, hostname):
93                         return
94                     dnsnames.append(value)
95     if len(dnsnames) > 1:
96         raise CertificateError("hostname %r "
97             "doesn't match either of %s"
98             % (hostname, ', '.join(map(repr, dnsnames))))
99     elif len(dnsnames) == 1:
100         raise CertificateError("hostname %r "
101             "doesn't match %r"
102             % (hostname, dnsnames[0]))
103     else:
104         raise CertificateError("no appropriate commonName or "
105             "subjectAltName fields were found")