]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add ssl.match_hostname function backported from Python 3.2.
authorBen Darnell <ben@bendarnell.com>
Tue, 15 Feb 2011 04:14:43 +0000 (20:14 -0800)
committerBen Darnell <ben@bendarnell.com>
Tue, 15 Feb 2011 04:14:43 +0000 (20:14 -0800)
https://bitbucket.org/brandon/backports.ssl_match_hostname

tornado/simple_httpclient.py

index f550980bbecb463d9052f825527039482f985b4d..cf9db3bf32eab2418eefad79377d33a6124ba9ca 100644 (file)
@@ -304,6 +304,65 @@ class _HTTPConnection(object):
         self.stream.read_until("\r\n", self._on_chunk_length)
 
 
+# match_hostname was added to the standard library ssl module in python 3.2.
+# The following code was backported for older releases and copied from
+# https://bitbucket.org/brandon/backports.ssl_match_hostname
+class CertificateError(ValueError):
+    pass
+
+def _dnsname_to_pat(dn):
+    pats = []
+    for frag in dn.split(r'.'):
+        if frag == '*':
+            # When '*' is a fragment by itself, it matches a non-empty dotless
+            # fragment.
+            pats.append('[^.]+')
+        else:
+            # Otherwise, '*' matches any dotless fragment.
+            frag = re.escape(frag)
+            pats.append(frag.replace(r'\*', '[^.]*'))
+    return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
+
+def match_hostname(cert, hostname):
+    """Verify that *cert* (in decoded format as returned by
+    SSLSocket.getpeercert()) matches the *hostname*.  RFC 2818 rules
+    are mostly followed, but IP addresses are not accepted for *hostname*.
+
+    CertificateError is raised on failure. On success, the function
+    returns nothing.
+    """
+    if not cert:
+        raise ValueError("empty or no certificate")
+    dnsnames = []
+    san = cert.get('subjectAltName', ())
+    for key, value in san:
+        if key == 'DNS':
+            if _dnsname_to_pat(value).match(hostname):
+                return
+            dnsnames.append(value)
+    if not san:
+        # The subject is only checked when subjectAltName is empty
+        for sub in cert.get('subject', ()):
+            for key, value in sub:
+                # XXX according to RFC 2818, the most specific Common Name
+                # must be used.
+                if key == 'commonName':
+                    if _dnsname_to_pat(value).match(hostname):
+                        return
+                    dnsnames.append(value)
+    if len(dnsnames) > 1:
+        raise CertificateError("hostname %r "
+            "doesn't match either of %s"
+            % (hostname, ', '.join(map(repr, dnsnames))))
+    elif len(dnsnames) == 1:
+        raise CertificateError("hostname %r "
+            "doesn't match %r"
+            % (hostname, dnsnames[0]))
+    else:
+        raise CertificateError("no appropriate commonName or "
+            "subjectAltName fields were found")
+
+
 def main():
     from tornado.options import define, options, parse_command_line
     args = parse_command_line()