From a94e9b5308ffcc3d2913ab873e9810a6601a67da Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Wed, 13 Mar 2024 15:58:45 -0500 Subject: [PATCH] Add local TLS server This also adds certificates for testing purposes and files to make it easy to generate/regenerate them. This also replaces an existing test of how we utilize our pool manager such that we don't connect to badssl.com Finally, this adds additional context parameters for our pool manager to account for mTLS certificates used by clients to authenticate to a server. --- src/requests/adapters.py | 18 +++- tests/certs/README.md | 10 ++ tests/certs/expired/Makefile | 13 +++ tests/certs/expired/README.md | 11 ++ tests/certs/expired/ca/Makefile | 13 +++ tests/certs/expired/ca/ca-private.key | 28 ++++++ tests/certs/expired/ca/ca.cnf | 12 +++ tests/certs/expired/ca/ca.crt | 20 ++++ tests/certs/expired/ca/ca.srl | 1 + tests/certs/expired/server/Makefile | 16 +++ tests/certs/expired/server/cert.cnf | 24 +++++ tests/certs/expired/server/server.csr | 19 ++++ tests/certs/expired/server/server.key | 28 ++++++ tests/certs/expired/server/server.pem | 41 ++++++++ tests/certs/mtls/Makefile | 7 ++ tests/certs/mtls/README.md | 4 + tests/certs/mtls/client/Makefile | 16 +++ tests/certs/mtls/client/ca | 1 + tests/certs/mtls/client/cert.cnf | 26 +++++ tests/certs/mtls/client/client.csr | 24 +++++ tests/certs/mtls/client/client.key | 28 ++++++ tests/certs/mtls/client/client.pem | 41 ++++++++ tests/certs/valid/ca | 1 + tests/certs/valid/server/Makefile | 16 +++ tests/certs/valid/server/cert.cnf | 31 ++++++ tests/certs/valid/server/server.csr | 19 ++++ tests/certs/valid/server/server.key | 28 ++++++ tests/certs/valid/server/server.pem | 47 +++++++++ tests/test_requests.py | 140 +++++++++++++++++++++++++- tests/testserver/server.py | 42 ++++++++ 30 files changed, 716 insertions(+), 9 deletions(-) create mode 100644 tests/certs/README.md create mode 100644 tests/certs/expired/Makefile create mode 100644 tests/certs/expired/README.md create mode 100644 tests/certs/expired/ca/Makefile create mode 100644 tests/certs/expired/ca/ca-private.key create mode 100644 tests/certs/expired/ca/ca.cnf create mode 100644 tests/certs/expired/ca/ca.crt create mode 100644 tests/certs/expired/ca/ca.srl create mode 100644 tests/certs/expired/server/Makefile create mode 100644 tests/certs/expired/server/cert.cnf create mode 100644 tests/certs/expired/server/server.csr create mode 100644 tests/certs/expired/server/server.key create mode 100644 tests/certs/expired/server/server.pem create mode 100644 tests/certs/mtls/Makefile create mode 100644 tests/certs/mtls/README.md create mode 100644 tests/certs/mtls/client/Makefile create mode 120000 tests/certs/mtls/client/ca create mode 100644 tests/certs/mtls/client/cert.cnf create mode 100644 tests/certs/mtls/client/client.csr create mode 100644 tests/certs/mtls/client/client.key create mode 100644 tests/certs/mtls/client/client.pem create mode 120000 tests/certs/valid/ca create mode 100644 tests/certs/valid/server/Makefile create mode 100644 tests/certs/valid/server/cert.cnf create mode 100644 tests/certs/valid/server/server.csr create mode 100644 tests/certs/valid/server/server.key create mode 100644 tests/certs/valid/server/server.pem diff --git a/src/requests/adapters.py b/src/requests/adapters.py index 6c627666..84ec48fc 100644 --- a/src/requests/adapters.py +++ b/src/requests/adapters.py @@ -73,7 +73,9 @@ DEFAULT_POOL_TIMEOUT = None def _urllib3_request_context( - request: "PreparedRequest", verify: "bool | str | None" + request: "PreparedRequest", + verify: "bool | str | None", + client_cert: "typing.Tuple[str, str] | str | None", ) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])": host_params = {} pool_kwargs = {} @@ -86,6 +88,14 @@ def _urllib3_request_context( if isinstance(verify, str): pool_kwargs["ca_certs"] = verify pool_kwargs["cert_reqs"] = cert_reqs + if client_cert is not None: + if isinstance(client_cert, tuple) and len(client_cert) == 2: + pool_kwargs["cert_file"] = client_cert[0] + pool_kwargs["key_file"] = client_cert[1] + else: + # According to our docs, we allow users to specify just the client + # cert path + pool_kwargs["cert_file"] = client_cert host_params = { "scheme": scheme, "host": parsed_request_url.hostname, @@ -354,13 +364,13 @@ class HTTPAdapter(BaseAdapter): return response - def _get_connection(self, request, verify, proxies=None): + def _get_connection(self, request, verify, proxies=None, cert=None): # Replace the existing get_connection without breaking things and # ensure that TLS settings are considered when we interact with # urllib3 HTTP Pools proxy = select_proxy(request.url, proxies) try: - host_params, pool_kwargs = _urllib3_request_context(request, verify) + host_params, pool_kwargs = _urllib3_request_context(request, verify, cert) except ValueError as e: raise InvalidURL(e, request=request) if proxy: @@ -509,7 +519,7 @@ class HTTPAdapter(BaseAdapter): """ try: - conn = self._get_connection(request, verify, proxies) + conn = self._get_connection(request, verify, proxies=proxies, cert=cert) except LocationValueError as e: raise InvalidURL(e, request=request) diff --git a/tests/certs/README.md b/tests/certs/README.md new file mode 100644 index 00000000..4bf7002e --- /dev/null +++ b/tests/certs/README.md @@ -0,0 +1,10 @@ +# Testing Certificates + +This is a collection of certificates useful for testing aspects of Requests' +behaviour. + +The certificates include: + +* [expired](./expired) server certificate with a valid certificate authority +* [mtls](./mtls) provides a valid client certificate with a 2 year validity +* [valid](./valid) has a valid server certificate diff --git a/tests/certs/expired/Makefile b/tests/certs/expired/Makefile new file mode 100644 index 00000000..d5a51da5 --- /dev/null +++ b/tests/certs/expired/Makefile @@ -0,0 +1,13 @@ +.PHONY: all clean ca server + +ca: + make -C $@ all + +server: + make -C $@ all + +all: ca server + +clean: + make -C ca clean + make -C server clean diff --git a/tests/certs/expired/README.md b/tests/certs/expired/README.md new file mode 100644 index 00000000..f7234f88 --- /dev/null +++ b/tests/certs/expired/README.md @@ -0,0 +1,11 @@ +# Expired Certificates and Configuration for Testing + +This has a valid certificate authority in [ca](./ca) and an invalid server +certificate in [server](./server). + +This can all be regenerated with: + +``` +make clean +make all +``` diff --git a/tests/certs/expired/ca/Makefile b/tests/certs/expired/ca/Makefile new file mode 100644 index 00000000..098193f8 --- /dev/null +++ b/tests/certs/expired/ca/Makefile @@ -0,0 +1,13 @@ +.PHONY: all clean + +root_files = ca-private.key ca.crt + +ca-private.key: + openssl genrsa -out ca-private.key 2048 + +all: ca-private.key + openssl req -x509 -sha256 -days 7300 -key ca-private.key -out ca.crt -config ca.cnf + ln -s ca.crt cacert.pem + +clean: + rm -f cacert.pem ca.crt ca-private.key *.csr diff --git a/tests/certs/expired/ca/ca-private.key b/tests/certs/expired/ca/ca-private.key new file mode 100644 index 00000000..507b1f56 --- /dev/null +++ b/tests/certs/expired/ca/ca-private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHlIhe7GLCeSk8 +RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He +mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK +na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6 +fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm +zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy +e6lzFyWnAgMBAAECggEAFwzHhzcD3PQDWCus85PwZoxTeQ817BmUBGpBBOKM0gLG +GCsT7XsmGP2NjICBy9OK+QTKawmb/wR5XK0OMUWDHXqtWn+NFIyojyo8+HEeCf8n +4ZleTFHLnJ+d2N1etbc2qc9mY3tjpaurq8/0Tol9YH06ock1TY2+lO+a5HvMURnY +hcWs70CamL+5B/6n67DhjzMtIW3dIXuEEceM1BW/jW8SKq0JHpQ3t+OJwID7zFaJ +bLyOwAVheMzVGvN3yphf8tll3tMA65bNjdOzgOfZSjAy7EGjW3DyAolDw9jKLRyu +E0gw/exNGe618oMIeUDv0KParlL4RjdiUP8l0xYOwQKBgQD3eYj9rWeqZquI9vKP +gaSv6urb2UJLngShZUpEZRNJgBO+Ewiof0w8tpQdsnuMvWudxMLbzgiUNA+NyC/K +CpzIXFkWnWx+A/pxs8ZO8moOfajVRayJgeOLsQZb7c4fXGsVGApbN4+cPNhTNG6d +ucErv6tae/SzAzcLc5Vkw/ELxwKBgQDOdJ5Wl5JeKAvU/3kF6+MYWCrXxZqMjoHS +y1BtyMX5RbdaWTCfDUu1aV3qJOJjjWQ9DJdJQcEsrTjOpD4bVdZx4w/XEG0JXAa3 +jRypVHGdeG/TjhUGJA8U+KX3a1DkcdqM9pqFYRw5Ie95Wz9YRroI+YkixqpK8d7W +C+5BodxXIQKBgCk8Lv9V7XgPM3XW8APJbk+BrTCEuu8unUbnQcCztssAdEmvkjnB +PErBgVyRaNTCmzPmnTFS20sWgaD2QkBAFG+uM4n5ISK+NvTLJ7fv3IwdlAw1V9Jx +uiCElrKqpTXEiHMzVkZss5ks6j6y9duCIBXSEhM5pERPvNRDphjsLTXxAoGARSNC +nyb1Kjjo9XR0V+pNy6pC9q1C+00B5tCVZ55zxe114Hi70pfGQcM+YxnlAoeoCNW9 +mBfAFDESNAlGjyrovIzYkiH7EcZSrYdBEOepgJ2DfWo4Wi0bK9+03K2AknAaS1iO +GJqTtAJMSuymwu40gKroJNA42Q40nKO0LyCARGECgYEAiFRHkblBtStv22SpZxNC +jim9yuM0ikh7Ij1lEHysc/GWb2RQNxQVk54BU2kQ0d9xwMZQTKvpF3VE9t7uGdwt +AasWPr/tWYt35Ud0D4bNlagJJ4Xdslf8n1nkq3qqqDQbd7kkQRgwGzVr0uVg7ZfS +26qSPQ0/aF9nagb5eHX3AuU= +-----END PRIVATE KEY----- diff --git a/tests/certs/expired/ca/ca.cnf b/tests/certs/expired/ca/ca.cnf new file mode 100644 index 00000000..8c4b8230 --- /dev/null +++ b/tests/certs/expired/ca/ca.cnf @@ -0,0 +1,12 @@ +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +encrypt_key = no +distinguished_name = dn + +[dn] +C = US # country code +O = Python Software Foundation # organization +OU = python-requests # organization unit/department +CN = Self-Signed Root CA # common name / your cert name diff --git a/tests/certs/expired/ca/ca.crt b/tests/certs/expired/ca/ca.crt new file mode 100644 index 00000000..c332b7cb --- /dev/null +++ b/tests/certs/expired/ca/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv +bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l +ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG +A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw +FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv +b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8 +RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He +mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK +na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6 +fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm +zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy +e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV +GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws +Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn +9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ +rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ +6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg= +-----END CERTIFICATE----- diff --git a/tests/certs/expired/ca/ca.srl b/tests/certs/expired/ca/ca.srl new file mode 100644 index 00000000..fab68405 --- /dev/null +++ b/tests/certs/expired/ca/ca.srl @@ -0,0 +1 @@ +4F36C3A7E075BA6452D10EEB81E7F189FF489B74 diff --git a/tests/certs/expired/server/Makefile b/tests/certs/expired/server/Makefile new file mode 100644 index 00000000..79914ee1 --- /dev/null +++ b/tests/certs/expired/server/Makefile @@ -0,0 +1,16 @@ +.PHONY: all clean + +server.key: + openssl genrsa -out $@ 2048 + +server.csr: server.key + openssl req -key $< -new -out $@ -config cert.cnf + +server.pem: server.csr + openssl x509 -req -CA ../ca/ca.crt -CAkey ../ca/ca-private.key -in server.csr -outform PEM -out server.pem -days 0 -CAcreateserial + openssl x509 -in ../ca/ca.crt -outform PEM >> $@ + +all: server.pem + +clean: + rm -f server.* diff --git a/tests/certs/expired/server/cert.cnf b/tests/certs/expired/server/cert.cnf new file mode 100644 index 00000000..a773fc67 --- /dev/null +++ b/tests/certs/expired/server/cert.cnf @@ -0,0 +1,24 @@ +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name +prompt=no + +[req_distinguished_name] +C = US +ST = DE +O = Python Software Foundation +OU = python-requests +CN = localhost + +[v3_req] +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = @alt_names + +[alt_names] +DNS.1 = *.localhost +DNS.1 = localhost +IP.1 = 127.0.0.1 +IP.2 = ::1 diff --git a/tests/certs/expired/server/server.csr b/tests/certs/expired/server/server.csr new file mode 100644 index 00000000..5e3c1776 --- /dev/null +++ b/tests/certs/expired/server/server.csr @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDHjCCAgYCAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK +DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl +cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCKulIMpo633iCgbkKv1UoiLC4sQt5xWpgguujywu3hLYwmPFp9 +kvPt//imqtl8FhuhKqJ8FCGrVl2YIGj1RJIB3GW7MSPNCuIBFL/gwNi35LxDPtoA +IPyXytIR7VH9+ch9DFInJaoA/BekMuKvbXk54VW9whpHbwkXSG2lBS2vKL0XemYh +9VjvtuRDji2iOZpznlVE2PEN80bojArp6oYKakv2kYzgzgxAJiI/NZGvC7mbSI4e +ja7ad3R9G0kB1FzNj36jrNO5WtxHO/mrRiXSpDeyUbitYvt0HKoM0vhTnOR+BspP +IltfwOQh8qq2Q2AaMHNcVjMH3gHCZADfhk/zAgMBAAGgbDBqBgkqhkiG9w0BCQ4x +XTBbMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB +MCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAN +BgkqhkiG9w0BAQsFAAOCAQEAfAhEhrulsZae71YFqgvzwJHm/hzXh47hErtgDXVJ +mFqAxgF6XrnzYujlt3XQXUx/8vdrU7jH+Pe8WO1rDvFwRPMDGoBF3RX29SzyX/2F +e102egnoRR+Hlf0Ixqu0CuTjEVnD+g4mRgXhV7LPKP4W6qGwzcVbaJ3c/zRcfqNR +g9gN6Q6Qt4fXDc7wlx2T3nOszBLQ2XCsIyzVtOJ2sSuadqKH9Aj+mrkrLBdzVFHD +FHnTMJ0t0+anZwd+AWDNsCr5lIwBGL634zw7/yJepMHuPFd2X24S3u8EaWPkfVQn +lV6rLQMGjXYTe2xuYzlUCUYnKvkyPTMjSXDkxWa+WSNwyQ== +-----END CERTIFICATE REQUEST----- diff --git a/tests/certs/expired/server/server.key b/tests/certs/expired/server/server.key new file mode 100644 index 00000000..27ddafd1 --- /dev/null +++ b/tests/certs/expired/server/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCKulIMpo633iCg +bkKv1UoiLC4sQt5xWpgguujywu3hLYwmPFp9kvPt//imqtl8FhuhKqJ8FCGrVl2Y +IGj1RJIB3GW7MSPNCuIBFL/gwNi35LxDPtoAIPyXytIR7VH9+ch9DFInJaoA/Bek +MuKvbXk54VW9whpHbwkXSG2lBS2vKL0XemYh9VjvtuRDji2iOZpznlVE2PEN80bo +jArp6oYKakv2kYzgzgxAJiI/NZGvC7mbSI4eja7ad3R9G0kB1FzNj36jrNO5WtxH +O/mrRiXSpDeyUbitYvt0HKoM0vhTnOR+BspPIltfwOQh8qq2Q2AaMHNcVjMH3gHC +ZADfhk/zAgMBAAECggEAFSF9RvUFzyb0BEvXN44+/QaKv+4tkMmSW4Xs3rFnZ4G3 +E8nkpLUCF9ICD2z9tKNvcPScDFdKq5z7o6ToJ9faf5MRIdrBz8UlGLIO6g6l1Bjw +vjNwJE3h+8MGjXl/IDbwXW/HgbQAeabsePPRSJRdvz2+ACn1M8VLdrLvFJA93ayW ++n3Bk0bXdsrzqBGdoDiNzmIHI3WqdONiR9TymuJe41NJtMKxQDF+c6Y1n/X1OtBk +s9L+u9Xr+R3H72xSYrf1KH1mFZJfTnIPoOmdEU2tVZnZj03rZhT7p8R1fVNX6OHu +NX1Dy9VA6J7dbcqdPvTI743ByQeb+hNnqI/3hmV5eQKBgQC++1Wn3v/dxtczjA+I +tN4a7zyjhazpB25lde55HVfCQPxmYxIYct+j6S0JkMaoLrjiEDb4pnu4Gt4MDqZa +r0Xm8t3wD1YKUUbhpBEGvsMhAEZEIsBOcwkTiEwsoF0mKFa2mTyqAImgIQa8uFt8 +Y/oTj55XFe1x6pZKEJRg+K+QSwKBgQC59ONVkMSBirLGS+G+b2kqiBdwZB/3s3wr +feS1xTa+deL3AChnKT9+MsVqOkxdE2TRj/mAeF+5Woa5bPMvgr9Kl7u8bulTH80l +YA/N6FneO11/ncnkgK9wN54kd5TiOtGsGB5S5t/nEAIMUIwWrM/cRau72xNEWOhT +Tvw7TOSF+QKBgQCa/texeiYmE24sA4vH4yIuseKAw8hlBwbtiRyVZt8GZD9zyQuy +k+g02tUWYk0XyXN65LX4bwURkZyMJIeWKZGNsaW1YnzturDQB5tZ4g/zBIoCWkHA +aVQAaimIPk3a3foiD5NQVUdckfEp0GVPOsSGg5R6EO23+i8mxPXnDW1OqQKBgGvf +lelTO8tyLFdAOcqBUt6rZ/1499p3snaAZ6bSqvk95dYnr0h48y5AQaln/FiaIYg4 +HyLZsZ4S18jFXSWYkWOyNeQP6yafciBWY5StT0TN52VaoX3+8McGXKUHAcVjHbLZ +ou2wpP6jmKyQJVQaF9LOT9uAMOMbOFrrnQLBjmfxAoGAQAnUhMFG5mwi9Otxt6Mz +g+Gr+3JTlzwC3L7UwGdlFc3G2vSdGx/yOrfzpxPImfIBS95mibDfdvEBMer26pvw +a/ycqybyX9d/5nPDIaJ1lc4M4cbHC/cB52JI6avr/1g8OMK7lR7b/FsPVHS1w8kl +n6uwEjVt2+gP2o9DFTGs158= +-----END PRIVATE KEY----- diff --git a/tests/certs/expired/server/server.pem b/tests/certs/expired/server/server.pem new file mode 100644 index 00000000..05a2a4da --- /dev/null +++ b/tests/certs/expired/server/server.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIDXjCCAkYCFE82w6fgdbpkUtEO64Hn8Yn/SJt0MA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv +bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l +ZCBSb290IENBMB4XDTI0MDMxMzIxMTQ0NVoXDTI0MDMxMzIxMTQ0NVowbTELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRIwEAYDVQQDDAls +b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCKulIMpo63 +3iCgbkKv1UoiLC4sQt5xWpgguujywu3hLYwmPFp9kvPt//imqtl8FhuhKqJ8FCGr +Vl2YIGj1RJIB3GW7MSPNCuIBFL/gwNi35LxDPtoAIPyXytIR7VH9+ch9DFInJaoA +/BekMuKvbXk54VW9whpHbwkXSG2lBS2vKL0XemYh9VjvtuRDji2iOZpznlVE2PEN +80bojArp6oYKakv2kYzgzgxAJiI/NZGvC7mbSI4eja7ad3R9G0kB1FzNj36jrNO5 +WtxHO/mrRiXSpDeyUbitYvt0HKoM0vhTnOR+BspPIltfwOQh8qq2Q2AaMHNcVjMH +3gHCZADfhk/zAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGeQdB4+iDbJ78eKhCMV +49Cm8nyYi9215rRRJ24Bw6BtVw1ECwymxLVOEB0gHCu8kKdsFnniFBtChts/ilFg +blIyPKTsb3+kQW9YV9QwVdFdC4mTIljujCSQ4HNUC/Vjfnz85SDKf9/3PMKRr36+ +GtSLIozudPvkNmCv68jy3RRXyCwWHc43BLMSZKPD/W+DEuXShI9OIpIlSLBx16Hz +4ce3/1pGuITWcsw6UcRqW31oPR31QmNs5fsq5ZCojDNFzEFCA1t9LiR6UOftFUKy +yOZWfZeAGGdK75U+XDqS9Xkr5/ic5jE0I5rT7e7r3lpvQdgIj8lSx493fczLOGHr +YA0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv +bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l +ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG +A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw +FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv +b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8 +RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He +mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK +na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6 +fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm +zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy +e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV +GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws +Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn +9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ +rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ +6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg= +-----END CERTIFICATE----- diff --git a/tests/certs/mtls/Makefile b/tests/certs/mtls/Makefile new file mode 100644 index 00000000..399a906d --- /dev/null +++ b/tests/certs/mtls/Makefile @@ -0,0 +1,7 @@ +.PHONY: all clean + +all: + make -C client all + +clean: + make -C client clean diff --git a/tests/certs/mtls/README.md b/tests/certs/mtls/README.md new file mode 100644 index 00000000..9a3df462 --- /dev/null +++ b/tests/certs/mtls/README.md @@ -0,0 +1,4 @@ +# Certificate Examples for mTLS + +This has some generated certificates for mTLS utilization. The idea is to be +able to have testing around how Requests handles client certificates. diff --git a/tests/certs/mtls/client/Makefile b/tests/certs/mtls/client/Makefile new file mode 100644 index 00000000..9c6c388b --- /dev/null +++ b/tests/certs/mtls/client/Makefile @@ -0,0 +1,16 @@ +.PHONY: all clean + +client.key: + openssl genrsa -out $@ 2048 + +client.csr: client.key + openssl req -key $< -new -out $@ -config cert.cnf + +client.pem: client.csr + openssl x509 -req -CA ./ca/ca.crt -CAkey ./ca/ca-private.key -in client.csr -outform PEM -out client.pem -days 730 -CAcreateserial + openssl x509 -in ./ca/ca.crt -outform PEM >> $@ + +all: client.pem + +clean: + rm -f client.* diff --git a/tests/certs/mtls/client/ca b/tests/certs/mtls/client/ca new file mode 120000 index 00000000..85c8e8f2 --- /dev/null +++ b/tests/certs/mtls/client/ca @@ -0,0 +1 @@ +../../expired/ca/ \ No newline at end of file diff --git a/tests/certs/mtls/client/cert.cnf b/tests/certs/mtls/client/cert.cnf new file mode 100644 index 00000000..338e2527 --- /dev/null +++ b/tests/certs/mtls/client/cert.cnf @@ -0,0 +1,26 @@ +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name +prompt=no + +[req_distinguished_name] +C = US +ST = DE +O = Python Software Foundation +OU = python-requests +CN = requests + +[v3_req] +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth +subjectAltName = @alt_names + +[alt_names] +DNS.1 = *.localhost +IP.1 = 127.0.0.1 +IP.2 = ::1 +URI.1 = spiffe://trust.python.org/v0/maintainer/sigmavirus24/project/requests/org/psf +URI.2 = spiffe://trust.python.org/v1/maintainer:sigmavirus24/project:requests/org:psf +URI.3 = spiffe://trust.python.org/v1/maintainer=sigmavirus24/project=requests/org=psf diff --git a/tests/certs/mtls/client/client.csr b/tests/certs/mtls/client/client.csr new file mode 100644 index 00000000..9a5713d5 --- /dev/null +++ b/tests/certs/mtls/client/client.csr @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEGjCCAwICAQAwbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK +DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl +cXVlc3RzMREwDwYDVQQDDAhyZXF1ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMn3iQycTjUzpKJChRNkcm33UB282cUwpxeqKN4ahHxBpS09HRhk +cQYO7yErEUQwzQnBQEcIpzzeIMZIqHuCkgnySjeEJd95AIzNzGyoLLkS51TcJwgR +v83AvT8ljA88s9h38qGTy4/TCxJgf76pfHIuC1qoKVQh3AuHj9nOxIZLUsrdDbWF +WoLqKSVyTby+RXvSAppAR+cuBCaWStQ6xFORn48RHfc6t30ggD4rDAjyU6Vz6oR8 +ot3XmGdK0h42UdqidUWkRJajEbpkCnQSXS21IvfXKxF5sFqAXJrj9iVbUfpNPpaa +W8IrHByngyV8amazGZrASstUVRFtWrnrcWECAwEAAaCCAWcwggFjBgkqhkiG9w0B +CQ4xggFUMIIBUDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggr +BgEFBQcDAjCCAR8GA1UdEQSCARYwggESggsqLmxvY2FsaG9zdIcEfwAAAYcQAAAA +AAAAAAAAAAAAAAAAAYZNc3BpZmZlOi8vdHJ1c3QucHl0aG9uLm9yZy92MC9tYWlu +dGFpbmVyL3NpZ21hdmlydXMyNC9wcm9qZWN0L3JlcXVlc3RzL29yZy9wc2aGTXNw +aWZmZTovL3RydXN0LnB5dGhvbi5vcmcvdjEvbWFpbnRhaW5lcjpzaWdtYXZpcnVz +MjQvcHJvamVjdDpyZXF1ZXN0cy9vcmc6cHNmhk1zcGlmZmU6Ly90cnVzdC5weXRo +b24ub3JnL3YxL21haW50YWluZXI9c2lnbWF2aXJ1czI0L3Byb2plY3Q9cmVxdWVz +dHMvb3JnPXBzZjANBgkqhkiG9w0BAQsFAAOCAQEAwP1KJ+Evddn2RV1FM6BFkoDK +MPDO9qwb8ea3j57SIJXZlpw168DljmuGzxJw9oys2O6FYcspbHIocAkfFwiYgVAr +NEog6xlCdPxNBJgC3YFIKwnmBjMPG6ZCWiJn940qTbaJ/j6ZviN17uW4K7Sl+THp +IkMv29uQTWkfg+GbZ9q1hm2m2GHhYLGLAUdJdtv7JI+yq5uxdsWaCANpH6kc8SnK +2rik6D3iItDhHCmToHBpdEnP8J+KDzf5pJrv/g3WH8XVrl4ZzBsOhmciWF4C3Hbf +9eu8eAsp1AsIrZOEGTfClBd7vFCES5DmI0/iRs4czQooqZPnHjOw3Azp/LujrA== +-----END CERTIFICATE REQUEST----- diff --git a/tests/certs/mtls/client/client.key b/tests/certs/mtls/client/client.key new file mode 100644 index 00000000..81071253 --- /dev/null +++ b/tests/certs/mtls/client/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDJ94kMnE41M6Si +QoUTZHJt91AdvNnFMKcXqijeGoR8QaUtPR0YZHEGDu8hKxFEMM0JwUBHCKc83iDG +SKh7gpIJ8ko3hCXfeQCMzcxsqCy5EudU3CcIEb/NwL0/JYwPPLPYd/Khk8uP0wsS +YH++qXxyLgtaqClUIdwLh4/ZzsSGS1LK3Q21hVqC6iklck28vkV70gKaQEfnLgQm +lkrUOsRTkZ+PER33Ord9IIA+KwwI8lOlc+qEfKLd15hnStIeNlHaonVFpESWoxG6 +ZAp0El0ttSL31ysRebBagFya4/YlW1H6TT6WmlvCKxwcp4MlfGpmsxmawErLVFUR +bVq563FhAgMBAAECggEABhWX97JJxN6JFNOjhgGzqiPA3R8lrFlv3zhNbODS9u9U +q404xYBZIKaYhkucLzgNJUBrevhZbsL+V8WJQIH0JlU57nw5ATIjAHA+uqiXraen +zRhTcLHK28b1AeRUA4LU+YN7jWnnawN075kf9WgjtfOJ0gcDimOkE7uCFjyyvPJA +LG9bG+8enGjvUleKXNgmwP4Sq/GlEdGz9Qy+8ga3mtfAULUWe8haFNZXK8CN3xPp +wmVqy7QzgH2TGN1p6Dyxib9ksSN/lOg0dShL8zgu+QXDNx2VwmVrI8Vr02vmB//0 +bYxCo5pfICPIFLjLl5yo30dvrUfYqF29PperStHGlQKBgQD/TdemlLjJNP0fvSs7 +KEVJj/22YuHK+wurNr2ZFbSdcF3v9sfiwysllmEyGr5cNYA56uUbfG+8VSw7kDll +G+6BKK2UdlPH++6RahqWLqo4k6rsNrkq7elj8xG4gIjR5qzu2uLpjNwp2BGmIoUI +eb1NcLfTlMcNCooV8RHjm1Z5WwKBgQDKhHkUPDcJm2/9Ltq2NZQMrCS7o4LV2uAI +GhGpISfY+SfHkQQNZ9Fvbe6hrFeZs31nAvlTDpPEg/LGSVKA5I2EZT9gwzAQU1TD +Cyol4xqqWFWlwze7w+RLYqX5LtXf7NJg2m5p+ZOoOzzqvTVpodDxqTlCNp2/6ICP +vAIvWhbA8wKBgAYlr62ZIyHlHrsm6OWRwKlWyDseAmXKyasjtEj9Vs37qKdgf8ub ++2v6RPjZ3/+EYkQCveV9h4s3WctNW7Rtib6eZh+PAdFs5X+m2GEJWpvmIlVxs9+u +vtHjRmf04FZ9gWh26MPK2no/c51Wc3GSzNYSgrqbeHd963k/xrh+QwTFAoGAZZjb +3UjwG4O9RPjyhCKQ6WKa8v9urbamWaoqXfziLrmgOUAJFmiU6x/tbXI2aEdhjAIz +7nULsLS5YLx8BWmjjV3106dYP3hut4KsXGF4iSjTnts25J27tA4DUeUrKrF2QVyT +s9qfNvCw+Np/J0Uku3e33/3iWdpcVL9vIS5C5/0CgYBEuxb3dffNRqEiNkpOUrCD +mQTqbO3X+hin9zT3GrxQE+7KpfCfdDIqdK6c5UWHirR3HUjUPZmIFLSx8msfLl3k +hgQw37NMV+asg0Wy3P908qbtnEA2P6aDOMQeHJoC7qEHIDOcOQ1KP3FMvOrdscwS +f0IIDygTH6fYr329s0iXjg== +-----END PRIVATE KEY----- diff --git a/tests/certs/mtls/client/client.pem b/tests/certs/mtls/client/client.pem new file mode 100644 index 00000000..0a11d4d4 --- /dev/null +++ b/tests/certs/mtls/client/client.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkUCFE82w6fgdbpkUtEO64Hn8Yn/SJtzMA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv +bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l +ZCBSb290IENBMB4XDTI0MDMxMzE4MzUwNFoXDTI2MDMxMzE4MzUwNFowbDELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMREwDwYDVQQDDAhy +ZXF1ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn3iQycTjUz +pKJChRNkcm33UB282cUwpxeqKN4ahHxBpS09HRhkcQYO7yErEUQwzQnBQEcIpzze +IMZIqHuCkgnySjeEJd95AIzNzGyoLLkS51TcJwgRv83AvT8ljA88s9h38qGTy4/T +CxJgf76pfHIuC1qoKVQh3AuHj9nOxIZLUsrdDbWFWoLqKSVyTby+RXvSAppAR+cu +BCaWStQ6xFORn48RHfc6t30ggD4rDAjyU6Vz6oR8ot3XmGdK0h42UdqidUWkRJaj +EbpkCnQSXS21IvfXKxF5sFqAXJrj9iVbUfpNPpaaW8IrHByngyV8amazGZrASstU +VRFtWrnrcWECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHHgMckLDRV72p1FEVmCh +AAPZjCswiPZFrwGPN57JqSWjoRB9ilKvo87aPosEO7vfa05OD/qkM/T9Qykuhati +I1T1T7qX4Ymb5kTJIBouuflAO3uKVaq+ga2Q/HLlU5w/VoMU4RuK7+RaiRUEE3xL +iPSMBvZpoMj695LnzcGrT5oLkFI0bTIlpQt1SFjDpHFtOj/ZdwgSbZYLoTCBXQK3 +7Y29qAj/XwEiCH63n8tJKvZcD8/ssMIMIdWhNmu+0jOWica/3WSih9Geoy6Ydtxi +I5t9vRjC4LIipMUAF86AJIfvHJyI6aCNT420LaR6NRW0FQn5CPTHPAsKg3JkAywn +Ew== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv +bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l +ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG +A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw +FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv +b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8 +RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He +mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK +na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6 +fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm +zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy +e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV +GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws +Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn +9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ +rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ +6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg= +-----END CERTIFICATE----- diff --git a/tests/certs/valid/ca b/tests/certs/valid/ca new file mode 120000 index 00000000..46f26c39 --- /dev/null +++ b/tests/certs/valid/ca @@ -0,0 +1 @@ +../expired/ca \ No newline at end of file diff --git a/tests/certs/valid/server/Makefile b/tests/certs/valid/server/Makefile new file mode 100644 index 00000000..9ce6778c --- /dev/null +++ b/tests/certs/valid/server/Makefile @@ -0,0 +1,16 @@ +.PHONY: all clean + +server.key: + openssl genrsa -out $@ 2048 + +server.csr: server.key + openssl req -key $< -config cert.cnf -new -out $@ + +server.pem: server.csr + openssl x509 -req -CA ../ca/ca.crt -CAkey ../ca/ca-private.key -in server.csr -outform PEM -out server.pem -extfile cert.cnf -extensions v3_ca -days 7200 -CAcreateserial + openssl x509 -in ../ca/ca.crt -outform PEM >> $@ + +all: server.pem + +clean: + rm -f server.* diff --git a/tests/certs/valid/server/cert.cnf b/tests/certs/valid/server/cert.cnf new file mode 100644 index 00000000..f9a01cd8 --- /dev/null +++ b/tests/certs/valid/server/cert.cnf @@ -0,0 +1,31 @@ +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name +prompt=no + +[req_distinguished_name] +C = US +ST = DE +O = Python Software Foundation +OU = python-requests +CN = localhost + +[v3_req] +# Extensions to add to a certificate request +basicConstraints = critical, CA:FALSE +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = critical, serverAuth +subjectAltName = critical, @alt_names + +[v3_ca] +# Extensions to add to a certificate request +basicConstraints = critical, CA:FALSE +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = critical, serverAuth +subjectAltName = critical, @alt_names + +[alt_names] +DNS.1 = *.localhost +DNS.1 = localhost +IP.1 = 127.0.0.1 +IP.2 = ::1 diff --git a/tests/certs/valid/server/server.csr b/tests/certs/valid/server/server.csr new file mode 100644 index 00000000..000d1fac --- /dev/null +++ b/tests/certs/valid/server/server.csr @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDKjCCAhICAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK +DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl +cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQChEKOx377ymuDg23By5Re1DHi2RiBKSHr85/ZTZuwP/69lHN7q +TQEO//EMEFZ9+ZwezeJJsejjP2HO5lQZbcsWok3hbM0wVT+vApkogPvJ8WNFFWFe +ZBnGLi/1WM9cSZpUsDJ0XCsG0RTtO27wfgZQlKQMZxTkfi971oPYxNVSjTm2JcLT +kvwYIwxjJXPDTOgRo9TEAY3cWkCrBJN4w74GWBTM5KDDA230T7WwLuv81XD2LvYj +YYdMBGcxPr5tYTIlp3LncbcrDRNk3pbYQk0bRJgkw2vUkteiRGjkt+dgVnLc6+MI +W+VLXEpj+zsOZ5/R4d1pofqj9sDyDPhtNr1JAgMBAAGgeDB2BgkqhkiG9w0BCQ4x +aTBnMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBYGA1UdJQEB/wQMMAoG +CCsGAQUFBwMBMC8GA1UdEQEB/wQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAA +AAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAFTlFTn5Mn8JXtqB5bGjuiChe +ClA6Y32Co4l7N0CtAlf+bExwLdpLOleTX3WnryIPALl9uBUI/67dy/STn/J1Yn86 +jWPEFwpmYNSKgQljYWcwtBdYLWfIsJO11kKdaAkOUHBEN5DKrXJ46Vs4918bD1/Q +6ztqdrThiKc646u9xB58Hg7F0IyMWbHfs0x16ZpcN9otrIkbqOE2wzTmc65O1t1i +HDljcSk7OnNy3a9wtLEnyPiyMqHf2k/bTlmiDRVe3cSy9xieoqmzHTnOCSASe1y9 +7lcEBQild18Jo4nACV4vCYOUwrMi/58LWW+lD6OmMnPiWUqOvMbgMffMNDpWPA== +-----END CERTIFICATE REQUEST----- diff --git a/tests/certs/valid/server/server.key b/tests/certs/valid/server/server.key new file mode 100644 index 00000000..d6afaf59 --- /dev/null +++ b/tests/certs/valid/server/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChEKOx377ymuDg +23By5Re1DHi2RiBKSHr85/ZTZuwP/69lHN7qTQEO//EMEFZ9+ZwezeJJsejjP2HO +5lQZbcsWok3hbM0wVT+vApkogPvJ8WNFFWFeZBnGLi/1WM9cSZpUsDJ0XCsG0RTt +O27wfgZQlKQMZxTkfi971oPYxNVSjTm2JcLTkvwYIwxjJXPDTOgRo9TEAY3cWkCr +BJN4w74GWBTM5KDDA230T7WwLuv81XD2LvYjYYdMBGcxPr5tYTIlp3LncbcrDRNk +3pbYQk0bRJgkw2vUkteiRGjkt+dgVnLc6+MIW+VLXEpj+zsOZ5/R4d1pofqj9sDy +DPhtNr1JAgMBAAECggEAIuLzBfXgCvXzlBjL2kMXd7p4EgkN+PEKnKmUr/t40b1Q +zR6sBQWBX3GeET4fseElSQHQzCQaPNCve4xltm1S4jftFREHP7sTVHHEYWLQxuy/ +Uwkewj5927CI6ERgg82YfVP91bjaA/u5I+pt7O7rKLyNbPdN7fEMEW+FNuhpiVvg +JMrcK1BCFL6pmIT21LyTwkacMKZSPko58pWE24MA9aSCHk6cXdwQWQK0AfQT3XGT +C4I0hRed7LgqMH+gMuhpakiO13t8yTwxt2iQC9+aa4oSHD3BOi/CwIWfe1mHwmlr +cj4Kof1JSnK4SVTD16T++PlnWZkF6oaLUNg+/c2C9QKBgQDOFSYIY7+HzinT2hbI +yTIJCHpp+Iee+WVvvxjdZIPMDINrlIiHcMfXb0itUdcUO6tz0KYDMDLRC9CSP0ar +6mBWUTHfAKF2S4JpI9JYI4PNtIpOP1NiYuyJlnh5+ytU1yIeIvl39hmLcRwI9mgz +njy/D7yEoDCrG1dhcltubKpNXQKBgQDIFAVg0A7MNcxBZDLlk1NAME2JKOSszX8E +VNucvZD+9l+L9V9BmwwPQdzYifv/dNp3nYn+lxRPPgze3ZWu4+PeDuGudxu0I6ll +beFdbIcp1wbeQguzHYLjBYJqsMb4Pao5HPInjPu/HWfZlg9oZpJbKVucQwbonJLX +lgca9KaE3QKBgA+OUx+g/+0tZ8ThGoUvgsJhzHPBWeNrKfgEcckMdFJrw2PUg3XN +0pf1g4PpwJV7Z5bHcjCda8iR3r2bXydM+tapLF2L+6QlUQPEu3UBwUo+zY3Yg9/S +Xc6I+DEk/4FY9+9UboZaolT/RcF7cCQtVqKJeo58VRAlcTQe4L32H+jVAoGALXX3 +Ht9HbXkP1w/YTLej4+LVy0OCag0rPiW13LBqALSkUx3GrhZ3sAPMFVuM6ad4eFNQ +ZouXbsXvkLgSabGYNf11o/mmTtEHjWdhHKQrNgOIqPmixOkAs2quDmXqX79LLTz5 +fKkZDny0+wiQqa0cth/4k9HbAQGKj/ej16kdKPUCgYAz08Y39NnJYxRNz3tu/7C6 +jKyXKxhuZCZCt3cSWto5Tg0mVVB+2Jk2GhG1hCfZoRCP25R3FFBR1HOJgOc59T7C +LL67FdO0+7mj/WNzHj3+9gyOYQyQgPVDaTmsJLbuzT2S+GpR94ZNliwL2NEa5baG +B/Nb2ruRNj0GgZVw48N4XQ== +-----END PRIVATE KEY----- diff --git a/tests/certs/valid/server/server.pem b/tests/certs/valid/server/server.pem new file mode 100644 index 00000000..0168cd3e --- /dev/null +++ b/tests/certs/valid/server/server.pem @@ -0,0 +1,47 @@ +-----BEGIN CERTIFICATE----- +MIIEhTCCA22gAwIBAgIUTzbDp+B1umRS0Q7rgefxif9Im3wwDQYJKoZIhvcNAQEL +BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu +ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt +U2lnbmVkIFJvb3QgQ0EwHhcNMjQwMzE0MDAxMDAzWhcNNDMxMTMwMDAxMDAzWjBt +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCREUxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0 +d2FyZSBGb3VuZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxEjAQBgNV +BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKEQ +o7HfvvKa4ODbcHLlF7UMeLZGIEpIevzn9lNm7A//r2Uc3upNAQ7/8QwQVn35nB7N +4kmx6OM/Yc7mVBltyxaiTeFszTBVP68CmSiA+8nxY0UVYV5kGcYuL/VYz1xJmlSw +MnRcKwbRFO07bvB+BlCUpAxnFOR+L3vWg9jE1VKNObYlwtOS/BgjDGMlc8NM6BGj +1MQBjdxaQKsEk3jDvgZYFMzkoMMDbfRPtbAu6/zVcPYu9iNhh0wEZzE+vm1hMiWn +cudxtysNE2TelthCTRtEmCTDa9SS16JEaOS352BWctzr4whb5UtcSmP7Ow5nn9Hh +3Wmh+qP2wPIM+G02vUkCAwEAAaOCAR4wggEaMAwGA1UdEwEB/wQCMAAwDgYDVR0P +AQH/BAQDAgWgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMBMC8GA1UdEQEB/wQlMCOC +CWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUJ90a +UnXKPP13yDprLhG39fUrnu8wgZEGA1UdIwSBiTCBhqFupGwwajELMAkGA1UEBhMC +VVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgwFgYDVQQL +DA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJvb3QgQ0GC +FA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAA4IBAQCVh4hiraRv +JzYbS/TombP//xfVEWHXDBEYsT5GgWf7GPJ/QtSvv6uJFsK7heqLzf9f+r4Z5xMh +YAkb0oe/Ge0T30Mo1YaBEqkKuQL9lOMcP69S9uFz2VT6I/76I8qqAu2AFhu74p8f +qudwmQyRYo1Ryg4R/SgRhSJKF/ST/2wOusNWSsBe1s8S2PmtOb4dr3cMBGihrUzS +DmCQpWjuiuE23HXnnYDc/EUAnEEPkLDgCsE9iLq37FPUHcHjqdYIAhmImPBpv2EL +ftXeRWfxN2hRHpS5Fn3QuAOwfJw5tUcVXojJCJfSpL+Ac97iSjxNaDIPlyomauKw +1rgbUkSw+9JQ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv +bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l +ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG +A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw +FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv +b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8 +RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He +mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK +na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6 +fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm +zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy +e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV +GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws +Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn +9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ +rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ +6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg= +-----END CERTIFICATE----- diff --git a/tests/test_requests.py b/tests/test_requests.py index d5cc13c7..4de47bc6 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -7,6 +7,7 @@ import json import os import pickle import re +import threading import warnings from unittest import mock @@ -51,6 +52,7 @@ from requests.structures import CaseInsensitiveDict from . import SNIMissingWarning from .compat import StringIO +from .testserver.server import TLSServer, consume_socket_content from .utils import override_environ # Requests to this URL should always fail with a connection timeout (nothing @@ -2828,12 +2830,140 @@ class TestPreparingURLs: assert r5 == 425 assert r6 == 425 - def test_different_connection_pool_for_tls_settings(self): + def test_different_connection_pool_for_tls_settings_verify_True(self): + def response_handler(sock): + consume_socket_content(sock, timeout=0.5) + sock.send( + b"HTTP/1.1 200 OK\r\n" + b"Content-Length: 18\r\n\r\n" + b'\xff\xfe{\x00"\x00K0"\x00=\x00"\x00\xab0"\x00\r\n' + ) + s = requests.Session() - r1 = s.get("https://invalid.badssl.com", verify=False) - assert r1.status_code == 421 - with pytest.raises(requests.exceptions.SSLError): - s.get("https://invalid.badssl.com") + close_server = threading.Event() + server = TLSServer( + handler=response_handler, + wait_to_close_event=close_server, + requests_to_handle=3, + cert_chain="tests/certs/expired/server/server.pem", + keyfile="tests/certs/expired/server/server.key", + ) + + with server as (host, port): + url = f"https://{host}:{port}" + r1 = s.get(url, verify=False) + assert r1.status_code == 200 + + # Cannot verify self-signed certificate + with pytest.raises(requests.exceptions.SSLError): + s.get(url) + + close_server.set() + assert 2 == len(s.adapters["https://"].poolmanager.pools) + + def test_different_connection_pool_for_tls_settings_verify_bundle_expired_cert( + self, + ): + def response_handler(sock): + consume_socket_content(sock, timeout=0.5) + sock.send( + b"HTTP/1.1 200 OK\r\n" + b"Content-Length: 18\r\n\r\n" + b'\xff\xfe{\x00"\x00K0"\x00=\x00"\x00\xab0"\x00\r\n' + ) + + s = requests.Session() + close_server = threading.Event() + server = TLSServer( + handler=response_handler, + wait_to_close_event=close_server, + requests_to_handle=3, + cert_chain="tests/certs/expired/server/server.pem", + keyfile="tests/certs/expired/server/server.key", + ) + + with server as (host, port): + url = f"https://{host}:{port}" + r1 = s.get(url, verify=False) + assert r1.status_code == 200 + + # Has right trust bundle, but certificate expired + with pytest.raises(requests.exceptions.SSLError): + s.get(url, verify="tests/certs/expired/ca/ca.crt") + + close_server.set() + assert 2 == len(s.adapters["https://"].poolmanager.pools) + + def test_different_connection_pool_for_tls_settings_verify_bundle_unexpired_cert( + self, + ): + def response_handler(sock): + consume_socket_content(sock, timeout=0.5) + sock.send( + b"HTTP/1.1 200 OK\r\n" + b"Content-Length: 18\r\n\r\n" + b'\xff\xfe{\x00"\x00K0"\x00=\x00"\x00\xab0"\x00\r\n' + ) + + s = requests.Session() + close_server = threading.Event() + server = TLSServer( + handler=response_handler, + wait_to_close_event=close_server, + requests_to_handle=3, + cert_chain="tests/certs/valid/server/server.pem", + keyfile="tests/certs/valid/server/server.key", + ) + + with server as (host, port): + url = f"https://{host}:{port}" + r1 = s.get(url, verify=False) + assert r1.status_code == 200 + + r2 = s.get(url, verify="tests/certs/valid/ca/ca.crt") + assert r2.status_code == 200 + + close_server.set() + assert 2 == len(s.adapters["https://"].poolmanager.pools) + + def test_different_connection_pool_for_mtls_settings(self): + client_cert = None + + def response_handler(sock): + nonlocal client_cert + client_cert = sock.getpeercert() + consume_socket_content(sock, timeout=0.5) + sock.send( + b"HTTP/1.1 200 OK\r\n" + b"Content-Length: 18\r\n\r\n" + b'\xff\xfe{\x00"\x00K0"\x00=\x00"\x00\xab0"\x00\r\n' + ) + + s = requests.Session() + close_server = threading.Event() + server = TLSServer( + handler=response_handler, + wait_to_close_event=close_server, + requests_to_handle=2, + cert_chain="tests/certs/expired/server/server.pem", + keyfile="tests/certs/expired/server/server.key", + mutual_tls=True, + cacert="tests/certs/expired/ca/ca.crt", + ) + + cert = ( + "tests/certs/mtls/client/client.pem", + "tests/certs/mtls/client/client.key", + ) + with server as (host, port): + url = f"https://{host}:{port}" + r1 = s.get(url, verify=False, cert=cert) + assert r1.status_code == 200 + with pytest.raises(requests.exceptions.SSLError): + s.get(url, cert=cert) + close_server.set() + + assert client_cert is not None def test_json_decode_errors_are_serializable_deserializable(): diff --git a/tests/testserver/server.py b/tests/testserver/server.py index 5936abdf..da1b6560 100644 --- a/tests/testserver/server.py +++ b/tests/testserver/server.py @@ -1,5 +1,6 @@ import select import socket +import ssl import threading @@ -132,3 +133,44 @@ class Server(threading.Thread): self._close_server_sock_ignore_errors() self.join() return False # allow exceptions to propagate + + +class TLSServer(Server): + def __init__( + self, + *, + handler=None, + host="localhost", + port=0, + requests_to_handle=1, + wait_to_close_event=None, + cert_chain=None, + keyfile=None, + mutual_tls=False, + cacert=None, + ): + super().__init__( + handler=handler, + host=host, + port=port, + requests_to_handle=requests_to_handle, + wait_to_close_event=wait_to_close_event, + ) + self.cert_chain = cert_chain + self.keyfile = keyfile + self.ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.ssl_context.load_cert_chain(self.cert_chain, keyfile=self.keyfile) + self.mutual_tls = mutual_tls + self.cacert = cacert + if mutual_tls: + # For simplicity, we're going to assume that the client cert is + # issued by the same CA as our Server certificate + self.ssl_context.verify_mode = ssl.CERT_OPTIONAL + self.ssl_context.load_verify_locations(self.cacert) + + def _create_socket_and_bind(self): + sock = socket.socket() + sock = self.ssl_context.wrap_socket(sock, server_side=True) + sock.bind((self.host, self.port)) + sock.listen() + return sock