diff --git a/.gitignore b/.gitignore index 9b41b38..dcc0eca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ /requests-pkcs12-1.7.tar.gz -/requests-pkcs12-1.25.tar.gz -/requests-pkcs12-1.27.tar.gz diff --git a/python-requests-pkcs12.spec b/python-requests-pkcs12.spec index f39902b..fe8405a 100644 --- a/python-requests-pkcs12.spec +++ b/python-requests-pkcs12.spec @@ -1,14 +1,13 @@ %global pypi_name requests-pkcs12 Name: python-%{pypi_name} -Version: 1.27 +Version: 1.7 Release: 1%{?dist} Summary: Add PKCS12 support to the requests library License: ISC URL: https://github.com/m-click/requests_pkcs12 Source0: %{url}/archive/%{version}/%{pypi_name}-%{version}.tar.gz -Source1: test_integration.py BuildArch: noarch %description @@ -20,11 +19,8 @@ TransportAdapter, which provides a custom SSLContext. Summary: %{summary} BuildRequires: python3-devel - -# For tests -BuildRequires: python3-requests -BuildRequires: python3-pytest -BuildRequires: openssl +BuildRequires: python3-setuptools +%{?python_provide:%python_provide python3-%{pypi_name}} %description -n python3-%{pypi_name} This library adds PKCS12 support to the Python requests library. It is @@ -33,105 +29,21 @@ TransportAdapter, which provides a custom SSLContext. %prep %autosetup -n requests_pkcs12-%{version} -cp %{SOURCE1} . - -%generate_buildrequires -%pyproject_buildrequires +rm -rf %{pypi_name}.egg-info %build -%pyproject_wheel +%py3_build %install -%pyproject_install -%pyproject_save_files -l requests_pkcs12 +%py3_install -%check -%pyproject_check_import -%{pytest} -v - -# embeded test with connection to example.com -# skip it with unavailable network (in koji) -if getent hosts example.com; then - PYTHONDONTWRITEBYTECODE=1 \ - PATH="%{buildroot}%{_bindir}:$PATH" \ - PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}" \ - %{__python3} -c 'import requests_pkcs12; requests_pkcs12.test()' -fi - -%files -n python3-%{pypi_name} -f %{pyproject_files} +%files -n python3-%{pypi_name} +%license LICENSE %doc README.rst +%{python3_sitelib}/__pycache__/* +%{python3_sitelib}/requests_pkcs12.py +%{python3_sitelib}/requests_pkcs12-%{version}-py*.egg-info/ %changelog -* Mon Sep 22 2025 Lukas Slebodnik - 1.27-1 -- New upstream version 1.27 -- Fix serialisation on FIPS enabled system - -* Fri Sep 19 2025 Python Maint - 1.25-7 -- Rebuilt for Python 3.14.0rc3 bytecode - -* Wed Aug 27 2025 Lukas Slebodnik - 1.25-6 -- rhbz#2378169 Migrating to pyproject macros -- https://fedoraproject.org/wiki/Changes/DeprecateSetuppyMacros - -* Fri Aug 15 2025 Python Maint - 1.25-5 -- Rebuilt for Python 3.14.0rc2 bytecode - -* Fri Jul 25 2025 Fedora Release Engineering - 1.25-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild - -* Mon Jun 02 2025 Python Maint - 1.25-3 -- Rebuilt for Python 3.14 - -* Sat Jan 18 2025 Fedora Release Engineering - 1.25-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild - -* Mon Jul 22 2024 Lukas Slebodnik - 1.25-1 -- New upstream version 1.25 - -* Fri Jul 19 2024 Fedora Release Engineering - 1.7-16 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild - -* Fri Jun 07 2024 Python Maint - 1.7-15 -- Rebuilt for Python 3.13 - -* Fri Jan 26 2024 Fedora Release Engineering - 1.7-14 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild - -* Mon Jan 22 2024 Fedora Release Engineering - 1.7-13 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild - -* Fri Jul 21 2023 Fedora Release Engineering - 1.7-12 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild - -* Tue Jun 13 2023 Python Maint - 1.7-11 -- Rebuilt for Python 3.12 - -* Fri Jan 20 2023 Fedora Release Engineering - 1.7-10 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild - -* Fri Jul 22 2022 Fedora Release Engineering - 1.7-9 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild - -* Mon Jun 13 2022 Python Maint - 1.7-8 -- Rebuilt for Python 3.11 - -* Fri Jan 21 2022 Fedora Release Engineering - 1.7-7 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild - -* Fri Jul 23 2021 Fedora Release Engineering - 1.7-6 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild - -* Fri Jun 04 2021 Python Maint - 1.7-5 -- Rebuilt for Python 3.10 - -* Wed Jan 27 2021 Fedora Release Engineering - 1.7-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild - -* Wed Jul 29 2020 Fedora Release Engineering - 1.7-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild - -* Tue May 26 2020 Miro HronĨok - 1.7-2 -- Rebuilt for Python 3.9 - * Thu Mar 19 2020 Fabian Affolter - 1.7-1 - Initial package for Fedora diff --git a/sources b/sources index 074ca0a..5fd4ed6 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (requests-pkcs12-1.27.tar.gz) = f8c2a8eb0a03ebb10f07ddf8e5470f63d7eb038938ead31053145a3eca37e77135e9d961230378d157ef616d575c1b39f9fa5fb84cb26f0d16738e1fff607ce5 +SHA512 (requests-pkcs12-1.7.tar.gz) = 69a12a15b3f851614d8672ad5b87d72abee4156453b21013f809288005342ac3cf84d2bbb07e116ff16ad400627bbacbb5bd340d01e87389b9ee5fef7d2b35a0 diff --git a/test_integration.py b/test_integration.py deleted file mode 100644 index a51911b..0000000 --- a/test_integration.py +++ /dev/null @@ -1,426 +0,0 @@ -# SPDX-License-Identifier: MIT -import http.server -import os -import ssl -import subprocess -import threading -import unittest - -import requests -import requests_pkcs12 -import urllib3.util - -# --- Configuration --- -HOST = "localhost" -IP_ADDRESS = "127.0.0.1" -PORT = 9443 - -# --- File Names --- -ROOT_CA_KEY = "test_rootCA.key" -ROOT_CA_CSR = "test_rootCA.csr" -ROOT_CA_PEM = "test_rootCA.pem" -SERVER_KEY = "test_server.key" -SERVER_CSR = "test_server.csr" -SERVER_CERT = "test_server.crt" -CLIENT_KEY = "test_client.key" -CLIENT_CSR = "test_client.csr" -CLIENT_CERT = "test_client.crt" -CLIENT_P12_NO_PWD = "test_client_no_pwd.p12" -CLIENT_P12_WITH_PWD = "test_client_with_pwd.p12" -CA_V3_EXT_FILE = "ca_v3.ext" -SERVER_V3_EXT_FILE = "server_v3.ext" -P12_PASSWORD = "testpassword" - -GENERATED_FILES = [ - ROOT_CA_KEY, - ROOT_CA_CSR, - ROOT_CA_PEM, - SERVER_KEY, - SERVER_CSR, - SERVER_CERT, - CLIENT_KEY, - CLIENT_CSR, - CLIENT_CERT, - CLIENT_P12_NO_PWD, - CLIENT_P12_WITH_PWD, - CA_V3_EXT_FILE, - SERVER_V3_EXT_FILE, - "test_rootCA.srl", -] - - -def run_command(args): - """Helper function to run a shell command as a list of arguments.""" - subprocess.run(args, check=True) - - -class TestMTLSClient(unittest.TestCase): - """Test suite for mTLS client connections with an embedded server.""" - - httpd = None - server_thread = None - - @staticmethod - def _start_embedded_server(): - """Creates and returns a configured HTTPServer instance.""" - server_address = (IP_ADDRESS, PORT) - - context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) - context.load_cert_chain(certfile=SERVER_CERT, keyfile=SERVER_KEY) - context.load_verify_locations(cafile=ROOT_CA_PEM) - context.verify_mode = ssl.CERT_REQUIRED - - httpd = http.server.HTTPServer(server_address, http.server.SimpleHTTPRequestHandler) - httpd.socket = context.wrap_socket(httpd.socket, server_side=True) - - return httpd - - @classmethod - def setUpClass(cls): - """Set up the test environment: generate certs and start the server.""" - print("--- Setting up test environment ---") - - # 1. Create the v3.ext files and generate all certificates - print("Generating certificates...") - - with open(CA_V3_EXT_FILE, "w") as f: - f.write("subjectKeyIdentifier=hash\n") - f.write("authorityKeyIdentifier=keyid:always,issuer\n") - f.write("basicConstraints = critical,CA:TRUE\n") - f.write("keyUsage = critical,digitalSignature,cRLSign,keyCertSign\n") - - with open(SERVER_V3_EXT_FILE, "w") as f: - f.write("authorityKeyIdentifier=keyid,issuer\n") - f.write("basicConstraints=CA:FALSE\n") - f.write("subjectAltName = @alt_names\n\n[alt_names]\n") - f.write(f"DNS.1 = {HOST}\nIP.1 = {IP_ADDRESS}\n") - - run_command(['openssl', 'genrsa', '-out', ROOT_CA_KEY, '4096']) - run_command( - [ - 'openssl', - 'req', - '-new', - '-key', - ROOT_CA_KEY, - '-out', - ROOT_CA_CSR, - '-subj', - '/C=ZZ/O=Test/CN=Test Root CA', - ] - ) - run_command( - [ - 'openssl', - 'x509', - '-req', - '-in', - ROOT_CA_CSR, - '-signkey', - ROOT_CA_KEY, - '-out', - ROOT_CA_PEM, - '-days', - '31', - '-sha512', - '-extfile', - CA_V3_EXT_FILE, - ] - ) - - run_command(['openssl', 'genrsa', '-out', SERVER_KEY, '2048']) - run_command( - [ - 'openssl', - 'req', - '-new', - '-key', - SERVER_KEY, - '-out', - SERVER_CSR, - '-subj', - f'/C=ZZ/O=Test/CN={HOST}', - ] - ) - run_command( - [ - 'openssl', - 'x509', - '-req', - '-in', - SERVER_CSR, - '-CA', - ROOT_CA_PEM, - '-CAkey', - ROOT_CA_KEY, - '-CAcreateserial', - '-out', - SERVER_CERT, - '-days', - '7', - '-sha512', - '-extfile', - SERVER_V3_EXT_FILE, - ] - ) - run_command(['openssl', 'genrsa', '-out', CLIENT_KEY, '2048']) - run_command( - [ - 'openssl', - 'req', - '-new', - '-key', - CLIENT_KEY, - '-out', - CLIENT_CSR, - '-subj', - '/C=ZZ/O=Test/CN=Test Client', - ] - ) - run_command( - [ - 'openssl', - 'x509', - '-req', - '-in', - CLIENT_CSR, - '-CA', - ROOT_CA_PEM, - '-CAkey', - ROOT_CA_KEY, - '-CAcreateserial', - '-out', - CLIENT_CERT, - '-days', - '7', - '-sha512', - ] - ) - run_command( - [ - 'openssl', - 'pkcs12', - '-export', - '-out', - CLIENT_P12_NO_PWD, - '-inkey', - CLIENT_KEY, - '-in', - CLIENT_CERT, - '-passout', - 'pass:', - ] - ) - run_command( - [ - 'openssl', - 'pkcs12', - '-export', - '-out', - CLIENT_P12_WITH_PWD, - '-inkey', - CLIENT_KEY, - '-in', - CLIENT_CERT, - '-passout', - f'pass:{P12_PASSWORD}', - ] - ) - print("Certificates generated successfully.") - - # 2. Start the embedded mTLS server in a background thread - print(f"Starting embedded server on https://{IP_ADDRESS}:{PORT}") - cls.httpd = cls._start_embedded_server() - - cls.server_thread = threading.Thread(target=cls.httpd.serve_forever) - cls.server_thread.daemon = ( - True # Allows main thread to exit even if server thread is running - ) - cls.server_thread.start() - - print("Server is running in a background thread.") - - if hasattr(urllib3.util, 'IS_SECURETRANSPORT'): - print(f"urllib3 version {urllib3.__version__} has IS_SECURETRANSPORT.") - print("Forcing to True to pass IP as server_hostname.") - urllib3.util.ssl_.IS_SECURETRANSPORT = True - - @classmethod - def tearDownClass(cls): - """Clean up the environment: stop the server and delete files.""" - print("\n--- Tearing down test environment ---") - if cls.httpd: - print("Shutting down embedded server...") - cls.httpd.shutdown() - cls.server_thread.join() - print("Server stopped.") - - print("Cleaning up generated files...") - for f in GENERATED_FILES: - try: - os.remove(f) - except FileNotFoundError: - pass - print("Cleanup complete.") - - def test_requests_pem_cert_with_hostname(self): - """Tests connection to localhost using PEM certificate and key.""" - url = f"https://{HOST}:{PORT}" - - response = requests.get(url, cert=(CLIENT_CERT, CLIENT_KEY), verify=ROOT_CA_PEM, timeout=10) - self.assertEqual(response.status_code, 200) - - def test_requests_pem_cert_with_ip(self): - """Tests connection to 127.0.0.1 using PEM certificate and key.""" - url = f"https://{IP_ADDRESS}:{PORT}" - - response = requests.get(url, cert=(CLIENT_CERT, CLIENT_KEY), verify=ROOT_CA_PEM, timeout=10) - self.assertEqual(response.status_code, 200) - - def test_requests_nocert_with_hostname(self): - """Tests connection to localhost without client certificate.""" - url = f"https://{HOST}:{PORT}" - - with self.assertRaises(requests.exceptions.SSLError) as cm: - requests.get(url, verify=ROOT_CA_PEM, timeout=10) - - exc = cm.exception - self.assertIn("alert certificate required", str(exc)) - - def test_requests_nocert_with_ip(self): - """Tests connection to localhost without client certificate.""" - url = f"https://{IP_ADDRESS}:{PORT}" - - with self.assertRaises(requests.exceptions.SSLError) as cm: - requests.get(url, verify=ROOT_CA_PEM, timeout=10) - - exc = cm.exception - self.assertIn("alert certificate required", str(exc)) - - def test_requests_pkcs12_with_password_and_hostname(self): - """Tests connection using a password-protected PKCS12 file.""" - url = f"https://{HOST}:{PORT}" - - response = requests_pkcs12.get( - url, - pkcs12_filename=CLIENT_P12_WITH_PWD, - pkcs12_password=P12_PASSWORD, - verify=ROOT_CA_PEM, - timeout=10, - ) - self.assertEqual(response.status_code, 200) - - def test_requests_pkcs12_with_password_and_ip(self): - """Tests connection using a password-protected PKCS12 file.""" - url = f"https://{IP_ADDRESS}:{PORT}" - - response = requests_pkcs12.get( - url, - pkcs12_filename=CLIENT_P12_WITH_PWD, - pkcs12_password=P12_PASSWORD, - verify=ROOT_CA_PEM, - timeout=10, - ) - self.assertEqual(response.status_code, 200) - - def test_requests_pkcs12_without_password_and_hostname(self): - """Tests connection using a PKCS12 file with an empty password.""" - url = f"https://{HOST}:{PORT}" - - response = requests_pkcs12.get( - url, - pkcs12_filename=CLIENT_P12_NO_PWD, - pkcs12_password="", - verify=ROOT_CA_PEM, - timeout=10, - ) - self.assertEqual(response.status_code, 200) - - def test_requests_pkcs12_without_password_and_ip(self): - """Tests connection using a PKCS12 file with an empty password.""" - url = f"https://{IP_ADDRESS}:{PORT}" - - response = requests_pkcs12.get( - url, - pkcs12_filename=CLIENT_P12_NO_PWD, - pkcs12_password="", - verify=ROOT_CA_PEM, - timeout=10, - ) - self.assertEqual(response.status_code, 200) - - def test_requests_pkcs12_with_password_none_and_hostname(self): - """Tests connection using a PKCS12 file with None as password.""" - url = f"https://{HOST}:{PORT}" - - response = requests_pkcs12.get( - url, - pkcs12_filename=CLIENT_P12_NO_PWD, - pkcs12_password=None, - verify=ROOT_CA_PEM, - timeout=10, - ) - self.assertEqual(response.status_code, 200) - - def test_requests_pkcs12_with_password_none_and_ip(self): - """Tests connection using a PKCS12 file with None as password.""" - url = f"https://{IP_ADDRESS}:{PORT}" - - response = requests_pkcs12.get( - url, - pkcs12_filename=CLIENT_P12_NO_PWD, - pkcs12_password=None, - verify=ROOT_CA_PEM, - timeout=10, - ) - self.assertEqual(response.status_code, 200) - - def test_requests_pkcs12_without_cert_parameters_and_hostname(self): - """Tests requests_pkcs12 connection without PKCS12 file.""" - url = f"https://{HOST}:{PORT}" - - with self.assertRaises(requests.exceptions.SSLError) as cm: - requests_pkcs12.get(url, verify=ROOT_CA_PEM, timeout=10) - - exc = cm.exception - self.assertIn("alert certificate required", str(exc)) - - def test_requests_pkcs12_without_cert_parameters_and_ip(self): - """Tests requests_pkcs12 connection without PKCS12 file.""" - url = f"https://{IP_ADDRESS}:{PORT}" - - with self.assertRaises(requests.exceptions.SSLError) as cm: - requests_pkcs12.get(url, verify=ROOT_CA_PEM, timeout=10) - - exc = cm.exception - self.assertIn("alert certificate required", str(exc)) - - def test_pkcs12_adapter_hostname(self): - """Tests connection using Pkcs12Adapter with PKCS12 file and password.""" - url = f"https://{HOST}:{PORT}" - client = requests.Session() - client.mount( - url, - requests_pkcs12.Pkcs12Adapter( - pkcs12_filename=CLIENT_P12_WITH_PWD, pkcs12_password=P12_PASSWORD - ), - ) - response = client.get(url, verify=ROOT_CA_PEM, timeout=10) - self.assertEqual(response.status_code, 200) - - def test_pkcs12_adapter_ip(self): - """Tests connection using Pkcs12Adapter with PKCS12 file and password.""" - url = f"https://{IP_ADDRESS}:{PORT}" - client = requests.Session() - client.mount( - url, - requests_pkcs12.Pkcs12Adapter( - pkcs12_filename=CLIENT_P12_WITH_PWD, pkcs12_password=P12_PASSWORD - ), - ) - response = client.get(url, verify=ROOT_CA_PEM, timeout=10) - self.assertEqual(response.status_code, 200) - - -if __name__ == '__main__': - unittest.main()