@@ -304,6 +304,65 @@ def _on_chunk_data(self, data):
304
304
self .stream .read_until ("\r \n " , self ._on_chunk_length )
305
305
306
306
307
+ # match_hostname was added to the standard library ssl module in python 3.2.
308
+ # The following code was backported for older releases and copied from
309
+ # https://bitbucket.org/brandon/backports.ssl_match_hostname
310
+ class CertificateError (ValueError ):
311
+ pass
312
+
313
+ def _dnsname_to_pat (dn ):
314
+ pats = []
315
+ for frag in dn .split (r'.' ):
316
+ if frag == '*' :
317
+ # When '*' is a fragment by itself, it matches a non-empty dotless
318
+ # fragment.
319
+ pats .append ('[^.]+' )
320
+ else :
321
+ # Otherwise, '*' matches any dotless fragment.
322
+ frag = re .escape (frag )
323
+ pats .append (frag .replace (r'\*' , '[^.]*' ))
324
+ return re .compile (r'\A' + r'\.' .join (pats ) + r'\Z' , re .IGNORECASE )
325
+
326
+ def match_hostname (cert , hostname ):
327
+ """Verify that *cert* (in decoded format as returned by
328
+ SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
329
+ are mostly followed, but IP addresses are not accepted for *hostname*.
330
+
331
+ CertificateError is raised on failure. On success, the function
332
+ returns nothing.
333
+ """
334
+ if not cert :
335
+ raise ValueError ("empty or no certificate" )
336
+ dnsnames = []
337
+ san = cert .get ('subjectAltName' , ())
338
+ for key , value in san :
339
+ if key == 'DNS' :
340
+ if _dnsname_to_pat (value ).match (hostname ):
341
+ return
342
+ dnsnames .append (value )
343
+ if not san :
344
+ # The subject is only checked when subjectAltName is empty
345
+ for sub in cert .get ('subject' , ()):
346
+ for key , value in sub :
347
+ # XXX according to RFC 2818, the most specific Common Name
348
+ # must be used.
349
+ if key == 'commonName' :
350
+ if _dnsname_to_pat (value ).match (hostname ):
351
+ return
352
+ dnsnames .append (value )
353
+ if len (dnsnames ) > 1 :
354
+ raise CertificateError ("hostname %r "
355
+ "doesn't match either of %s"
356
+ % (hostname , ', ' .join (map (repr , dnsnames ))))
357
+ elif len (dnsnames ) == 1 :
358
+ raise CertificateError ("hostname %r "
359
+ "doesn't match %r"
360
+ % (hostname , dnsnames [0 ]))
361
+ else :
362
+ raise CertificateError ("no appropriate commonName or "
363
+ "subjectAltName fields were found" )
364
+
365
+
307
366
def main ():
308
367
from tornado .options import define , options , parse_command_line
309
368
args = parse_command_line ()
0 commit comments