summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am12
-rwxr-xr-xgencert.in71
-rw-r--r--test/README.sni30
-rwxr-xr-xtest/createinstance.sh74
-rwxr-xr-xtest/printenv.pl11
-rwxr-xr-xtest/setup.sh20
-rw-r--r--test/sni.tmpl28
-rw-r--r--test/suite1.tmpl178
-rw-r--r--test/test.py39
-rw-r--r--test/test_config.py41
-rw-r--r--test/test_request.py35
-rw-r--r--test/testsni.py101
12 files changed, 510 insertions, 130 deletions
diff --git a/Makefile.am b/Makefile.am
index 4978ff3..ca89989 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -103,12 +103,20 @@ check:
cd test; \
rm -rf work; \
nosetests -v test_cipher.py; \
- ./setup.sh; \
+ ./setup.sh -s 1; \
nosetests -v test.py; \
sleep 5; \
rm -rf work; \
- ./setup.sh sql:; \
+ ./setup.sh sql: -s 1; \
DBPREFIX=sql: nosetests -v test.py; \
cd ..
+checksni:
+ cd test; \
+ rm -rf work; \
+ ./setup.sh -s 25; \
+ nosetests -v testsni.py; \
+ cd ..
+
+
.PHONY: all test clean
diff --git a/gencert.in b/gencert.in
index ffccaab..1b76e1f 100755
--- a/gencert.in
+++ b/gencert.in
@@ -61,7 +61,7 @@ ALPHA_CERTDN="E=alpha@${FQDN}, CN=Frank Alpha, UID=alpha, OU=People, O=example.c
BETA_CERTDN="E=beta@${FQDN}, CN=Anna Beta, UID=beta, OU=People, O=example.com, C=US"
# size of the keys
-KEYSIZE=1024
+KEYSIZE=2048
# validity of the certs in months
VALIDITY=48
@@ -84,9 +84,65 @@ then
fi
DBDIR=$1
+shift
+
+if [ $# > 0 ]; then
+ SNI=$1
+else
+ SNI=0
+fi
echo "httptest" > $DEST/pw.txt
+function generate_server_sni_cert {
+ hostname=$1
+
+ local SERVER_DN="CN=${hostname}, O=SNI, O=example.com, C=US"
+ local NICKNAME="Server-Cert-${hostname}"
+
+ echo ""
+ echo "#####################################################################"
+ echo "Generating $NICKNAME server certificate request"
+ echo "#####################################################################"
+ (ps -elf; date; netstat -a) > $DEST/noise
+ $CERTUTIL -R -d $DBDIR \
+ -s "$SERVER_DN" \
+ -o $DEST/tmpcertreq \
+ -g $KEYSIZE \
+ -z $DEST/noise \
+ -f $DEST/pw.txt
+
+ echo ""
+ echo "#####################################################################"
+ echo "Generating $NICKNAME server certificate"
+ echo "#####################################################################"
+ let CERTSERIAL=CERTSERIAL+1
+ echo -e "2\n9\nn\n1\n9\nn\n" | \
+ $CERTUTIL -C -d $DBDIR \
+ -c cacert \
+ -i $DEST/tmpcertreq \
+ -o $DEST/tmpcert.der \
+ -m $CERTSERIAL \
+ -v $VALIDITY \
+ -f $DEST/pw.txt \
+ -1 \
+ -5 \
+ -8 $hostname
+
+ rm $DEST/tmpcertreq
+
+ echo ""
+ echo "#####################################################################"
+ echo "Importing $NICKNAME certificate into server cert DB"
+ echo "#####################################################################"
+ $CERTUTIL -A -d $DBDIR -n $NICKNAME \
+ -t u,u,u \
+ -i $DEST/tmpcert.der \
+ -f $DEST/pw.txt
+
+ rm $DEST/tmpcert.der
+}
+
echo ""
echo "#####################################################################"
echo "Generating new server certificate and key database. The password"
@@ -115,7 +171,6 @@ $CERTUTIL -S -d $DBDIR -n cacert \
-z $DEST/noise \
-2 \
-1 \
- -5
echo ""
echo "#####################################################################"
@@ -185,7 +240,8 @@ $CERTUTIL -C -d $DBDIR \
-v $VALIDITY \
-f $DEST/pw.txt \
-1 \
- -5
+ -5 \
+ -8 $FQDN
rm $DEST/tmpcertreq
@@ -200,6 +256,15 @@ $CERTUTIL -A -d $DBDIR -n Server-Cert \
rm $DEST/tmpcert.der
+if [ $SNI > 0 ]; then
+ SNI=`expr $SNI + 1`
+ count=1
+ while test $count -lt $SNI ; do
+ generate_server_sni_cert www$count.example.com
+ count=`expr $count + 1`
+ done
+fi
+
echo ""
echo "#####################################################################"
echo "Cleaning up"
diff --git a/test/README.sni b/test/README.sni
new file mode 100644
index 0000000..08ea95b
--- /dev/null
+++ b/test/README.sni
@@ -0,0 +1,30 @@
+The SNI tests are overly complicated because of the sad state of affairs
+of pyOpenSSL. If I use the SSL client in http.client I can override some
+methods and have access to the negotiated protocol and cipher but don't
+have access to SNI.
+
+Or I can use the SSL client in urllib3.contrib and have working SNI but
+no access to the negotiated protocol or cipher.
+
+So I split the baby. When running the existing test suite I use the original
+override methods so I can continue to do cipher and protocol negotiation
+testing. When running the SNI tests I use the urllib3 SSL client and do
+only SNI testing.
+
+To run the tests:
+
+You need to edit /etc/hosts and add:
+
+your_ip_address www[1-25].example.com
+
+E.g.
+
+192.168.0.1 www1.example.com
+192.168.0.1 www2.example.com
+...
+192.168.0.1 www25.example.com
+
+Do not create www26 as that is used as a negative test.
+
+setup.sh and gencert have been extended to generate a bunch of certs
+suitable for SNI testing.
diff --git a/test/createinstance.sh b/test/createinstance.sh
index 9cdba62..45a8f04 100755
--- a/test/createinstance.sh
+++ b/test/createinstance.sh
@@ -2,15 +2,45 @@
#
# Make a temporary Apache instance for testing.
-if [ " $#" -eq 0 ]; then
- echo "Usage: $0 /path/to/instance"
- exit 1
-fi
+function create_content_dirs {
+ local dir=$1
+
+ mkdir $1
+
+ # Create the content
+ mkdir $dir/rc4_cipher
+ mkdir $dir/openssl_rc4_cipher
+ mkdir $dir/openssl_aes_cipher
+ mkdir $dir/acl
+ mkdir $dir/protocolssl2
+ mkdir $dir/protocolssl3
+ mkdir $dir/protocoltls1
+ mkdir $dir/protocoltls11
+ mkdir $dir/protocoltls12
+
+ cat > $dir/index.html << EOF
+ <html>
+ Basic index page for $dir
+ </html
+EOF
+
+ cp $dir/index.html $dir/acl/aclS01.html
+ cp $dir/index.html $dir/acl/aclS02.html
+ cp $dir/index.html $dir/acl/aclS03.html
+ cp $dir/index.html $dir/secret-test.html
+ cp $dir/index.html $dir/protocolssl2/index.html
+ cp $dir/index.html $dir/protocolssl3/index.html
+ cp $dir/index.html $dir/protocoltls1/index.html
+ cp $dir/index.html $dir/protocoltls11/index.html
+ cp $dir/index.html $dir/protocoltls12/index.html
+}
+
target=$1
echo "Creating instance in $target"
mkdir -p $target
+# Make the default server root
cd $target
mkdir alias
mkdir bin
@@ -18,35 +48,18 @@ mkdir conf
mkdir conf.d
mkdir logs
mkdir run
-mkdir content
mkdir cgi-bin
mkdir lib
-# Create the content
-mkdir content/rc4_cipher
-mkdir content/openssl_rc4_cipher
-mkdir content/openssl_aes_cipher
-mkdir content/acl
-mkdir content/protocolssl2
-mkdir content/protocolssl3
-mkdir content/protocoltls1
-mkdir content/protocoltls11
-mkdir content/protocoltls12
-
-cat > content/index.html << EOF
-<html>
-Basic index page
-</html
-EOF
-cp content/index.html content/acl/aclS01.html
-cp content/index.html content/acl/aclS02.html
-cp content/index.html content/acl/aclS03.html
-cp content/index.html content/secret-test.html
-cp content/index.html content/protocolssl2/index.html
-cp content/index.html content/protocolssl3/index.html
-cp content/index.html content/protocoltls1/index.html
-cp content/index.html content/protocoltls11/index.html
-cp content/index.html content/protocoltls12/index.html
+touch conf.d/empty.conf
+
+# Create the content directories
+create_content_dirs content
+count=1
+while test $count -lt 26 ; do
+ create_content_dirs "sni${count}"
+ count=`expr $count + 1`
+done
ln -s /etc/httpd/modules modules
@@ -60,6 +73,7 @@ EOF
cat << EOF > start
#!/bin/sh
HTTPD=/usr/sbin/httpd
+#valgrind --leak-check=full --log-file=valgrind.out --trace-children=yes \$HTTPD -X -k start -d . -f ./conf/httpd.conf
\$HTTPD -k start -d . -f ./conf/httpd.conf
EOF
diff --git a/test/printenv.pl b/test/printenv.pl
new file mode 100755
index 0000000..a24e98b
--- /dev/null
+++ b/test/printenv.pl
@@ -0,0 +1,11 @@
+#!/usr/bin/perl
+
+binmode(STDOUT);
+binmode(STDIN);
+
+print "Content-Type: text/plain\r\n";
+print "\r\n";
+
+foreach $key (sort (keys (%ENV))) {
+ print "$key=$ENV{$key}\n";
+}
diff --git a/test/setup.sh b/test/setup.sh
index 487a5f5..33cf4f6 100755
--- a/test/setup.sh
+++ b/test/setup.sh
@@ -5,6 +5,22 @@ server_gid=$USER
server_port=8000
server_name=`hostname`
+while [[ $# > 1 ]]
+do
+key="$1"
+
+case $key in
+ -s|--sni)
+ SNI="$2"
+ shift # past argument
+ ;;
+ *)
+ # unknown option
+ ;;
+esac
+shift # past argument or value
+done
+
DBPREFIX=$1
test_root=$currentpath/work/httpd
@@ -20,12 +36,14 @@ if [ -e $test_root ]; then
fi
./createinstance.sh ${test_root}
+cp printenv.pl ${test_root}/cgi-bin
+chmod 755 ${test_root}/cgi-bin/printenv.pl
cp ../.libs/libmodnss.so ${test_root}/lib
cp ../nss_pcache ${test_root}/bin
echo "Generating a new certificate database..."
-bash ../gencert ${DBPREFIX}${test_root}/alias > /dev/null 2>&1
+bash ../gencert ${DBPREFIX}${test_root}/alias $SNI > /dev/null 2>&1
echo internal:httptest > ${test_root}/conf/password.conf
# Export the CA cert
diff --git a/test/sni.tmpl b/test/sni.tmpl
new file mode 100644
index 0000000..f58e9aa
--- /dev/null
+++ b/test/sni.tmpl
@@ -0,0 +1,28 @@
+<VirtualHost *:8000>
+
+ ServerName $SNINAME
+ DocumentRoot $SERVER_ROOT/sni$SNINUM
+
+ NSSEngine on
+ NSSFIPS off
+ NSSOCSP off
+ NSSRenegotiation on
+
+ NSSCipherSuite +aes_128_sha_256,+aes_256_sha_256,+rsa_aes_128_gcm_sha_256
+
+ NSSProtocol TLSv1.2
+
+ NSSNickname Server-Cert-$SNINAME
+
+ NSSVerifyClient none
+
+ # A bit redundant since the initial handshake should fail if no TLSv1.2
+ <Location "/protocoltls12">
+ NSSRequire %{SSL_PROTOCOL} eq "TLSv1.2"
+ </Location>
+
+ <Directory "$SERVER_ROOT/cgi-bin">
+ NSSOptions +ExportCertData +CompatEnvVars +StdEnvVars
+ </Directory>
+
+</VirtualHost>
diff --git a/test/suite1.tmpl b/test/suite1.tmpl
index 0f739a2..a069383 100644
--- a/test/suite1.tmpl
+++ b/test/suite1.tmpl
@@ -12,107 +12,139 @@ Listen 0.0.0.0:8001
LogLevel debug
-<VirtualHost $SERVER_NAME:$SERVER_PORT>
+<VirtualHost *:$SERVER_PORT>
-NSSEngine on
-NSSFIPS off
-NSSOCSP off
-NSSRenegotiation on
+ ServerName $SERVER_NAME
+ DocumentRoot $SERVER_ROOT/content
-NSSCipherSuite +rsa_rc4_128_md5,+rsa_3des_sha,+rsa_des_sha,+rsa_rc4_40_md5,+rsa_rc2_40_md5,+rsa_null_md5,+rsa_des_56_sha,+rsa_rc4_56_sha,+rsa_aes_128_sha,+rsa_aes_256_sha
+ NSSSNI $SNI
+ NSSEngine on
+ NSSFIPS off
+ NSSOCSP off
+ NSSRenegotiation on
-NSSProtocol SSLv3,TLSv1.0
+ NSSCipherSuite +rsa_rc4_128_md5,+rsa_3des_sha,+rsa_des_sha,+rsa_aes_128_sha,+rsa_aes_256_sha
-NSSNickname Server-Cert
+ NSSProtocol SSLv3,TLSv1.0
-NSSCertificateDatabase $DBPREFIX$SERVER_ROOT/alias
+ NSSNickname Server-Cert
-NSSVerifyClient none
+ NSSCertificateDatabase $DBPREFIX$SERVER_ROOT/alias
-NSSUserName SSL_CLIENT_S_DN_UID
+ NSSVerifyClient none
-<Location "/rc4_cipher">
- NSSCipherSuite +rsa_rc4_128_md5
-</Location>
+ NSSUserName SSL_CLIENT_S_DN_UID
-<Location "/openssl_rc4_cipher">
- NSSCipherSuite RC4-SHA
-</Location>
+ <Location "/rc4_cipher">
+ NSSCipherSuite +rsa_rc4_128_md5
+ </Location>
-<Location "/openssl_aes_cipher">
- # In openssl equivalent of AES:-ECDH:-ADH:-PSK:-DH
- # In NSS equivalent of AES:-ECDH
- NSSCipherSuite AES+RSA
-</Location>
+ <Location "/openssl_rc4_cipher">
+ NSSCipherSuite RC4-SHA
+ </Location>
-<Location "/acl/aclS01.html">
- NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData
- NSSVerifyClient require
-</Location>
+ <Location "/openssl_aes_cipher">
+ # In openssl equivalent of AES:-ECDH:-ADH:-PSK:-DH
+ # In NSS equivalent of AES:-ECDH
+ NSSCipherSuite AES+RSA
+ </Location>
-<Location "/acl/aclS02.html">
- NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData
- NSSVerifyClient require
- NSSRequire ( %{SSL_CLIENT_S_DN_UID} eq "alpha" \
- or %{SSL_CLIENT_S_DN_UID} eq "gamma" ) \
- and %{SSL_CLIENT_S_DN_O} eq "example.com" \
- and %{SSL_CLIENT_S_DN_OU} eq "People"
-</Location>
+ <Location "/acl/aclS01.html">
+ NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData
+ NSSVerifyClient require
+ </Location>
-<Location "/acl/aclS03.html">
- NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData +FakeBasicAuth
- NSSVerifyClient require
- AuthType Basic
- AuthName Cert
- AuthUserFile conf/htpasswd
- Require valid-user
-</Location>
+ <Location "/acl/aclS02.html">
+ NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData
+ NSSVerifyClient require
+ NSSRequire ( %{SSL_CLIENT_S_DN_UID} eq "alpha" \
+ or %{SSL_CLIENT_S_DN_UID} eq "gamma" ) \
+ and %{SSL_CLIENT_S_DN_O} eq "example.com" \
+ and %{SSL_CLIENT_S_DN_OU} eq "People"
+ </Location>
-<Location "/secret-test.html">
- NSSRequire %{SSL_CIPHER_USEKEYSIZE} > 40
-</Location>
+ <Location "/acl/aclS03.html">
+ NSSOptions +StdEnvVars +CompatEnvVars +ExportCertData +FakeBasicAuth
+ NSSVerifyClient require
+ AuthType Basic
+ AuthName Cert
+ AuthUserFile conf/htpasswd
+ Require valid-user
+ </Location>
-<Location "/secret-test-impossible.html">
- NSSRequire %{SSL_CIPHER_USEKEYSIZE} > 4000
-</Location>
+ <Location "/secret-test.html">
+ NSSRequire %{SSL_CIPHER_USEKEYSIZE} > 40
+ </Location>
-<Location "/protocolssl3">
- NSSRequire %{SSL_PROTOCOL} eq "SSLv3"
-</Location>
+ <Location "/secret-test-impossible.html">
+ NSSRequire %{SSL_CIPHER_USEKEYSIZE} > 4000
+ </Location>
-<Location "/protocoltls1">
- NSSRequire %{SSL_PROTOCOL} eq "TLSv1"
-</Location>
+ <Location "/protocolssl3">
+ NSSRequire %{SSL_PROTOCOL} eq "SSLv3"
+ </Location>
-<Location "/protocoltls11">
- NSSRequire %{SSL_PROTOCOL} eq "TLSv1.1"
-</Location>
+ <Location "/protocoltls1">
+ NSSRequire %{SSL_PROTOCOL} eq "TLSv1"
+ </Location>
+
+ <Location "/protocoltls11">
+ NSSRequire %{SSL_PROTOCOL} eq "TLSv1.1"
+ </Location>
+
+ <Location "/protocoltls12">
+ NSSRequire %{SSL_PROTOCOL} eq "TLSv1.2"
+ </Location>
+
+ <Directory "$SERVER_ROOT/cgi-bin">
+ NSSOptions +ExportCertData +CompatEnvVars +StdEnvVars
+ </Directory>
-<Location "/protocoltls12">
- NSSRequire %{SSL_PROTOCOL} eq "TLSv1.2"
-</Location>
</VirtualHost>
#
# For testing protocol handling
#
-<VirtualHost $SERVER_NAME:8001>
+<VirtualHost *:8001>
+
+ ServerName $SERVER_NAME
+ DocumentRoot $SERVER_ROOT/content
-NSSEngine on
-NSSFIPS off
-NSSOCSP off
-NSSRenegotiation on
+ NSSEngine on
+ NSSFIPS off
+ NSSOCSP off
+ NSSRenegotiation on
-NSSCipherSuite +rsa_rc4_128_md5,+rsa_3des_sha,+rsa_des_sha,+rsa_rc4_40_md5,+rsa_rc2_40_md5,+rsa_null_md5,+rsa_des_56_sha,+rsa_rc4_56_sha,+rsa_aes_128_sha,+rsa_aes_256_sha
+ NSSCipherSuite +aes_128_sha_256,+aes_256_sha_256,+rsa_aes_128_gcm_sha_256
-NSSProtocol TLSv1.2
+ NSSProtocol TLSv1.2
-NSSNickname Server-Cert
+ NSSNickname Server-Cert
-NSSVerifyClient none
+ NSSVerifyClient none
+
+ # A bit redundant since the initial handshake should fail if no TLSv1.2
+ <Location "/protocoltls12">
+ NSSRequire %{SSL_PROTOCOL} eq "TLSv1.2"
+ </Location>
+
+ <Directory "$SERVER_ROOT/cgi-bin">
+ NSSOptions +ExportCertData +CompatEnvVars +StdEnvVars
+ </Directory>
-# A bit redundant since the initial handshake should fail if no TLSv1.2
-<Location "/protocoltls12">
- NSSRequire %{SSL_PROTOCOL} eq "TLSv1.2"
-</Location>
</VirtualHost>
+
+#
+# SNI testing. Requires that you add an entry like this to /etc/hosts:
+#
+# <your_IP> www1.example.com
+#
+# 25 of these are needed
+#
+# Test with something like:
+# curl --cacert alias/ca.pem -v https://www1.example.com:8000/index.html
+#
+# Output should be something like: Basic index page for sni1
+#
+
+include conf.d/*
diff --git a/test/test.py b/test/test.py
index 23e093c..721d210 100644
--- a/test/test.py
+++ b/test/test.py
@@ -4,10 +4,31 @@ import ssl
import requests.exceptions
import os
+try:
+ # python3.2+
+ from ssl import CertificateError
+except ImportError:
+ try:
+ # Older python where the backport from pypi is installed
+ from backports.ssl_match_hostname import CertificateError
+ except ImportError:
+ # Other older python we use the urllib3 bundled copy
+ from urllib3.packages.ssl_match_hostname import CertificateError
+
class test_suite1(Declarative):
@classmethod
def setUpClass(cls):
- write_template_file('suite1.tmpl', 'work/httpd/conf/test.conf', {'DBPREFIX': os.environ.get('DBPREFIX', '')})
+ write_template_file('suite1.tmpl', 'work/httpd/conf/test.conf',
+ {'DBPREFIX': os.environ.get('DBPREFIX', ''),
+ 'SNI': 'off'}
+ )
+ # Generate a single VH to do negative SNI testing
+ write_template_file('sni.tmpl', 'work/httpd/conf.d/sni1.conf',
+ {'DBPREFIX': os.environ.get('DBPREFIX', ''),
+ 'SNINAME': 'www1.example.com',
+ 'SNINUM': 1,
+ }
+ )
restart_apache()
@classmethod
@@ -32,6 +53,7 @@ class test_suite1(Declarative):
desc='SSL connection, fail to verify',
request=('/', {'verify': True}),
expected=requests.exceptions.SSLError(),
+ expected_str='certificate verify failed',
),
dict(
@@ -56,10 +78,10 @@ class test_suite1(Declarative):
),
dict(
- desc='client-side RC4 cipher check',
- request=('/', {'ciphers': 'RC4-MD5'}),
+ desc='client-side cipher check',
+ request=('/', {'ciphers': 'AES256-SHA'}),
expected=200,
- cipher='RC4-MD5',
+ cipher='AES256-SHA',
),
dict(
@@ -237,4 +259,13 @@ class test_suite1(Declarative):
expected=requests.exceptions.SSLError(),
),
+ dict(
+ desc='Make non-SNI request',
+ request=('/index.html',
+ {'host': 'www1.example.com', 'port': 8000}
+ ),
+ expected=requests.exceptions.SSLError(),
+ expected_str='doesn\'t match',
+ ),
+
]
diff --git a/test/test_config.py b/test/test_config.py
index 24a2f2a..31c59a1 100644
--- a/test/test_config.py
+++ b/test/test_config.py
@@ -136,21 +136,31 @@ class Declarative(object):
session.mount('https://', test_request.MyAdapter())
verify = dict(verify = options)
port = options.get('port', DEF_PORT)
- request = session.get('https://%s:%d%s' % (FQDN, port, uri), **verify)
+ host = options.get('host', FQDN)
+ request = session.get('https://%s:%d%s' % (host, port, uri), **verify)
return request
- def check(self, nice, desc, request, expected, cipher=None, protocol=None):
+ def check(self, nice, desc, request, expected, cipher=None, protocol=None,
+ expected_str=None, content=None):
# TODO: need way to set auth, etc.
(uri, options) = request
if not 'verify' in options:
options['verify'] = 'work/httpd/alias/ca.pem'
if isinstance(expected, Exception):
- self.check_exception(nice, uri, options, expected)
+ self.check_exception(nice, uri, options, expected, expected_str)
else:
- self.check_result(nice, uri, options, expected, cipher, protocol)
+ self.check_result(nice, uri, options, expected, cipher, protocol,
+ content)
- def check_exception(self, nice, uri, options, expected):
+ def check_exception(self, nice, uri, options, expected, expected_str):
+ """
+ With some requests we expect an exception to be raised. See if
+ is the one we expect. There is at least one case where depending
+ on the distro either a CertificateError or an SSLError is
+ thrown but the message of the exception is the same, so compare
+ that.
+ """
klass = expected.__class__
name = klass.__name__
try:
@@ -162,15 +172,24 @@ class Declarative(object):
EXPECTED % (uri, name, options, output)
)
if not isinstance(e, klass):
- raise AssertionError(
- UNEXPECTED % (uri, name, options, e.__class__.__name__, e)
- )
-
+ if expected_str not in str(e):
+ raise AssertionError(
+ UNEXPECTED % (uri, name, options, e.__class__.__name__, e)
+ )
- def check_result(self, nice, uri, options, expected, cipher=None, protocol=None):
+ def check_result(self, nice, uri, options, expected, cipher=None,
+ protocol=None, content=None):
name = expected.__class__.__name__
request = self.make_request(uri, options)
+ has_sni = options.get('sni', False)
+
+ if content and not content in request.content:
+ raise AssertionError(
+ 'Expected %s not in %s' % (content, request.content)
+ )
if cipher:
+ if has_sni:
+ raise AssertionError('Cannot do cipher tests in SNI')
client_cipher = request.raw._pool._get_conn().client_cipher
if cipher != client_cipher[0]:
raise AssertionError(
@@ -178,6 +197,8 @@ class Declarative(object):
)
if protocol:
client_cipher = request.raw._pool._get_conn().client_cipher
+ if has_sni:
+ raise AssertionError('Cannot do protocol tests in SNI')
if protocol != client_cipher[1]:
raise AssertionError(
'Expected protocol %s, got %s' % (protocol, client_cipher[1])
diff --git a/test/test_request.py b/test/test_request.py
index bac2a2d..5d2a525 100644
--- a/test/test_request.py
+++ b/test/test_request.py
@@ -4,9 +4,12 @@
import socket
import requests
import urlparse
-from urllib3.util import get_host
-from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
import logging
+import socket
+from requests.packages.urllib3.util import get_host
+from requests.packages.urllib3.util.timeout import Timeout
+from requests.packages.urllib3.contrib import pyopenssl
+from requests.packages.urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool, VerifiedHTTPSConnection
# Don't bend over backwards for ssl support, assume it is there.
import ssl
@@ -30,6 +33,8 @@ except ImportError:
# Other older python we use the urllib3 bundled copy
from urllib3.packages.ssl_match_hostname import match_hostname, CertificateError
+SAVE_DEFAULT_SSL_CIPHER_LIST = pyopenssl.DEFAULT_SSL_CIPHER_LIST
+
log = logging.getLogger(__name__)
@@ -61,7 +66,7 @@ def connection_from_url(url, **kw):
class MyHTTPSConnectionPool(HTTPSConnectionPool):
def __init__(self, host, port=None,
- strict=False, timeout=None, maxsize=1,
+ strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1,
block=False, headers=None,
key_file=None, cert_file=None,
cert_reqs='CERT_REQUIRED', ca_certs='/etc/ssl/certs/ca-certificates.crt', ssl_version=ssl.PROTOCOL_SSLv23, ciphers=None):
@@ -75,6 +80,8 @@ class MyHTTPSConnectionPool(HTTPSConnectionPool):
self.ca_certs = ca_certs
self.ssl_version = ssl_version
self.ciphers = ciphers
+ self.assert_hostname = None
+ self.assert_fingerprint = None
def _new_conn(self):
"""
@@ -92,13 +99,14 @@ class MyHTTPSConnectionPool(HTTPSConnectionPool):
# return HTTPSConnection(host=self.host, port=self.port)
connection = MyVerifiedHTTPSConnection(host=self.host, port=self.port)
+ connection.sni = self.sni
connection.set_cert(key_file=self.key_file, cert_file=self.cert_file,
cert_reqs=self.cert_reqs, ca_certs=self.ca_certs)
connection.set_ssl_version(self.ssl_version)
connection.set_ciphers(self.ciphers)
return connection
-class MyVerifiedHTTPSConnection(HTTPSConnection):
+class MyVerifiedHTTPSConnection(VerifiedHTTPSConnection):
"""
Based on httplib.HTTPSConnection but wraps the socket with
SSL certification.
@@ -106,6 +114,10 @@ class MyVerifiedHTTPSConnection(HTTPSConnection):
cert_reqs = None
ca_certs = None
client_cipher = None
+ is_verified = True # squelch warning
+ sni = False
+ assert_hostname = None
+ assert_fingerprint = None
def set_cert(self, key_file=None, cert_file=None,
cert_reqs='CERT_NONE', ca_certs=None):
@@ -127,6 +139,13 @@ class MyVerifiedHTTPSConnection(HTTPSConnection):
self.ciphers = ciphers
def connect(self):
+ if self.sni:
+ if self.ciphers:
+ pyopenssl.DEFAULT_SSL_CIPHER_LIST = self.ciphers
+ else:
+ pyopenssl.DEFAULT_SSL_CIPHER_LIST = SAVE_DEFAULT_SSL_CIPHER_LIST
+ return super(MyVerifiedHTTPSConnection, self).connect()
+
# Add certificate verification
sock = socket.create_connection((self.host, self.port), self.timeout)
@@ -141,9 +160,10 @@ class MyVerifiedHTTPSConnection(HTTPSConnection):
match_hostname(self.sock.getpeercert(), self.host)
def close(self):
- if self.sock:
- self.client_cipher = self.sock.cipher()
- HTTPSConnection.close(self)
+ if not self.sni:
+ if self.sock:
+ self.client_cipher = self.sock.cipher()
+ super(MyVerifiedHTTPSConnection, self).close()
class MyAdapter(requests.adapters.HTTPAdapter):
@@ -171,6 +191,7 @@ class MyAdapter(requests.adapters.HTTPAdapter):
conn.cert_file = verify['cert_file']
if 'key_file' in verify:
conn.key_file = verify['key_file']
+ conn.sni = verify.get('sni', False)
else: # huh? Do nothing
pass
diff --git a/test/testsni.py b/test/testsni.py
new file mode 100644
index 0000000..9808f91
--- /dev/null
+++ b/test/testsni.py
@@ -0,0 +1,101 @@
+from test_config import Declarative, write_template_file, restart_apache
+from test_config import stop_apache
+import ssl
+import requests.exceptions
+import os
+
+class test_suite1(Declarative):
+ @classmethod
+ def setUpClass(cls):
+ write_template_file('suite1.tmpl', 'work/httpd/conf/test.conf',
+ {'DBPREFIX': os.environ.get('DBPREFIX', ''),
+ 'SNI': 'on'}
+ )
+ for i in range(1,26):
+ write_template_file('sni.tmpl', 'work/httpd/conf.d/sni%d.conf' % i,
+ {'DBPREFIX': os.environ.get('DBPREFIX', ''),
+ 'SNINAME': 'www%d.example.com' % i,
+ 'SNINUM': i,
+ }
+ )
+ restart_apache()
+
+ @classmethod
+ def tearDownClass(cls):
+ stop_apache()
+
+ tests = [
+
+ dict(
+ desc='Get this host',
+ request=('/', {'sni': True}),
+ expected=200,
+ content='content',
+ ),
+
+ dict(
+ desc='Get www1.example.com',
+ request=('/', {'host': 'www1.example.com', 'sni': True}),
+ expected=200,
+ content='sni1',
+ ),
+
+ dict(
+ desc='Get www2.example.com',
+ request=('/', {'host': 'www2.example.com', 'sni': True}),
+ expected=200,
+ content='sni2',
+ ),
+
+ dict(
+ desc='Get www4.example.com',
+ request=('/', {'host': 'www4.example.com', 'sni': True}),
+ expected=200,
+ content='sni4',
+ ),
+
+ dict(
+ desc='Get www6.example.com',
+ request=('/', {'host': 'www6.example.com', 'sni': True}),
+ expected=200,
+ content='sni6',
+ ),
+
+ dict(
+ desc='Get www1.example.com again',
+ request=('/', {'host': 'www1.example.com', 'sni': True}),
+ expected=200,
+ content='sni1',
+ ),
+
+ dict(
+ desc='Get non-existant page on www8.example.com',
+ request=('/notfound', {'host': 'www8.example.com', 'sni': True}),
+ expected=404,
+ ),
+
+ dict(
+ desc='Client auth to www10.example.com, valid certificate',
+ request=('/acl/aclS01.html', {
+ 'host': 'www10.example.com', 'sni': True,
+ 'key_file': 'work/httpd/alpha.key',
+ 'cert_file': 'work/httpd/alpha.crt',}
+ ),
+ expected=200,
+ content='sni10',
+ ),
+
+ dict(
+ desc='Get www25.example.com',
+ request=('/', {'host': 'www25.example.com', 'sni': True}),
+ expected=200,
+ content='sni25',
+ ),
+
+ dict(
+ desc='Non-existant www26.example.com',
+ request=('/', {'host': 'www26.example.com', 'sni': True}),
+ expected=requests.exceptions.ConnectionError(),
+ ),
+
+ ]