Skip to content

Commit

Permalink
Expose set_purpose on X509Store to allow verify_certificate with purpose
Browse files Browse the repository at this point in the history
Signed-off-by: Arne Schwabe <[email protected]>
  • Loading branch information
schwabe committed Jan 14, 2025
1 parent 787f176 commit 4ce2d29
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
41 changes: 41 additions & 0 deletions src/OpenSSL/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def deprecated(msg: str, **kwargs: object) -> Callable[[_T], _T]:
"X509StoreContext",
"X509StoreContextError",
"X509StoreFlags",
"X509StorePurposes",
"dump_certificate",
"dump_certificate_request",
"dump_privatekey",
Expand Down Expand Up @@ -1709,6 +1710,28 @@ class X509StoreFlags:
PARTIAL_CHAIN: int = _lib.X509_V_FLAG_PARTIAL_CHAIN


class X509StorePurposes:
"""
Flags for X509 verification, used to change the behavior of
: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 @@ -1832,6 +1855,24 @@ def load_locations(
cafile: StrOrBytesPath | None,
capath: StrOrBytesPath | None = None,
) -> None:

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.
"""

param = _lib.X509_VERIFY_PARAM_new()
Param = _ffi.gc(param, _lib.X509_VERIFY_PARAM_free)
_lib.X509_VERIFY_PARAM_set_purpose(param, purpose)
_openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0)

def load_locations(self, cafile, capath=None):
"""
Let X509Store know where we can find trusted certificates for the
certificate chain. Note that the certificates have to be in PEM
Expand Down
30 changes: 29 additions & 1 deletion tests/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
X509StoreContext,
X509StoreContextError,
X509StoreFlags,
X509StorePurposes,
_EllipticCurve,
_Key,
_PrivateKey,
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(X509StorePurposes.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(X509StorePurposes.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][2] == "unsupported certificate purpose"

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

0 comments on commit 4ce2d29

Please sign in to comment.