Skip to content

load Windows certificate store in set_default_verify_paths #1425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/OpenSSL/SSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -48,6 +52,7 @@
_EllipticCurve,
_PassphraseHelper,
_PrivateKey,
load_certificate,
)

__all__ = [
Expand Down Expand Up @@ -1013,7 +1018,6 @@ def set_default_verify_paths(self) -> None:
* macOS will only load certificates using this method if the user has
the ``[email protected]`` `Homebrew <https://brew.sh>`_ 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.
Expand All @@ -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
Expand Down
26 changes: 26 additions & 0 deletions tests/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading