diff options
author | Cory Benfield <lukasaoz@gmail.com> | 2017-08-16 20:11:56 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-16 20:11:56 +0100 |
commit | a72275c83a05df64156ff1ab89044ac8e649c80f (patch) | |
tree | ddcf99ba16de4cc550d5a6aebdb6bb0960ed60e8 | |
parent | 769898e3604ee0ad05e5b25fd950a615b60543df (diff) | |
parent | 890a2114a28b6b3d7ea008d3ad443119355ef1a4 (diff) | |
download | urllib3-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.rst | 3 | ||||
-rw-r--r-- | CONTRIBUTORS.txt | 3 | ||||
-rw-r--r-- | dummyserver/certs/client_intermediate.key | 15 | ||||
-rw-r--r-- | dummyserver/certs/client_intermediate.pem | 37 | ||||
-rw-r--r-- | dummyserver/certs/client_no_intermediate.pem | 19 | ||||
-rw-r--r-- | dummyserver/certs/intermediate.key | 15 | ||||
-rw-r--r-- | dummyserver/certs/intermediate.pem | 18 | ||||
-rw-r--r-- | dummyserver/handlers.py | 9 | ||||
-rwxr-xr-x | dummyserver/server.py | 15 | ||||
-rw-r--r-- | test/with_dummyserver/test_https.py | 31 | ||||
-rw-r--r-- | urllib3/contrib/pyopenssl.py | 2 |
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) |