|
|
f35787 |
diff --git a/CHANGES.rst b/CHANGES.rst
|
|
|
f35787 |
index b2b8ae6..0150d85 100644
|
|
|
f35787 |
--- a/CHANGES.rst
|
|
|
f35787 |
+++ b/CHANGES.rst
|
|
|
f35787 |
@@ -1,6 +1,9 @@
|
|
|
f35787 |
Changes
|
|
|
f35787 |
=======
|
|
|
f35787 |
|
|
|
f35787 |
+* Accept ``iPAddress`` subject alternative name fields in TLS certificates.
|
|
|
f35787 |
+ (Issue #258)
|
|
|
f35787 |
+
|
|
|
f35787 |
1.10.2 (2015-02-25)
|
|
|
f35787 |
+++++++++++++++++++
|
|
|
f35787 |
|
|
|
f35787 |
diff --git a/dummyserver/certs/server.ip_san.crt b/dummyserver/certs/server.ip_san.crt
|
|
|
f35787 |
new file mode 100644
|
|
|
f35787 |
index 000000000..58689d64d
|
|
|
f35787 |
--- /dev/null
|
|
|
f35787 |
+++ b/dummyserver/certs/server.ip_san.crt
|
|
|
f35787 |
@@ -0,0 +1,21 @@
|
|
|
f35787 |
+-----BEGIN CERTIFICATE-----
|
|
|
f35787 |
+MIIDeTCCAuKgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UEBhMCRkkx
|
|
|
f35787 |
+DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQHDAVkdW1teTEOMAwGA1UECgwFZHVtbXkx
|
|
|
f35787 |
+DjAMBgNVBAsMBWR1bW15MREwDwYDVQQDDAhTbmFrZU9pbDEfMB0GCSqGSIb3DQEJ
|
|
|
f35787 |
+ARYQZHVtbXlAdGVzdC5sb2NhbDAeFw0xMTEyMjIwNzU4NDBaFw0yMTEyMTgwNzU4
|
|
|
f35787 |
+NDBaMGExCzAJBgNVBAYTAkZJMQ4wDAYDVQQIDAVkdW1teTEOMAwGA1UEBwwFZHVt
|
|
|
f35787 |
+bXkxDjAMBgNVBAoMBWR1bW15MQ4wDAYDVQQLDAVkdW1teTESMBAGA1UEAwwJbG9j
|
|
|
f35787 |
+YWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXe3FqmCWvP8XPxqtT
|
|
|
f35787 |
++0bfL1Tvzvebi46k0WIcUV8bP3vyYiSRXG9ALmyzZH4GHY9UVs4OEDkCMDOBSezB
|
|
|
f35787 |
+0y9ai/9doTNcaictdEBu8nfdXKoTtzrn+VX4UPrkH5hm7NQ1fTQuj1MR7yBCmYqN
|
|
|
f35787 |
+3Q2Q+Efuujyx0FwBzAuy1aKYuwIDAQABo4IBHjCCARowCQYDVR0TBAIwADAdBgNV
|
|
|
f35787 |
+HQ4EFgQUG+dK5Uos08QUwAWofDb3a8YcYlIwgbYGA1UdIwSBrjCBq4AUGXd/I2Ji
|
|
|
f35787 |
+QllF+3Wdx3NyBLszCi2hgYekgYQwgYExCzAJBgNVBAYTAkZJMQ4wDAYDVQQIDAVk
|
|
|
f35787 |
+dW1teTEOMAwGA1UEBwwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MQ4wDAYDVQQLDAVk
|
|
|
f35787 |
+dW1teTERMA8GA1UEAwwIU25ha2VPaWwxHzAdBgkqhkiG9w0BCQEWEGR1bW15QHRl
|
|
|
f35787 |
+c3QubG9jYWyCCQCz67HKL+G/4zAJBgNVHRIEAjAAMCoGA1UdEQQjMCGBDnJvb3RA
|
|
|
f35787 |
+bG9jYWxob3N0gglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEFBQADgYEAFEAy
|
|
|
f35787 |
+O9rxM14W0pVJWHTZkWBcDTqp8A8OB3JFVxeuCNcbtyfyYLWs2juv4YMmo1EKBOQe
|
|
|
f35787 |
+7LYfGuIvtIzT7KBa2QAPmX9JR+F6yl0IVSrYYt9hS7w9Cqr8+jK9QRpNwm3k25hp
|
|
|
f35787 |
+BmmoT5b9Q+AYcLMtdMu3uFjLmQBI2XobI/9vCT4=
|
|
|
f35787 |
+-----END CERTIFICATE-----
|
|
|
f35787 |
diff --git a/dummyserver/server.py b/dummyserver/server.py
|
|
|
f35787 |
index 18d81e1..3190835 100755
|
|
|
f35787 |
--- a/dummyserver/server.py
|
|
|
f35787 |
+++ b/dummyserver/server.py
|
|
|
f35787 |
@@ -32,6 +32,10 @@ NO_SAN_CERTS = {
|
|
|
f35787 |
'certfile': os.path.join(CERTS_PATH, 'server.no_san.crt'),
|
|
|
f35787 |
'keyfile': DEFAULT_CERTS['keyfile']
|
|
|
f35787 |
}
|
|
|
f35787 |
+IP_SAN_CERTS = {
|
|
|
f35787 |
+ 'certfile': os.path.join(CERTS_PATH, 'server.ip_san.crt'),
|
|
|
f35787 |
+ 'keyfile': DEFAULT_CERTS['keyfile']
|
|
|
f35787 |
+}
|
|
|
f35787 |
DEFAULT_CA = os.path.join(CERTS_PATH, 'cacert.pem')
|
|
|
f35787 |
DEFAULT_CA_BAD = os.path.join(CERTS_PATH, 'client_bad.pem')
|
|
|
f35787 |
NO_SAN_CA = os.path.join(CERTS_PATH, 'cacert.no_san.pem')
|
|
|
f35787 |
diff --git a/urllib3/packages/ssl_match_hostname/__init__.py b/urllib3/packages/ssl_match_hostname/__init__.py
|
|
|
f35787 |
index dd59a75fd..d6594eb26 100644
|
|
|
f35787 |
--- a/urllib3/packages/ssl_match_hostname/__init__.py
|
|
|
f35787 |
+++ b/urllib3/packages/ssl_match_hostname/__init__.py
|
|
|
f35787 |
@@ -1,5 +1,11 @@
|
|
|
f35787 |
+import sys
|
|
|
f35787 |
+
|
|
|
f35787 |
try:
|
|
|
f35787 |
- # Python 3.2+
|
|
|
f35787 |
+ # Our match_hostname function is the same as 3.5's, so we only want to
|
|
|
f35787 |
+ # import the match_hostname function if it's at least that good.
|
|
|
f35787 |
+ if sys.version_info < (3, 5):
|
|
|
f35787 |
+ raise ImportError("Fallback to vendored code")
|
|
|
f35787 |
+
|
|
|
f35787 |
from ssl import CertificateError, match_hostname
|
|
|
f35787 |
except ImportError:
|
|
|
f35787 |
try:
|
|
|
f35787 |
diff --git a/urllib3/packages/ssl_match_hostname/_implementation.py b/urllib3/packages/ssl_match_hostname/_implementation.py
|
|
|
f35787 |
index 52f428733..1fd42f38a 100644
|
|
|
f35787 |
--- a/urllib3/packages/ssl_match_hostname/_implementation.py
|
|
|
f35787 |
+++ b/urllib3/packages/ssl_match_hostname/_implementation.py
|
|
|
f35787 |
@@ -4,8 +4,20 @@
|
|
|
f35787 |
# stdlib. http://docs.python.org/3/license.html
|
|
|
f35787 |
|
|
|
f35787 |
import re
|
|
|
f35787 |
+import sys
|
|
|
f35787 |
+
|
|
|
f35787 |
+# ipaddress has been backported to 2.6+ in pypi. If it is installed on the
|
|
|
f35787 |
+# system, use it to handle IPAddress ServerAltnames (this was added in
|
|
|
f35787 |
+# python-3.5) otherwise only do DNS matching. This allows
|
|
|
f35787 |
+# backports.ssl_match_hostname to continue to be used all the way back to
|
|
|
f35787 |
+# python-2.4.
|
|
|
f35787 |
+try:
|
|
|
f35787 |
+ import ipaddress
|
|
|
f35787 |
+except ImportError:
|
|
|
f35787 |
+ ipaddress = None
|
|
|
f35787 |
+
|
|
|
f35787 |
+__version__ = '3.5.0.1'
|
|
|
f35787 |
|
|
|
f35787 |
-__version__ = '3.4.0.2'
|
|
|
f35787 |
|
|
|
f35787 |
class CertificateError(ValueError):
|
|
|
f35787 |
pass
|
|
|
f35787 |
@@ -64,6 +76,23 @@ def _dnsname_match(dn, hostname, max_wildcards=1):
|
|
|
f35787 |
return pat.match(hostname)
|
|
|
f35787 |
|
|
|
f35787 |
|
|
|
f35787 |
+def _to_unicode(obj):
|
|
|
f35787 |
+ if isinstance(obj, str) and sys.version_info < (3,):
|
|
|
f35787 |
+ obj = unicode(obj, encoding='ascii', errors='strict')
|
|
|
f35787 |
+ return obj
|
|
|
f35787 |
+
|
|
|
f35787 |
+def _ipaddress_match(ipname, host_ip):
|
|
|
f35787 |
+ """Exact matching of IP addresses.
|
|
|
f35787 |
+
|
|
|
f35787 |
+ RFC 6125 explicitly doesn't define an algorithm for this
|
|
|
f35787 |
+ (section 1.7.2 - "Out of Scope").
|
|
|
f35787 |
+ """
|
|
|
f35787 |
+ # OpenSSL may add a trailing newline to a subjectAltName's IP address
|
|
|
f35787 |
+ # Divergence from upstream: ipaddress can't handle byte str
|
|
|
f35787 |
+ ip = ipaddress.ip_address(_to_unicode(ipname).rstrip())
|
|
|
f35787 |
+ return ip == host_ip
|
|
|
f35787 |
+
|
|
|
f35787 |
+
|
|
|
f35787 |
def match_hostname(cert, hostname):
|
|
|
f35787 |
"""Verify that *cert* (in decoded format as returned by
|
|
|
f35787 |
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
|
|
|
f35787 |
@@ -73,12 +102,35 @@ def match_hostname(cert, hostname):
|
|
|
f35787 |
returns nothing.
|
|
|
f35787 |
"""
|
|
|
f35787 |
if not cert:
|
|
|
f35787 |
- raise ValueError("empty or no certificate")
|
|
|
f35787 |
+ raise ValueError("empty or no certificate, match_hostname needs a "
|
|
|
f35787 |
+ "SSL socket or SSL context with either "
|
|
|
f35787 |
+ "CERT_OPTIONAL or CERT_REQUIRED")
|
|
|
f35787 |
+ try:
|
|
|
f35787 |
+ # Divergence from upstream: ipaddress can't handle byte str
|
|
|
f35787 |
+ host_ip = ipaddress.ip_address(_to_unicode(hostname))
|
|
|
f35787 |
+ except ValueError:
|
|
|
f35787 |
+ # Not an IP address (common case)
|
|
|
f35787 |
+ host_ip = None
|
|
|
f35787 |
+ except UnicodeError:
|
|
|
f35787 |
+ # Divergence from upstream: Have to deal with ipaddress not taking
|
|
|
f35787 |
+ # byte strings. addresses should be all ascii, so we consider it not
|
|
|
f35787 |
+ # an ipaddress in this case
|
|
|
f35787 |
+ host_ip = None
|
|
|
f35787 |
+ except AttributeError:
|
|
|
f35787 |
+ # Divergence from upstream: Make ipaddress library optional
|
|
|
f35787 |
+ if ipaddress is None:
|
|
|
f35787 |
+ host_ip = None
|
|
|
f35787 |
+ else:
|
|
|
f35787 |
+ raise
|
|
|
f35787 |
dnsnames = []
|
|
|
f35787 |
san = cert.get('subjectAltName', ())
|
|
|
f35787 |
for key, value in san:
|
|
|
f35787 |
if key == 'DNS':
|
|
|
f35787 |
- if _dnsname_match(value, hostname):
|
|
|
f35787 |
+ if host_ip is None and _dnsname_match(value, hostname):
|
|
|
f35787 |
+ return
|
|
|
f35787 |
+ dnsnames.append(value)
|
|
|
f35787 |
+ elif key == 'IP Address':
|
|
|
f35787 |
+ if host_ip is not None and _ipaddress_match(value, host_ip):
|
|
|
f35787 |
return
|
|
|
f35787 |
dnsnames.append(value)
|
|
|
f35787 |
if not dnsnames:
|