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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/OpenSSL/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"X509Req",
"X509",
"X509StoreFlags",
"X509StorePurposes",
"X509Store",
"X509StoreContextError",
"X509StoreContext",
Expand Down Expand Up @@ -1583,6 +1584,28 @@ class X509StoreFlags:
CHECK_SS_SIGNATURE = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE


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 @@ -1687,6 +1710,22 @@ def set_time(self, vfy_time):
)
_openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0)

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

.. versionadded:: 22.1.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
Expand Down
28 changes: 28 additions & 0 deletions tests/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from OpenSSL.crypto import (
X509Store,
X509StoreFlags,
X509StorePurposes,
X509StoreContext,
X509StoreContextError,
)
Expand Down Expand Up @@ -3528,6 +3529,7 @@ class TestCRL:
intermediate_server_key = load_privatekey(
FILETYPE_PEM, intermediate_server_key_pem
)
server_cert = load_certificate(FILETYPE_PEM, server_cert_pem)

def test_construction(self):
"""
Expand Down Expand Up @@ -3835,6 +3837,32 @@ def test_verify_with_revoked(self):
store_ctx.verify_certificate()
assert err.value.args[0][2] == "certificate revoked"

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
Expand Down