11# Backport of the match_hostname logic introduced in python 3.2
2- # http://svn .python.org/projects/python/branches/release32-maint /Lib/ssl.py
2+ # http://hg .python.org/releasing/3.3.5/file/993955b807b3 /Lib/ssl.py
33
44import re
55
@@ -8,31 +8,61 @@ class CertificateError(ValueError):
88 pass
99
1010
11- def _dnsname_to_pat (dn , max_wildcards = 1 ):
11+ def _dnsname_match (dn , hostname , max_wildcards = 1 ):
12+ """Matching according to RFC 6125, section 6.4.3
13+
14+ http://tools.ietf.org/html/rfc6125#section-6.4.3
15+ """
1216 pats = []
13- for frag in dn .split (r'.' ):
14- if frag .count ('*' ) > max_wildcards :
15- # Issue #17980: avoid denials of service by refusing more
16- # than one wildcard per fragment. A survery of established
17- # policy among SSL implementations showed it to be a
18- # reasonable choice.
19- raise CertificateError (
20- "too many wildcards in certificate DNS name: " + repr (dn ))
21- if frag == '*' :
22- # When '*' is a fragment by itself, it matches a non-empty dotless
23- # fragment.
24- pats .append ('[^.]+' )
25- else :
26- # Otherwise, '*' matches any dotless fragment.
27- frag = re .escape (frag )
28- pats .append (frag .replace (r'\*' , '[^.]*' ))
29- return re .compile (r'\A' + r'\.' .join (pats ) + r'\Z' , re .IGNORECASE )
17+ if not dn :
18+ return False
19+
20+ parts = dn .split (r'.' )
21+ leftmost = parts [0 ]
22+ remainder = parts [1 :]
23+
24+ wildcards = leftmost .count ('*' )
25+ if wildcards > max_wildcards :
26+ # Issue #17980: avoid denials of service by refusing more
27+ # than one wildcard per fragment. A survey of established
28+ # policy among SSL implementations showed it to be a
29+ # reasonable choice.
30+ raise CertificateError (
31+ "too many wildcards in certificate DNS name: " + repr (dn ))
32+
33+ # speed up common case w/o wildcards
34+ if not wildcards :
35+ return dn .lower () == hostname .lower ()
36+
37+ # RFC 6125, section 6.4.3, subitem 1.
38+ # The client SHOULD NOT attempt to match a presented identifier in which
39+ # the wildcard character comprises a label other than the left-most label.
40+ if leftmost == '*' :
41+ # When '*' is a fragment by itself, it matches a non-empty dotless
42+ # fragment.
43+ pats .append ('[^.]+' )
44+ elif leftmost .startswith ('xn--' ) or hostname .startswith ('xn--' ):
45+ # RFC 6125, section 6.4.3, subitem 3.
46+ # The client SHOULD NOT attempt to match a presented identifier
47+ # where the wildcard character is embedded within an A-label or
48+ # U-label of an internationalized domain name.
49+ pats .append (re .escape (leftmost ))
50+ else :
51+ # Otherwise, '*' matches any dotless string, e.g. www*
52+ pats .append (re .escape (leftmost ).replace (r'\*' , '[^.]*' ))
53+
54+ # add the remaining fragments, ignore any wildcards
55+ for frag in remainder :
56+ pats .append (re .escape (frag ))
57+
58+ pat = re .compile (r'\A' + r'\.' .join (pats ) + r'\Z' , re .IGNORECASE )
59+ return pat .match (hostname )
3060
3161
3262def match_hostname (cert , hostname ):
3363 """Verify that *cert* (in decoded format as returned by
34- SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
35- are mostly followed, but IP addresses are not accepted for *hostname*.
64+ SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
65+ rules are followed, but IP addresses are not accepted for *hostname*.
3666
3767 CertificateError is raised on failure. On success, the function
3868 returns nothing.
@@ -43,17 +73,18 @@ def match_hostname(cert, hostname):
4373 san = cert .get ('subjectAltName' , ())
4474 for key , value in san :
4575 if key == 'DNS' :
46- if _dnsname_to_pat (value ). match ( hostname ):
76+ if _dnsname_match (value , hostname ):
4777 return
4878 dnsnames .append (value )
49- if not san :
50- # The subject is only checked when subjectAltName is empty
79+ if not dnsnames :
80+ # The subject is only checked when there is no dNSName entry
81+ # in subjectAltName
5182 for sub in cert .get ('subject' , ()):
5283 for key , value in sub :
5384 # XXX according to RFC 2818, the most specific Common Name
5485 # must be used.
5586 if key == 'commonName' :
56- if _dnsname_to_pat (value ). match ( hostname ):
87+ if _dnsname_match (value , hostname ):
5788 return
5889 dnsnames .append (value )
5990 if len (dnsnames ) > 1 :
0 commit comments