Make pyOpenSSL skip DNS names which can't be idna-encoded
MODIFY
CHANGES.rst
CHANGES.rst
@@ -4,6 +4,8 @@ Changes
dev (master)
------------
+* Skip DNS names that can't be idna-decoded when using pyOpenSSL (Issue #1405).
+
* ... [Short description of non-trivial change.] (Issue #)

MODIFY
urllib3/contrib/pyopenssl.py
urllib3/contrib/pyopenssl.py
@@ -163,6 +163,9 @@ def _dnsname_to_stdlib(name):
from ASCII bytes. We need to idna-encode that string to get it back, and
then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib
uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8).
+
+ If the name cannot be idna-encoded then we return None signalling that
+ the name given should be skipped.
"""
def idna_encode(name):
"""
@@ -172,14 +175,19 @@ def _dnsname_to_stdlib(name):
"""
import idna
+ try:
for prefix in [u'*.', u'.']:
if name.startswith(prefix):
name = name[len(prefix):]
return prefix.encode('ascii') + idna.encode(name)
return idna.encode(name)
+ except idna.core.IDNAError:
+ return None
name = idna_encode(name)
- if sys.version_info >= (3, 0):
+ if name is None:
+ return None
+ elif sys.version_info >= (3, 0):
name = name.decode('utf-8')
return name
@@ -223,9 +231,10 @@ def get_subj_alt_name(peer_cert):
# Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8
# decoded. This is pretty frustrating, but that's what the standard library
# does with certificates, and so we need to attempt to do the same.
+ # We also want to skip over names which cannot be idna encoded.
names = [
- ('DNS', _dnsname_to_stdlib(name))
- for name in ext.get_values_for_type(x509.DNSName)
+ ('DNS', name) for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName))
+ if name is not None
]
names.extend(
('IP Address', str(name))

Make pyOpenSSL skip DNS names which can't be idna-encoded (#1406)