From a2ffa8e19cebc5c9c7e61176c27ac5c0099902ec Mon Sep 17 00:00:00 2001 From: David Adam Date: Sat, 12 Apr 2025 09:01:16 +0800 Subject: [PATCH] load Windows certificate store in set_default_verify_paths Uses the standard library ssl module to enumerate system certificates. --- src/OpenSSL/SSL.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- tests/test_ssl.py | 26 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index 0cde0b26..7ad985ee 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -12,6 +12,9 @@ from typing import Any, Callable, Optional, TypeVar from weakref import WeakValueDictionary +if platform == "win32": + import ssl + from cryptography import x509 from cryptography.hazmat.primitives.asymmetric import ec @@ -40,6 +43,7 @@ text_to_bytes_and_warn as _text_to_bytes_and_warn, ) from OpenSSL.crypto import ( + FILETYPE_ASN1, FILETYPE_PEM, X509, PKey, @@ -48,6 +52,7 @@ _EllipticCurve, _PassphraseHelper, _PrivateKey, + load_certificate, ) __all__ = [ @@ -1013,7 +1018,6 @@ def set_default_verify_paths(self) -> None: * macOS will only load certificates using this method if the user has the ``openssl@1.1`` `Homebrew `_ formula installed in the default location. - * Windows will not work. * manylinux cryptography wheels will work on most common Linux distributions in pyOpenSSL 17.1.0 and above. pyOpenSSL detects the manylinux wheel and attempts to load roots via a fallback path. @@ -1032,6 +1036,46 @@ def set_default_verify_paths(self) -> None: # we won't try to do anything else because the user has set the path # themselves. if not self._check_env_vars_set("SSL_CERT_DIR", "SSL_CERT_FILE"): + # On Windows, fallback to the system store + if platform == "win32": + store = self.get_cert_store() + if store: + # "ROOT" is the "Trusted Root Certification Authorities", + # "CA" is the "Intermediate Certification Authorities" + # The Python SSL module loads both, so do the same + for store_name in ("ROOT", "CA"): + for ( + cert_bytes, + encoding_type, + trust, + # mypy does not like that this attribute does not + # exist except on win32, even though it is guarded + # above + ) in ssl.enum_certificates(store_name): # type: ignore[attr-defined] + if encoding_type == "x509_asn": + # Must be trusted for all purposes (True) or + # for server authentication + if ( + trust is True + or "1.3.6.1.5.5.7.3.1" in trust + ): + try: + store.add_cert( + load_certificate( + FILETYPE_ASN1, cert_bytes + ) + ) + except Error: + warnings.warn( + "Unable to load system " + "certificate: {e!s}" + ) + else: + warnings.warn( + "Unrecognised certificate type ({type}) " + "when importing from system certificate " + "store." + ) default_dir = _ffi.string(_lib.X509_get_default_cert_dir()) default_file = _ffi.string(_lib.X509_get_default_cert_file()) # Now we check to see if the default_dir and default_file are set diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 12ca4af6..c90b7328 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -1260,6 +1260,32 @@ def test_fallback_default_verify_paths( num = _lib.sk_X509_OBJECT_num(sk_obj) assert num != 0 + @pytest.mark.skipif( + not sys.platform == "win32", + reason=( + "Loading certificates from the Windows store only works on Windows" + ), + ) + def test_default_verify_paths_windows_cert_store( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + """ + Test that we load certificates successfully on Windows from the system + stores. To do this we disable SSL_CTX_SET_default_verify_paths so that + it won't load any other certificates. + """ + context = Context(SSLv23_METHOD) + monkeypatch.setattr( + _lib, "SSL_CTX_set_default_verify_paths", lambda x: 1 + ) + context.set_default_verify_paths() + store = context.get_cert_store() + assert store is not None + sk_obj = _lib.X509_STORE_get0_objects(store._store) + assert sk_obj != _ffi.NULL + num = _lib.sk_X509_OBJECT_num(sk_obj) + assert num != 0 + def test_check_env_vars(self, monkeypatch: pytest.MonkeyPatch) -> None: """ Test that we return True/False appropriately if the env vars are set.