diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 8d247425d..09c1b9ccd 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -33,6 +33,7 @@ "X509Req", "X509", "X509StoreFlags", + "X509StorePurposes", "X509Store", "X509StoreContextError", "X509StoreContext", @@ -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. @@ -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 diff --git a/tests/test_crypto.py b/tests/test_crypto.py index ca2a17a61..7308f979e 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -26,6 +26,7 @@ from OpenSSL.crypto import ( X509Store, X509StoreFlags, + X509StorePurposes, X509StoreContext, X509StoreContextError, ) @@ -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): """ @@ -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