summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCory Benfield <lukasaoz@gmail.com>2017-08-16 20:11:56 +0100
committerGitHub <noreply@github.com>2017-08-16 20:11:56 +0100
commita72275c83a05df64156ff1ab89044ac8e649c80f (patch)
treeddcf99ba16de4cc550d5a6aebdb6bb0960ed60e8
parent769898e3604ee0ad05e5b25fd950a615b60543df (diff)
parent890a2114a28b6b3d7ea008d3ad443119355ef1a4 (diff)
downloadurllib3-a72275c83a05df64156ff1ab89044ac8e649c80f.tar.gz
Merge pull request #1246 from rtdean/theonewolf-1060-pyopenssl-load-chain-fix
PyOpenSSL client certificate load chain fix
-rw-r--r--CHANGES.rst3
-rw-r--r--CONTRIBUTORS.txt3
-rw-r--r--dummyserver/certs/client_intermediate.key15
-rw-r--r--dummyserver/certs/client_intermediate.pem37
-rw-r--r--dummyserver/certs/client_no_intermediate.pem19
-rw-r--r--dummyserver/certs/intermediate.key15
-rw-r--r--dummyserver/certs/intermediate.pem18
-rw-r--r--dummyserver/handlers.py9
-rwxr-xr-xdummyserver/server.py15
-rw-r--r--test/with_dummyserver/test_https.py31
-rw-r--r--urllib3/contrib/pyopenssl.py2
11 files changed, 166 insertions, 1 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 4d604a65..d9abc576 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,6 +9,9 @@ dev (master)
* Put the connection back in the pool when calling stream() or read_chunked() on
a chunked HEAD response. (Issue #1234)
+* Fixed pyOpenSSL-specific ssl client authentication issue when clients
+ attempted to auth via certificate + chain (Issue #1060)
+
* ... [Short description of non-trivial change.] (Issue #)
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 6f8c2543..05605ce9 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -233,5 +233,8 @@ In chronological order:
* Erik Rose <erik@mozilla.com>
* Bugfix to pyopenssl vendoring
+* Wolfgang Richter <wolfgang.richter@gmail.com>
+ * Bugfix related to loading full certificate chains with PyOpenSSL backend.
+
* [Your name or handle] <[email or website]>
* [Brief summary of your changes]
diff --git a/dummyserver/certs/client_intermediate.key b/dummyserver/certs/client_intermediate.key
new file mode 100644
index 00000000..79c2b244
--- /dev/null
+++ b/dummyserver/certs/client_intermediate.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQCadkbPLXPfA1bNjgL9F6+rVLs3uZdbXemHf1oKkT4q9uruZTQC
+TDFvvWHq32r6G8KV7MASariSz+bIgpx1euZEOmwucd+ULs0HMdfqorRa3MuUtKuI
+zYiQvCsv788VoNKjs+NNMIexO6p6S9E36ce2trzeBCmpYmi0WofO0bSwnwIDAQAB
+AoGAXP/nxGfmgxj8k4j0rbQsNekvS+73fbB+ofGAoiovFylR7DWM6fE8Nr39DbB1
+NZ+vOhuwzaXp+aMpngJd97IGn5BPJ0QBEvNcypUxzh0xyRMm+a2xIZ+8TL+rJsas
+k+oH/AQ6IcVlZFM5IQl5kAe1aq7VLnsi8KvrvljmxhzC9tECQQDMLl4FliIbfDHs
+GipTcWpdEhqfiH5FJkwT9rIS7+naa+QnbIS6vbMWnQp8OWumWcqpLJ7Zi9nOhrLO
+7a4CxuiHAkEAwampqF4ipKSYxI3/2BhfIN3pxXL5gNbPmU2nnNDhk8I3+rc+zLeg
+tEePDU6X59AhjKB6IANC0F2LQaxkHX61KQJAPR+IT/3Qug+k1jxC/XXPVItN4wI2
+YrcDQVqxlk+x3ww7Yb3vwgN18EgU0nlSC5uHurs71n4yNsxGDQJD/FrVUwJAbQf5
+RZpiBLHKdHbBwMbP3/AwKgL2J6xIyrWmlSogphCldZjvWVBUwMq85jAGY/OQv9yl
+hRpw5mCUA1BsORLaKQJAOQdFHzTrSRwoWemg9+5PDM0uVbLISnrKBDOUdUBouEbJ
+6qRUz1oUiBrIIIen6acaaJRS0aT+eWgm0gY7m7DmTg==
+-----END RSA PRIVATE KEY-----
diff --git a/dummyserver/certs/client_intermediate.pem b/dummyserver/certs/client_intermediate.pem
new file mode 100644
index 00000000..4bd85a98
--- /dev/null
+++ b/dummyserver/certs/client_intermediate.pem
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAmegAwIBAgImFhgDOYh0mJSEggRYaDQ2VjgRdyAwkXmAV2KGITVEhiJw
+UmBGKBgwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UEBhMCRkkxDjAMBgNVBAgMBWR1
+bW15MQ4wDAYDVQQKDAVkdW1teTEOMAwGA1UECwwFZHVtbXkxETAPBgNVBAMMCFNu
+YWtlT2lsMR8wHQYJKoZIhvcNAQkBFhBkdW1teUB0ZXN0LmxvY2FsMB4XDTE3MDUx
+MjE4MzQyNloXDTIxMTIxOTE4MzQyNlowdzELMAkGA1UEBhMCRkkxDjAMBgNVBAgM
+BWR1bW15MQ4wDAYDVQQKDAVkdW1teTEOMAwGA1UECwwFZHVtbXkxFzAVBgNVBAMM
+DlNuYWtlT2lsQ2xpZW50MR8wHQYJKoZIhvcNAQkBFhBkdW1teUB0ZXN0LmxvY2Fs
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCadkbPLXPfA1bNjgL9F6+rVLs3
+uZdbXemHf1oKkT4q9uruZTQCTDFvvWHq32r6G8KV7MASariSz+bIgpx1euZEOmwu
+cd+ULs0HMdfqorRa3MuUtKuIzYiQvCsv788VoNKjs+NNMIexO6p6S9E36ce2trze
+BCmpYmi0WofO0bSwnwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf
+Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUbe9reSw2
+C72JuGVpc+/L/O2hVjwwHwYDVR0jBBgwFoAUnltsnuh2mjtqqDWk2RNSwC7njHkw
+DQYJKoZIhvcNAQELBQADgYEADlJp3uMKxgS2hgCK+JZV4qsXGuZ/rcHgq5qlrfg0
+i76+wwZ6fs3WQe+zNgXbJnRviM0VScSUBM8IuclyovFWLvs0Z0piELtZ7KPwrDVf
+5S5ynJHnJSG+sj4N6v+tvtpGDb1S3ueLQm79MGXv9pmbaYBmUJ0YSEnrScWy90Bv
+Tno=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAkegAwIBAgImMUFZJlNYl5MjhGJkM4MnlQKIQZcWk5k3UQWCCXSURZIw
+eBZAYoYwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAkZJMQ4wDAYDVQQIDAVk
+dW1teTEOMAwGA1UEBwwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MQ4wDAYDVQQLDAVk
+dW1teTERMA8GA1UEAwwIU25ha2VPaWwxHzAdBgkqhkiG9w0BCQEWEGR1bW15QHRl
+c3QubG9jYWwwHhcNMTcwNTEyMTgyMDUyWhcNMjExMjE5MTgyMDUyWjBxMQswCQYD
+VQQGEwJGSTEOMAwGA1UECAwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MQ4wDAYDVQQL
+DAVkdW1teTERMA8GA1UEAwwIU25ha2VPaWwxHzAdBgkqhkiG9w0BCQEWEGR1bW15
+QHRlc3QubG9jYWwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMQE4WdDs9Tl
+Oop5/EfRVBnSDF/Wzwyu28IfDYOi5f50CaB5tzEgGjcjhaVHYg6rDTHk2v3/N82g
+7xQRWKhW+GxlddpMJjObiOOzhvH3Xam97xEf+rlnyl0cuhRbwcNH3GIm6hE5f/Qq
+YPstYBuP6SZlUJ0DQQag2n/9uALo6X+7AgMBAAGjUDBOMB0GA1UdDgQWBBSeW2ye
+6HaaO2qoNaTZE1LALueMeTAfBgNVHSMEGDAWgBQZd38jYmJCWUX7dZ3Hc3IEuzMK
+LTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAGnXyMzPPe5o4tYasY0K
+A9sgxg42rH1gAeDJXeG4QqLoVi9JKbOBXdJGN9ZWD9K4EASknwWsa0TWSv291jHN
+4+Uz8bHZ+4mH5HMpXZPsHorHR2te2XCZGMNE1V/1N0Q8qQk8CoxDSl5l5n67W9DY
+iTQB1g/ymK3/hnTohqkFj9xd
+-----END CERTIFICATE-----
diff --git a/dummyserver/certs/client_no_intermediate.pem b/dummyserver/certs/client_no_intermediate.pem
new file mode 100644
index 00000000..ab656c30
--- /dev/null
+++ b/dummyserver/certs/client_no_intermediate.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAmegAwIBAgImFhgDOYh0mJSEggRYaDQ2VjgRdyAwkXmAV2KGITVEhiJw
+UmBGKBgwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UEBhMCRkkxDjAMBgNVBAgMBWR1
+bW15MQ4wDAYDVQQKDAVkdW1teTEOMAwGA1UECwwFZHVtbXkxETAPBgNVBAMMCFNu
+YWtlT2lsMR8wHQYJKoZIhvcNAQkBFhBkdW1teUB0ZXN0LmxvY2FsMB4XDTE3MDUx
+MjE4MzQyNloXDTIxMTIxOTE4MzQyNlowdzELMAkGA1UEBhMCRkkxDjAMBgNVBAgM
+BWR1bW15MQ4wDAYDVQQKDAVkdW1teTEOMAwGA1UECwwFZHVtbXkxFzAVBgNVBAMM
+DlNuYWtlT2lsQ2xpZW50MR8wHQYJKoZIhvcNAQkBFhBkdW1teUB0ZXN0LmxvY2Fs
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCadkbPLXPfA1bNjgL9F6+rVLs3
+uZdbXemHf1oKkT4q9uruZTQCTDFvvWHq32r6G8KV7MASariSz+bIgpx1euZEOmwu
+cd+ULs0HMdfqorRa3MuUtKuIzYiQvCsv788VoNKjs+NNMIexO6p6S9E36ce2trze
+BCmpYmi0WofO0bSwnwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf
+Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUbe9reSw2
+C72JuGVpc+/L/O2hVjwwHwYDVR0jBBgwFoAUnltsnuh2mjtqqDWk2RNSwC7njHkw
+DQYJKoZIhvcNAQELBQADgYEADlJp3uMKxgS2hgCK+JZV4qsXGuZ/rcHgq5qlrfg0
+i76+wwZ6fs3WQe+zNgXbJnRviM0VScSUBM8IuclyovFWLvs0Z0piELtZ7KPwrDVf
+5S5ynJHnJSG+sj4N6v+tvtpGDb1S3ueLQm79MGXv9pmbaYBmUJ0YSEnrScWy90Bv
+Tno=
+-----END CERTIFICATE-----
diff --git a/dummyserver/certs/intermediate.key b/dummyserver/certs/intermediate.key
new file mode 100644
index 00000000..0b6cd739
--- /dev/null
+++ b/dummyserver/certs/intermediate.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDEBOFnQ7PU5TqKefxH0VQZ0gxf1s8MrtvCHw2DouX+dAmgebcx
+IBo3I4WlR2IOqw0x5Nr9/zfNoO8UEVioVvhsZXXaTCYzm4jjs4bx912pve8RH/q5
+Z8pdHLoUW8HDR9xiJuoROX/0KmD7LWAbj+kmZVCdA0EGoNp//bgC6Ol/uwIDAQAB
+AoGAO76dEQNioWYItMI/cYhM0N3jpaZsTxpQotciIFgbL7YgZQgUHOYC94FdL6YV
+LhFWoTl2wenzETqXBA/RbOWtK5wmpmu4Qy9Bq+r7FW296lyYOYyjuwz8AJD2kOTe
+A9VjTJ9YBGxcbyI3/sSfF3tyNSyQZoh2AEIhsbKEmJPByzkCQQDhLbpvjkMAK5hq
+P6TB4IwgK/dAF6fHdlDFCNfwhBvb1LiRe2OJMuPuAGlx+sMgiEiMntKoJrlE09NB
+SnWnlaeHAkEA3tlmR+6GsEt6Quv4KgBMlJdVH/AYCba5eX/Ru5kz4WJwLCqJ5sik
+V5wPt+lmgAsOLbivKXwuhb4p9WrBtOXLLQJANYOflhlyFN1HeKCtcCIESzUHqqS0
+i/OzWFA0uYU79a+FOZXgXt/ISWyxopPcwaOB0mGAYNPrHc9VmmOuuGgZiwJALFnR
+/FDhZ2auH3F9A0bp9syjeWa8Mfq2sRKaOB7Gb326219f8JlP88uwaSa/ao5ItRrD
+aZs4Ww+8pAYqJQlyxQJBANtJ14x0aOeFPY4MD7JdqRKxiQF4g9lL149yRGd+mRQC
+OEonvmOrib8hGE6ivElMR3azEWCC3BcoKUxdplkY+Xw=
+-----END RSA PRIVATE KEY-----
diff --git a/dummyserver/certs/intermediate.pem b/dummyserver/certs/intermediate.pem
new file mode 100644
index 00000000..1e789cd7
--- /dev/null
+++ b/dummyserver/certs/intermediate.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAkegAwIBAgImMUFZJlNYl5MjhGJkM4MnlQKIQZcWk5k3UQWCCXSURZIw
+eBZAYoYwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAkZJMQ4wDAYDVQQIDAVk
+dW1teTEOMAwGA1UEBwwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MQ4wDAYDVQQLDAVk
+dW1teTERMA8GA1UEAwwIU25ha2VPaWwxHzAdBgkqhkiG9w0BCQEWEGR1bW15QHRl
+c3QubG9jYWwwHhcNMTcwNTEyMTgyMDUyWhcNMjExMjE5MTgyMDUyWjBxMQswCQYD
+VQQGEwJGSTEOMAwGA1UECAwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MQ4wDAYDVQQL
+DAVkdW1teTERMA8GA1UEAwwIU25ha2VPaWwxHzAdBgkqhkiG9w0BCQEWEGR1bW15
+QHRlc3QubG9jYWwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMQE4WdDs9Tl
+Oop5/EfRVBnSDF/Wzwyu28IfDYOi5f50CaB5tzEgGjcjhaVHYg6rDTHk2v3/N82g
+7xQRWKhW+GxlddpMJjObiOOzhvH3Xam97xEf+rlnyl0cuhRbwcNH3GIm6hE5f/Qq
+YPstYBuP6SZlUJ0DQQag2n/9uALo6X+7AgMBAAGjUDBOMB0GA1UdDgQWBBSeW2ye
+6HaaO2qoNaTZE1LALueMeTAfBgNVHSMEGDAWgBQZd38jYmJCWUX7dZ3Hc3IEuzMK
+LTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAGnXyMzPPe5o4tYasY0K
+A9sgxg42rH1gAeDJXeG4QqLoVi9JKbOBXdJGN9ZWD9K4EASknwWsa0TWSv291jHN
+4+Uz8bHZ+4mH5HMpXZPsHorHR2te2XCZGMNE1V/1N0Q8qQk8CoxDSl5l5n67W9DY
+iTQB1g/ymK3/hnTohqkFj9xd
+-----END CERTIFICATE-----
diff --git a/dummyserver/handlers.py b/dummyserver/handlers.py
index b91fe721..e2974433 100644
--- a/dummyserver/handlers.py
+++ b/dummyserver/handlers.py
@@ -106,6 +106,15 @@ class TestingApp(RequestHandler):
"Render simple message"
return Response("Dummy server!")
+ def certificate(self, request):
+ """Return the requester's certificate."""
+ cert = request.get_ssl_certificate()
+ subject = dict()
+ if cert is not None:
+ subject = dict((k, v) for (k, v) in [y for z in cert['subject']
+ for y in z])
+ return Response(json.dumps(subject))
+
def source_address(self, request):
"""Return the requester's IP address."""
return Response(request.remote_ip)
diff --git a/dummyserver/server.py b/dummyserver/server.py
index 29247a6d..113324b3 100755
--- a/dummyserver/server.py
+++ b/dummyserver/server.py
@@ -14,6 +14,7 @@ import sys
import threading
import socket
import warnings
+import ssl
from datetime import datetime
from urllib3.exceptions import HTTPWarning
@@ -30,6 +31,20 @@ CERTS_PATH = os.path.join(os.path.dirname(__file__), 'certs')
DEFAULT_CERTS = {
'certfile': os.path.join(CERTS_PATH, 'server.crt'),
'keyfile': os.path.join(CERTS_PATH, 'server.key'),
+ 'cert_reqs': ssl.CERT_OPTIONAL,
+ 'ca_certs': os.path.join(CERTS_PATH, 'cacert.pem'),
+}
+DEFAULT_CLIENT_CERTS = {
+ 'certfile': os.path.join(CERTS_PATH, 'client_intermediate.pem'),
+ 'keyfile': os.path.join(CERTS_PATH, 'client_intermediate.key'),
+ 'subject': dict(countryName=u'FI', stateOrProvinceName=u'dummy',
+ organizationName=u'dummy', organizationalUnitName=u'dummy',
+ commonName=u'SnakeOilClient',
+ emailAddress=u'dummy@test.local'),
+}
+DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS = {
+ 'certfile': os.path.join(CERTS_PATH, 'client_no_intermediate.pem'),
+ 'keyfile': os.path.join(CERTS_PATH, 'client_intermediate.key'),
}
NO_SAN_CERTS = {
'certfile': os.path.join(CERTS_PATH, 'server.no_san.crt'),
diff --git a/test/with_dummyserver/test_https.py b/test/with_dummyserver/test_https.py
index ad91f37d..ba947ea9 100644
--- a/test/with_dummyserver/test_https.py
+++ b/test/with_dummyserver/test_https.py
@@ -1,4 +1,5 @@
import datetime
+import json
import logging
import ssl
import sys
@@ -12,6 +13,8 @@ from dummyserver.testcase import (
HTTPSDummyServerTestCase, IPV6HTTPSDummyServerTestCase
)
from dummyserver.server import (DEFAULT_CA, DEFAULT_CA_BAD, DEFAULT_CERTS,
+ DEFAULT_CLIENT_CERTS,
+ DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS,
NO_SAN_CERTS, NO_SAN_CA, DEFAULT_CA_DIR,
IPV6_ADDR_CERTS, IPV6_ADDR_CA, HAS_IPV6,
IP_SAN_CERTS)
@@ -67,6 +70,34 @@ class TestHTTPS(HTTPSDummyServerTestCase):
r = self._pool.request('GET', '/')
self.assertEqual(r.status, 200, r.data)
+ def test_client_intermediate(self):
+ client_cert, client_key, client_subject = (
+ DEFAULT_CLIENT_CERTS['certfile'],
+ DEFAULT_CLIENT_CERTS['keyfile'],
+ DEFAULT_CLIENT_CERTS['subject']
+ )
+ https_pool = HTTPSConnectionPool(self.host, self.port,
+ key_file=client_key,
+ cert_file=client_cert)
+ r = https_pool.request('GET', '/certificate')
+ self.assertDictEqual(json.loads(r.data.decode('utf-8')),
+ client_subject, r.data)
+
+ def test_client_no_intermediate(self):
+ client_cert, client_key = (
+ DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS['certfile'],
+ DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS['keyfile']
+ )
+ https_pool = HTTPSConnectionPool(self.host, self.port,
+ cert_file=client_cert,
+ key_file=client_key)
+ try:
+ https_pool.request('GET', '/certificate', retries=False)
+ except SSLError as e:
+ self.assertTrue('alert unknown ca' in str(e) or
+ 'invalid certificate chain' in str(e) or
+ 'unknown Cert Authority' in str(e))
+
def test_verified(self):
https_pool = HTTPSConnectionPool(self.host, self.port,
cert_reqs='CERT_REQUIRED',
diff --git a/urllib3/contrib/pyopenssl.py b/urllib3/contrib/pyopenssl.py
index f63b8401..2762bca4 100644
--- a/urllib3/contrib/pyopenssl.py
+++ b/urllib3/contrib/pyopenssl.py
@@ -418,7 +418,7 @@ class PyOpenSSLContext(object):
self._ctx.load_verify_locations(BytesIO(cadata))
def load_cert_chain(self, certfile, keyfile=None, password=None):
- self._ctx.use_certificate_file(certfile)
+ self._ctx.use_certificate_chain_file(certfile)
if password is not None:
self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password)
self._ctx.use_privatekey_file(keyfile or certfile)