1
1
# 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
3
3
4
4
import re
5
5
@@ -8,31 +8,61 @@ class CertificateError(ValueError):
8
8
pass
9
9
10
10
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
+ """
12
16
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 )
30
60
31
61
32
62
def match_hostname (cert , hostname ):
33
63
"""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*.
36
66
37
67
CertificateError is raised on failure. On success, the function
38
68
returns nothing.
@@ -43,17 +73,18 @@ def match_hostname(cert, hostname):
43
73
san = cert .get ('subjectAltName' , ())
44
74
for key , value in san :
45
75
if key == 'DNS' :
46
- if _dnsname_to_pat (value ). match ( hostname ):
76
+ if _dnsname_match (value , hostname ):
47
77
return
48
78
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
51
82
for sub in cert .get ('subject' , ()):
52
83
for key , value in sub :
53
84
# XXX according to RFC 2818, the most specific Common Name
54
85
# must be used.
55
86
if key == 'commonName' :
56
- if _dnsname_to_pat (value ). match ( hostname ):
87
+ if _dnsname_match (value , hostname ):
57
88
return
58
89
dnsnames .append (value )
59
90
if len (dnsnames ) > 1 :
0 commit comments