Permalink
Browse files

Fix subjectAltName support.

The SNI/subjectAltName tests have a one big problem: They access the
great big, bad Internet, because I am not sure how to create the
correct certificates for this.

Also, this only works in all cases if pyasn1 and ndg-httpsclient are
installed (parsing DER-encoded binary data extracted from X.509
extensions is not my idea of fun.)
  • Loading branch information...
1 parent 953b5a4 commit df1d95016f6c423b9c1425aa20614f67c7d1621e @kirkeby committed Feb 26, 2013
Showing with 87 additions and 7 deletions.
  1. +41 −0 test/test_sni.py
  2. +46 −7 urllib3/util.py
View
@@ -0,0 +1,41 @@
+from __future__ import unicode_literals
+
+import os
+from ssl import PROTOCOL_TLSv1, CERT_REQUIRED
+import sys
+import urllib3
+from urllib3.util import HAS_SNI
+
+CA_BUNDLE = '/etc/ssl/certs/ca-certificates.crt'
+
+hosts = ['alice', 'bob', 'carol', 'dave', 'mallory', 'www']
+
+
+def test_sni_really_well():
+ if not HAS_SNI:
+ from nose.plugins.skip import SkipTest
+ raise SkipTest('SNI not supported')
+
+ if not 'URLLIB3_EXTERNAL_TESTS' in os.environ:
+ from nose.plugins.skip import SkipTest
+ raise SkipTest('External tests not wanted')
+
+ yield assert_for_host, 'sni.velox.ch'
+ for host in hosts:
+ hostname = host + '.sni.velox.ch'
+ yield assert_for_host, hostname
+
+
+def assert_for_host(hostname):
+ ### FIXME - test this without connecting to the big, bad Internet.
+ pool = urllib3.HTTPSConnectionPool(hostname,
+ strict=True,
+ cert_reqs=CERT_REQUIRED,
+ ca_certs=CA_BUNDLE,
+ ssl_version=PROTOCOL_TLSv1)
+
+ r = pool.request('GET', '/')
+ content = r.data.decode('utf-8', 'replace')
+ assert 'Great!' in content, \
+ 'Did not get "Great!" from https://%s/: %s' % (hostname, content)
+ assert hostname in content
View
@@ -7,6 +7,7 @@
from base64 import b64encode
from collections import namedtuple
+import os
from socket import error as SocketError
try:
@@ -30,10 +31,16 @@
pass
try:
- OpenSSL = None
- import OpenSSL.SSL
- from socket import _fileobject
- HAS_SNI = True
+ OpenSSL = ServerSSLCertVerification = None
+ if os.environ.get('URLLIB3_USE_PYOPENSSL') == 'true':
+ import OpenSSL.SSL
+ from ndg.httpsclient.ssl_peer_verification import ServerSSLCertVerification, SUBJ_ALT_NAME_SUPPORT
+ from ndg.httpsclient.subj_alt_name import SubjectAltName
+ from pyasn1.codec.der import decoder as der_decoder
+ from socket import _fileobject
+ # SNI only *really* works if we can read the subjectAltName of
+ # certificates, so.
+ HAS_SNI = SUBJ_ALT_NAME_SUPPORT
except ImportError:
pass
@@ -338,6 +345,37 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
return context.wrap_socket(sock)
elif OpenSSL is not None: # Use PyOpenSSL if installed
+ ### FIXME - This is a slightly bug-fixed version of same from
+ ### ndg-httpsclient.
+ def get_subj_alt_name(peer_cert):
+ # Search through extensions
+ dns_name = []
+ if not SUBJ_ALT_NAME_SUPPORT:
+ return dns_name
+
+ general_names = SubjectAltName()
+ for i in range(peer_cert.get_extension_count()):
+ ext = peer_cert.get_extension(i)
+ ext_name = ext.get_short_name()
+ if ext_name != 'subjectAltName':
+ continue
+
+ # PyOpenSSL returns extension data in ASN.1 encoded form
+ ext_dat = ext.get_data()
+ decoded_dat = der_decoder.decode(ext_dat,
+ asn1Spec=general_names)
+
+ for name in decoded_dat:
+ if not isinstance(name, SubjectAltName):
+ continue
+ for entry in range(len(name)):
+ component = name.getComponentByPosition(entry)
+ if component.getName() != 'dNSName':
+ continue
+ dns_name.append(str(component.getComponent()))
+
+ return dns_name
+
class wrapped_socket(object):
'''API-compatibility wrapper for Python OpenSSL's Connection-class.'''
@@ -359,10 +397,13 @@ def getpeercert(self):
if not x509:
raise SSLError('')
return {
- ### FIXME - need subjectAltName
'subject': (
(('commonName', x509.get_subject().CN),),
),
+ 'subjectAltName': [
+ ('DNS', value)
+ for value in get_subj_alt_name(x509)
+ ]
}
_openssl_versions = {
@@ -378,8 +419,6 @@ def getpeercert(self):
}
def _verify_callback(cnx, x509, err_no, err_depth, return_code):
- # FIXME - log this
- #print `x509, err_no, err_depth, return_code`
return err_no == 0
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,

0 comments on commit df1d950

Please sign in to comment.