Skip to content
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

Expose set_purpose on X509Store to allow verify_certificate with purpose #1090

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
35 changes: 35 additions & 0 deletions src/OpenSSL/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def deprecated(msg: str, **kwargs: object) -> Callable[[_T], _T]:
"X509Extension",
"X509Name",
"X509Req",
"X509Purpose",
"X509Store",
"X509StoreContext",
"X509StoreContextError",
Expand Down Expand Up @@ -1709,6 +1710,28 @@ class X509StoreFlags:
PARTIAL_CHAIN: int = _lib.X509_V_FLAG_PARTIAL_CHAIN


class X509Purpose:
"""
Enumeration of X509 purposes, e.g. used to set the purpose of a
:class:`X509Store`.

See `OpenSSL check purpose`_ for details.

.. _OpenSSL check purpose:
https://www.openssl.org/docs/manmaster/man3/X509_check_purpose.html
"""

X509_PURPOSE_SSL_CLIENT = _lib.X509_PURPOSE_SSL_CLIENT
X509_PURPOSE_SSL_SERVER = _lib.X509_PURPOSE_SSL_SERVER
X509_PURPOSE_NS_SSL_SERVER = _lib.X509_PURPOSE_NS_SSL_SERVER
X509_PURPOSE_SMIME_SIGN = _lib.X509_PURPOSE_SMIME_SIGN
X509_PURPOSE_SMIME_ENCRYPT = _lib.X509_PURPOSE_SMIME_ENCRYPT
X509_PURPOSE_CRL_SIGN = _lib.X509_PURPOSE_CRL_SIGN
X509_PURPOSE_ANY = _lib.X509_PURPOSE_ANY
X509_PURPOSE_OCSP_HELPER = _lib.X509_PURPOSE_OCSP_HELPER
X509_PURPOSE_TIMESTAMP_SIGN = _lib.X509_PURPOSE_TIMESTAMP_SIGN


class X509Store:
"""
An X.509 store.
Expand Down Expand Up @@ -1827,6 +1850,18 @@ def set_time(self, vfy_time: datetime.datetime) -> None:
)
_openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0)

def set_purpose(self, purpose):
"""
Set purpose of this store.

.. versionadded:: 26.0.0

:param int flags: The verification flags to set on this store.
See :class:`X509StorePurposes` for available constants.
:return: ``None`` if the verification flags were successfully set.
"""
_openssl_assert(_lib.X509_STORE_set_purpose(self._store, purpose) != 0)

def load_locations(
self,
cafile: StrOrBytesPath | None,
Expand Down
30 changes: 29 additions & 1 deletion tests/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
PKey,
X509Extension,
X509Name,
X509Purpose,
X509Req,
X509Store,
X509StoreContext,
Expand Down Expand Up @@ -3007,6 +3008,7 @@ class TestCRL:
intermediate_server_key = load_privatekey(
FILETYPE_PEM, intermediate_server_key_pem
)
server_cert = load_certificate(FILETYPE_PEM, server_cert_pem)

@staticmethod
def _make_test_crl_cryptography(
Expand Down Expand Up @@ -3069,7 +3071,33 @@ def test_verify_with_revoked(self) -> None:
store_ctx.verify_certificate()
assert str(err.value) == "certificate revoked"

def test_verify_with_missing_crl(self) -> None:
def test_verify_with_correct_purpose(self):
store = X509Store()
store.add_cert(self.root_cert)
store.add_cert(self.intermediate_cert)
store.set_purpose(X509Purpose.X509_PURPOSE_SSL_SERVER)

store_ctx = X509StoreContext(store, self.server_cert)
store_ctx.verify_certificate()

# The intermediate server certificate has no EKU and so it is fit
# for any purpose
store_ctx = X509StoreContext(store, self.intermediate_server_cert)
store_ctx.verify_certificate()

def test_verify_with_incorrect_purpose(self):
store = X509Store()
store.add_cert(self.root_cert)
store.add_cert(self.intermediate_cert)
store.set_purpose(X509Purpose.X509_PURPOSE_SSL_CLIENT)

store_ctx = X509StoreContext(store, self.server_cert)
with pytest.raises(X509StoreContextError) as err:
store_ctx.verify_certificate()

assert err.value.args[0] == "unsupported certificate purpose"

def test_verify_with_missing_crl(self):
"""
`verify_certificate` raises error when an intermediate certificate's
CRL is missing.
Expand Down
Loading