tests: import CPython's hostname matching tests stable
authorGregory Szorc <gregory.szorc@gmail.com>
Sun, 26 Jun 2016 19:16:54 -0700
branchstable
changeset 29451 676f4d0e3a7b
parent 29450 66c709b5af4b
child 29452 26a5d605b868
tests: import CPython's hostname matching tests CPython has a more comprehensive test suite for it's built-in hostname matching functionality. This patch adds its tests so we can improve our hostname matching functionality. Many of the tests have different results from CPython. These will be addressed in a subsequent commit.
tests/test-url.py
--- a/tests/test-url.py	Fri Jul 01 07:41:37 2016 -0300
+++ b/tests/test-url.py	Sun Jun 26 19:16:54 2016 -0700
@@ -1,3 +1,4 @@
+# coding=utf-8
 from __future__ import absolute_import, print_function
 
 import doctest
@@ -63,6 +64,177 @@
 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
       'IDN in certificate not supported')
 
+# The following tests are from CPython's test_ssl.py.
+check(_verifycert(cert('example.com'), 'example.com'), None)
+check(_verifycert(cert('example.com'), 'ExAmple.cOm'), None)
+check(_verifycert(cert('example.com'), 'www.example.com'),
+      'certificate is for example.com')
+check(_verifycert(cert('example.com'), '.example.com'),
+      'certificate is for example.com')
+check(_verifycert(cert('example.com'), 'example.org'),
+      'certificate is for example.com')
+check(_verifycert(cert('example.com'), 'exampleXcom'),
+      'certificate is for example.com')
+check(_verifycert(cert('*.a.com'), 'foo.a.com'), None)
+check(_verifycert(cert('*.a.com'), 'bar.foo.a.com'),
+      'certificate is for *.a.com')
+check(_verifycert(cert('*.a.com'), 'a.com'),
+      'certificate is for *.a.com')
+check(_verifycert(cert('*.a.com'), 'Xa.com'),
+      'certificate is for *.a.com')
+check(_verifycert(cert('*.a.com'), '.a.com'), None)
+
+# only match one left-most wildcard
+check(_verifycert(cert('f*.com'), 'foo.com'),
+      'certificate is for f*.com')
+check(_verifycert(cert('f*.com'), 'f.com'),
+      'certificate is for f*.com')
+check(_verifycert(cert('f*.com'), 'bar.com'),
+      'certificate is for f*.com')
+check(_verifycert(cert('f*.com'), 'foo.a.com'),
+      'certificate is for f*.com')
+check(_verifycert(cert('f*.com'), 'bar.foo.com'),
+      'certificate is for f*.com')
+
+# NULL bytes are bad, CVE-2013-4073
+check(_verifycert(cert('null.python.org\x00example.org'),
+                  'null.python.org\x00example.org'), None)
+check(_verifycert(cert('null.python.org\x00example.org'),
+                  'example.org'),
+      'certificate is for null.python.org\x00example.org')
+check(_verifycert(cert('null.python.org\x00example.org'),
+                  'null.python.org'),
+      'certificate is for null.python.org\x00example.org')
+
+# error cases with wildcards
+check(_verifycert(cert('*.*.a.com'), 'bar.foo.a.com'),
+      'certificate is for *.*.a.com')
+check(_verifycert(cert('*.*.a.com'), 'a.com'),
+      'certificate is for *.*.a.com')
+check(_verifycert(cert('*.*.a.com'), 'Xa.com'),
+      'certificate is for *.*.a.com')
+check(_verifycert(cert('*.*.a.com'), '.a.com'),
+      'certificate is for *.*.a.com')
+
+check(_verifycert(cert('a.*.com'), 'a.foo.com'),
+      'certificate is for a.*.com')
+check(_verifycert(cert('a.*.com'), 'a..com'),
+      'certificate is for a.*.com')
+check(_verifycert(cert('a.*.com'), 'a.com'),
+      'certificate is for a.*.com')
+
+# wildcard doesn't match IDNA prefix 'xn--'
+idna = u'püthon.python.org'.encode('idna').decode('ascii')
+check(_verifycert(cert(idna), idna), None)
+check(_verifycert(cert('x*.python.org'), idna),
+      'certificate is for x*.python.org')
+check(_verifycert(cert('xn--p*.python.org'), idna),
+      'certificate is for xn--p*.python.org')
+
+# wildcard in first fragment and  IDNA A-labels in sequent fragments
+# are supported.
+idna = u'www*.pythön.org'.encode('idna').decode('ascii')
+check(_verifycert(cert(idna),
+                  u'www.pythön.org'.encode('idna').decode('ascii')),
+      'certificate is for www*.xn--pythn-mua.org')
+check(_verifycert(cert(idna),
+                  u'www1.pythön.org'.encode('idna').decode('ascii')),
+      'certificate is for www*.xn--pythn-mua.org')
+check(_verifycert(cert(idna),
+                  u'ftp.pythön.org'.encode('idna').decode('ascii')),
+      'certificate is for www*.xn--pythn-mua.org')
+check(_verifycert(cert(idna),
+                  u'pythön.org'.encode('idna').decode('ascii')),
+      'certificate is for www*.xn--pythn-mua.org')
+
+c = {
+    'notAfter': 'Jun 26 21:41:46 2011 GMT',
+    'subject': (((u'commonName', u'linuxfrz.org'),),),
+    'subjectAltName': (
+        ('DNS', 'linuxfr.org'),
+        ('DNS', 'linuxfr.com'),
+        ('othername', '<unsupported>'),
+    )
+}
+check(_verifycert(c, 'linuxfr.org'), None)
+check(_verifycert(c, 'linuxfr.com'), None)
+# Not a "DNS" entry
+check(_verifycert(c, '<unsupported>'),
+      'certificate is for linuxfr.org, linuxfr.com')
+# When there is a subjectAltName, commonName isn't used
+check(_verifycert(c, 'linuxfrz.org'),
+      'certificate is for linuxfr.org, linuxfr.com')
+
+# A pristine real-world example
+c = {
+    'notAfter': 'Dec 18 23:59:59 2011 GMT',
+    'subject': (
+        ((u'countryName', u'US'),),
+        ((u'stateOrProvinceName', u'California'),),
+        ((u'localityName', u'Mountain View'),),
+        ((u'organizationName', u'Google Inc'),),
+        ((u'commonName', u'mail.google.com'),),
+    ),
+}
+check(_verifycert(c, 'mail.google.com'), None)
+check(_verifycert(c, 'gmail.com'), 'certificate is for mail.google.com')
+
+# Only commonName is considered
+check(_verifycert(c, 'California'), 'certificate is for mail.google.com')
+
+# Neither commonName nor subjectAltName
+c = {
+    'notAfter': 'Dec 18 23:59:59 2011 GMT',
+    'subject': (
+        ((u'countryName', u'US'),),
+        ((u'stateOrProvinceName', u'California'),),
+        ((u'localityName', u'Mountain View'),),
+        ((u'organizationName', u'Google Inc'),),
+    ),
+}
+check(_verifycert(c, 'mail.google.com'),
+      'no commonName or subjectAltName found in certificate')
+
+# No DNS entry in subjectAltName but a commonName
+c = {
+    'notAfter': 'Dec 18 23:59:59 2099 GMT',
+    'subject': (
+        ((u'countryName', u'US'),),
+        ((u'stateOrProvinceName', u'California'),),
+        ((u'localityName', u'Mountain View'),),
+        ((u'commonName', u'mail.google.com'),),
+    ),
+    'subjectAltName': (('othername', 'blabla'),),
+}
+check(_verifycert(c, 'mail.google.com'), None)
+
+# No DNS entry subjectAltName and no commonName
+c = {
+    'notAfter': 'Dec 18 23:59:59 2099 GMT',
+    'subject': (
+        ((u'countryName', u'US'),),
+        ((u'stateOrProvinceName', u'California'),),
+        ((u'localityName', u'Mountain View'),),
+        ((u'organizationName', u'Google Inc'),),
+    ),
+    'subjectAltName': (('othername', 'blabla'),),
+}
+check(_verifycert(c, 'google.com'),
+      'no commonName or subjectAltName found in certificate')
+
+# Empty cert / no cert
+check(_verifycert(None, 'example.com'), 'no certificate received')
+check(_verifycert({}, 'example.com'), 'no certificate received')
+
+# avoid denials of service by refusing more than one
+# wildcard per fragment.
+check(_verifycert({'subject': (((u'commonName', u'a*b.com'),),)},
+                  'axxb.com'), 'certificate is for a*b.com')
+check(_verifycert({'subject': (((u'commonName', u'a*b.co*'),),)},
+                  'axxb.com'), 'certificate is for a*b.co*')
+check(_verifycert({'subject': (((u'commonName', u'a*b*.com'),),)},
+                  'axxbxxc.com'), 'certificate is for a*b*.com')
+
 def test_url():
     """
     >>> from mercurial.util import url