From f38ea826687504262e03efd495d6381ccb4bd19e Mon Sep 17 00:00:00 2001 From: Stephen Holsapple Date: Fri, 3 Jun 2016 18:19:30 -0700 Subject: [PATCH 01/18] Enable use of CRL (and more) in verify context. --- src/OpenSSL/crypto.py | 212 ++++++++++++++++++++++++++++++++++-------- tests/test_crypto.py | 76 +++++++++++++++ 2 files changed, 248 insertions(+), 40 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 21bdadd7a..c0286164f 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1427,8 +1427,32 @@ def get_extension(self, index): class X509Store(object): """ - An X509 certificate store. - """ + An X.509 store. + + An X.509 store is used to describe a context in which to verify a + certificate. A description of a context may include a set of certificates + to trust, a set of certificate revocation lists, verification flags and + more. + + An X.509 store, being only a description, cannot be used by itself to verify + a certificate. To carry out the actual verification process, see + :py:class:`X509StoreContext`. + """ + + CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK + CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL + IGNORE_CRITICAL = _lib.X509_V_FLAG_IGNORE_CRITICAL + X509_STRICT = _lib.X509_V_FLAG_X509_STRICT + ALLOW_PROXY_CERTS = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS + POLICY_CHECK = _lib.X509_V_FLAG_POLICY_CHECK + EXPLICIT_POLICY = _lib.X509_V_FLAG_EXPLICIT_POLICY + # FLAG_INHIBIT_ANY = _lib.X509_V_FLAG_FLAG_INHIBIT_ANY + INHIBIT_MAP = _lib.X509_V_FLAG_INHIBIT_MAP + NOTIFY_POLICY = _lib.X509_V_FLAG_NOTIFY_POLICY + # USE_DELTAS = _lib.X509_V_FLAG_USE_DELTAS + CHECK_SS_SIGNATURE = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE + CB_ISSUER_CHECK = _lib.X509_V_FLAG_CB_ISSUER_CHECK + # NO_ALT_CHAINS = _lib.X509_V_FLAG_NO_ALT_CHAINS def __init__(self): store = _lib.X509_STORE_new() @@ -1436,9 +1460,10 @@ def __init__(self): def add_cert(self, cert): """ - Adds the certificate :py:data:`cert` to this store. + Adds a trusted certificate to this store. - This is the Python equivalent of OpenSSL's ``X509_STORE_add_cert``. + Adding a certificate with this method adds this certificate as a + *trusted* certificate. :param X509 cert: The certificate to add to this store. :raises TypeError: If the certificate is not an :py:class:`X509`. @@ -1452,6 +1477,44 @@ def add_cert(self, cert): if not result: _raise_current_error() + def add_crl(self, crl): + """ + Add a certificate revocation list to this store. + + The certificate revocation lists added to a store will only be used if + the associated flags are configured to check certificate revocation + lists. + + .. versionadded:: 0.17 + + :param CRL crl: The certificate revocation list to add to this store. + :return: :py:data:`None` if the certificate revocation list was added successfully. + """ + _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl._crl) != 0) + + def set_flags(self, flags): + """ + Set verification flags to this store. + + Verification flags can be combined by oring them together. + + .. note:: + + Setting a verification flag sometimes requires clients to add + additional information to the store, otherwise a suitable error will + be raised. + + For example, in setting flags to enable CRL checking a + suitable CRL must be added to the store otherwise an error will be + raised. + + .. versionadded:: 0.17 + + :param int flags: The verification flags to set on this store. + :return: :py:data:`None` if the verification flags were successfully set. + """ + _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0) + X509StoreType = X509Store @@ -1474,29 +1537,19 @@ class X509StoreContext(object): """ An X.509 store context. - An :py:class:`X509StoreContext` is used to define some of the criteria for - certificate verification. The information encapsulated in this object - includes, but is not limited to, a set of trusted certificates, - verification parameters, and revoked certificates. - - .. note:: - - Currently, one can only set the trusted certificates on an - :py:class:`X509StoreContext`. Future versions of pyOpenSSL will expose - verification parameters and certificate revocation lists. + An X.509 store context is used to carry out the actual verification process + of a certificate in a described context. For describing such a context, see + :py:class:`X509Store`. :ivar _store_ctx: The underlying X509_STORE_CTX structure used by this instance. It is dynamically allocated and automatically garbage collected. - :ivar _store: See the ``store`` ``__init__`` parameter. - :ivar _cert: See the ``certificate`` ``__init__`` parameter. - :param X509Store store: The certificates which will be trusted for the purposes of any verifications. - :param X509 certificate: The certificate to be verified. + """ def __init__(self, store, certificate): @@ -1552,12 +1605,12 @@ def _exception_from_context(self): def set_store(self, store): """ - Set the context's trust store. + Set the context's X.509 store. .. versionadded:: 0.15 - :param X509Store store: The certificates which will be trusted for the - purposes of any *future* verifications. + :param X509Store store: The store description which will be used for + the purposes of any *future* verifications. """ self._store = store @@ -1567,8 +1620,6 @@ def verify_certificate(self): .. versionadded:: 0.15 - :param store_ctx: The :py:class:`X509StoreContext` to verify. - :raises X509StoreContextError: If an error occurred when validating a certificate in the context. Sets ``certificate`` attribute to indicate which certificate caused the error. @@ -1902,9 +1953,6 @@ class CRL(object): """ def __init__(self): - """ - Create a new empty certificate revocation list. - """ crl = _lib.X509_CRL_new() self._crl = _ffi.gc(crl, _lib.X509_CRL_free) @@ -1937,9 +1985,7 @@ def add_revoked(self, revoked): means it's okay to mutate it after adding: it won't affect this CRL. - :param revoked: The new revocation. - :type revoked: :class:`Revoked` - + :param Revoked revoked: The new revocation. :return: :py:const:`None` """ copy = _lib.Cryptography_X509_REVOKED_dup(revoked._revoked) @@ -1952,27 +1998,113 @@ def add_revoked(self, revoked): # TODO: This is untested. _raise_current_error() - def export(self, cert, key, type=FILETYPE_PEM, days=100, - digest=_UNSPECIFIED): + def get_issuer(self): """ - Export a CRL as a string. + Get the CRL's issuer. - :param cert: The certificate used to sign the CRL. - :type cert: :py:class:`X509` + .. versionadded:: 0.17 - :param key: The key used to sign the CRL. - :type key: :py:class:`PKey` + :return: :py:class:`X509Name` + """ + _issuer = _lib.X509_NAME_dup(_lib.X509_CRL_get_issuer(self._crl)) + _openssl_assert(_issuer != _ffi.NULL) + _issuer = _ffi.gc(_issuer, _lib.X509_NAME_free) + issuer = X509Name.__new__(X509Name) + issuer._name = _issuer + return issuer - :param type: The export format, either :py:data:`FILETYPE_PEM`, - :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`. + def set_version(self, version): + """ + Set the CRL version. - :param int days: The number of days until the next update of this CRL. + .. versionadded:: 0.17 + + :param int version: The version of the CRL. + :return: :py:const:`None` + """ + _openssl_assert(_lib.X509_CRL_set_version(self._crl, version) != 0) + + def _set_boundary_time(self, which, when): + return _set_asn1_time(which(self._crl), when) + def set_lastUpdate(self, when): + """ + Set when the CRL was last updated. + + The timestamp is formatted as an ASN.1 GENERALIZEDTIME:: + + YYYYMMDDhhmmssZ + YYYYMMDDhhmmss+hhmm + YYYYMMDDhhmmss-hhmm + + .. versionadded:: 0.17 + + :param bytes when: A timestamp string. + :return: :py:const:`None` + """ + return self._set_boundary_time(_lib.X509_CRL_get_lastUpdate, when) + + def set_nextUpdate(self, when): + """ + Set when the CRL will next be udpated. + + The timestamp is formatted as an ASN.1 GENERALIZEDTIME:: + + YYYYMMDDhhmmssZ + YYYYMMDDhhmmss+hhmm + YYYYMMDDhhmmss-hhmm + + .. versionadded:: 0.17 + + :param bytes when: A timestamp string. + :return: :py:const:`None` + """ + return self._set_boundary_time(_lib.X509_CRL_get_nextUpdate, when) + + def sign(self, issuer_cert, issuer_key, digest='sha1'): + """ + Sign the CRL. + + Signing a CRL enables clients to associate the CRL itself with an + issuer. Before a CRL is meaningful to other OpenSSL functions, it must + be signed by an issuer. + + This method implicitly sets the issuer's name based on the issuer + certificate and private key used to sign the CRL. + + .. versionadded:: 0.17 + + :param X509 issuer_cert: The issuer's certificate. + :param PKey issuer_key: The issuer's private key. + :param str digest: The digest method to sign the CRL with. + """ + digest_obj = _lib.EVP_get_digestbyname(digest) + _openssl_assert(digest_obj != _ffi.NULL) + _lib.X509_CRL_set_issuer_name(self._crl, _lib.X509_get_subject_name(issuer_cert._x509)) + _lib.X509_CRL_sort(self._crl) + sign_result = _lib.X509_CRL_sign(self._crl, issuer_key._pkey, digest_obj) + _openssl_assert(sign_result != 0) + + def export(self, cert, key, type=FILETYPE_PEM, days=100, + digest=_UNSPECIFIED): + """ + Export the CRL as a string. + + :param X509 cert: The certificate used to sign the CRL. + :param PKey key: The key used to sign the CRL. + :param int type: The export format, either :py:data:`FILETYPE_PEM`, + :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`. + :param int days: The number of days until the next update of this CRL. :param bytes digest: The name of the message digest to use (eg ``b"sha1"``). - :return: :py:data:`bytes` """ + + # TODO: fix this function to use functionality added in version 0.16. + # Doing this without changing the public API is tricky. Checking if + # lastUpdate, nextUpdate, issuer or signing has happened is hard to do + # without generating a segmentation fault. + if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") if not isinstance(key, PKey): diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 136af0a04..7d3b4f473 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -3109,6 +3109,14 @@ class CRLTests(TestCase): cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) + root_key = load_privatekey(FILETYPE_PEM, root_key_pem) + intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) + intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem) + intermediate_server_cert = load_certificate( + FILETYPE_PEM, intermediate_server_cert_pem) + intermediate_server_key = load_privatekey(FILETYPE_PEM, intermediate_server_key_pem) + def test_construction(self): """ Confirm we can create :py:obj:`OpenSSL.crypto.CRL`. Check @@ -3389,6 +3397,14 @@ def test_load_crl_bad_data(self): """ self.assertRaises(Error, load_crl, FILETYPE_PEM, b"hello, world") + def test_get_issuer(self): + """ + Load a known CRL and inspect its issuer's common name. + """ + crl = load_crl(FILETYPE_PEM, crlData) + self.assertTrue(isinstance(crl.get_issuer(), X509Name)) + self.assertEqual(crl.get_issuer().CN, 'Testing Root CA') + def test_dump_crl(self): """ The dumped CRL matches the original input. @@ -3397,15 +3413,75 @@ def test_dump_crl(self): buf = dump_crl(FILETYPE_PEM, crl) assert buf == crlData + def _make_test_crl(self, issuer_cert, issuer_key, certs=()): + """ + Create a CRL. + + :param list[X509] certs: A list of certificates to revoke. + :return: :py:class:`CRL` + """ + crl = CRL() + for cert in certs: + revoked = Revoked() + # FIXME: This string splicing is an unfortunate implementation detail + # that has been reported in + # https://github.com/pyca/pyopenssl/issues/258 + serial = hex(cert.get_serial_number())[2:].encode('utf-8') + revoked.set_serial(serial) + revoked.set_reason(b'unspecified') + revoked.set_rev_date(b'20140601000000Z') + crl.add_revoked(revoked) + crl.set_version(1) + crl.set_lastUpdate(b'20140601000000Z') + crl.set_nextUpdate(b'20180601000000Z') + crl.sign(issuer_cert, issuer_key, digest=b'sha512') + return crl + + def test_verify_with_revoked(self): + """ + :py:obj:`verify_certificate` raises error when an intermediate + certificate is revoked. + """ + store = X509Store() + store.add_cert(self.root_cert) + store.add_cert(self.intermediate_cert) + root_crl = self._make_test_crl(self.root_cert, self.root_key, certs=[self.intermediate_cert]) + intermediate_crl = self._make_test_crl(self.intermediate_cert, self.intermediate_key, certs=[]) + store.add_crl(root_crl) + store.add_crl(intermediate_crl) + store.set_flags(store.CRL_CHECK | store.CRL_CHECK_ALL) + store_ctx = X509StoreContext(store, self.intermediate_server_cert) + e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) + self.assertEqual(e.args[0][2], 'certificate revoked') + + def test_verify_with_missing_crl(self): + """ + :py:obj:`verify_certificate` raises error when an intermediate + certificate's CRL is missing. + """ + store = X509Store() + store.add_cert(self.root_cert) + store.add_cert(self.intermediate_cert) + root_crl = self._make_test_crl(self.root_cert, self.root_key, certs=[self.intermediate_cert]) + store.add_crl(root_crl) + store.set_flags(store.CRL_CHECK | store.CRL_CHECK_ALL) + store_ctx = X509StoreContext(store, self.intermediate_server_cert) + e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) + self.assertEqual(e.args[0][2], 'unable to get certificate CRL') + self.assertEqual(e.certificate.get_subject().CN, 'intermediate-service') + class X509StoreContextTests(TestCase): """ Tests for :py:obj:`OpenSSL.crypto.X509StoreContext`. """ root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) + root_key = load_privatekey(FILETYPE_PEM, root_key_pem) intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) + intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem) intermediate_server_cert = load_certificate( FILETYPE_PEM, intermediate_server_cert_pem) + intermediate_server_key = load_privatekey(FILETYPE_PEM, intermediate_server_key_pem) def test_valid(self): """ From 45d3447f609e1a64be0fe05fe5cc9423200b49bb Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Fri, 3 Jun 2016 18:36:05 -0700 Subject: [PATCH 02/18] flake8 fixes. --- src/OpenSSL/crypto.py | 17 ++++++++++------- tests/test_crypto.py | 28 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index c0286164f..b732c1560 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1434,8 +1434,8 @@ class X509Store(object): to trust, a set of certificate revocation lists, verification flags and more. - An X.509 store, being only a description, cannot be used by itself to verify - a certificate. To carry out the actual verification process, see + An X.509 store, being only a description, cannot be used by itself to + verify a certificate. To carry out the actual verification process, see :py:class:`X509StoreContext`. """ @@ -1488,7 +1488,8 @@ def add_crl(self, crl): .. versionadded:: 0.17 :param CRL crl: The certificate revocation list to add to this store. - :return: :py:data:`None` if the certificate revocation list was added successfully. + :return: :py:data:`None` if the certificate revocation list was added + successfully. """ _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl._crl) != 0) @@ -1511,7 +1512,8 @@ def set_flags(self, flags): .. versionadded:: 0.17 :param int flags: The verification flags to set on this store. - :return: :py:data:`None` if the verification flags were successfully set. + :return: :py:data:`None` if the verification flags were + successfully set. """ _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0) @@ -2080,10 +2082,11 @@ def sign(self, issuer_cert, issuer_key, digest='sha1'): """ digest_obj = _lib.EVP_get_digestbyname(digest) _openssl_assert(digest_obj != _ffi.NULL) - _lib.X509_CRL_set_issuer_name(self._crl, _lib.X509_get_subject_name(issuer_cert._x509)) + _lib.X509_CRL_set_issuer_name( + self._crl, _lib.X509_get_subject_name(issuer_cert._x509)) _lib.X509_CRL_sort(self._crl) - sign_result = _lib.X509_CRL_sign(self._crl, issuer_key._pkey, digest_obj) - _openssl_assert(sign_result != 0) + result = _lib.X509_CRL_sign(self._crl, issuer_key._pkey, digest_obj) + _openssl_assert(result != 0) def export(self, cert, key, type=FILETYPE_PEM, days=100, digest=_UNSPECIFIED): diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 7d3b4f473..8f07bab0e 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -3115,7 +3115,8 @@ class CRLTests(TestCase): intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem) intermediate_server_cert = load_certificate( FILETYPE_PEM, intermediate_server_cert_pem) - intermediate_server_key = load_privatekey(FILETYPE_PEM, intermediate_server_key_pem) + intermediate_server_key = load_privatekey( + FILETYPE_PEM, intermediate_server_key_pem) def test_construction(self): """ @@ -3423,8 +3424,8 @@ def _make_test_crl(self, issuer_cert, issuer_key, certs=()): crl = CRL() for cert in certs: revoked = Revoked() - # FIXME: This string splicing is an unfortunate implementation detail - # that has been reported in + # FIXME: This string splicing is an unfortunate implementation + # detail that has been reported in # https://github.com/pyca/pyopenssl/issues/258 serial = hex(cert.get_serial_number())[2:].encode('utf-8') revoked.set_serial(serial) @@ -3445,13 +3446,16 @@ def test_verify_with_revoked(self): store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) - root_crl = self._make_test_crl(self.root_cert, self.root_key, certs=[self.intermediate_cert]) - intermediate_crl = self._make_test_crl(self.intermediate_cert, self.intermediate_key, certs=[]) + root_crl = self._make_test_crl( + self.root_cert, self.root_key, certs=[self.intermediate_cert]) + intermediate_crl = self._make_test_crl( + self.intermediate_cert, self.intermediate_key, certs=[]) store.add_crl(root_crl) store.add_crl(intermediate_crl) store.set_flags(store.CRL_CHECK | store.CRL_CHECK_ALL) store_ctx = X509StoreContext(store, self.intermediate_server_cert) - e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) + e = self.assertRaises( + X509StoreContextError, store_ctx.verify_certificate) self.assertEqual(e.args[0][2], 'certificate revoked') def test_verify_with_missing_crl(self): @@ -3462,13 +3466,16 @@ def test_verify_with_missing_crl(self): store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) - root_crl = self._make_test_crl(self.root_cert, self.root_key, certs=[self.intermediate_cert]) + root_crl = self._make_test_crl( + self.root_cert, self.root_key, certs=[self.intermediate_cert]) store.add_crl(root_crl) store.set_flags(store.CRL_CHECK | store.CRL_CHECK_ALL) store_ctx = X509StoreContext(store, self.intermediate_server_cert) - e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) + e = self.assertRaises( + X509StoreContextError, store_ctx.verify_certificate) self.assertEqual(e.args[0][2], 'unable to get certificate CRL') - self.assertEqual(e.certificate.get_subject().CN, 'intermediate-service') + self.assertEqual( + e.certificate.get_subject().CN, 'intermediate-service') class X509StoreContextTests(TestCase): @@ -3481,7 +3488,8 @@ class X509StoreContextTests(TestCase): intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem) intermediate_server_cert = load_certificate( FILETYPE_PEM, intermediate_server_cert_pem) - intermediate_server_key = load_privatekey(FILETYPE_PEM, intermediate_server_key_pem) + intermediate_server_key = load_privatekey( + FILETYPE_PEM, intermediate_server_key_pem) def test_valid(self): """ From a9830726b5fe0de5193544a40c96f1ad24e39b1f Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 13:03:31 -0700 Subject: [PATCH 03/18] Comments from Hynek & Paul. --- src/OpenSSL/crypto.py | 92 +++++++++++++++++++++---------------------- tests/test_crypto.py | 13 +++--- 2 files changed, 51 insertions(+), 54 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index b732c1560..29e11fa2c 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1436,7 +1436,7 @@ class X509Store(object): An X.509 store, being only a description, cannot be used by itself to verify a certificate. To carry out the actual verification process, see - :py:class:`X509StoreContext`. + :class:`X509StoreContext`. """ CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK @@ -1466,16 +1466,14 @@ def add_cert(self, cert): *trusted* certificate. :param X509 cert: The certificate to add to this store. - :raises TypeError: If the certificate is not an :py:class:`X509`. + :raises TypeError: If the certificate is not an :class:`X509`. :raises Error: If OpenSSL was unhappy with your certificate. - :return: :py:data:`None` if the certificate was added successfully. + :return: ``None`` if the certificate was added successfully. """ if not isinstance(cert, X509): raise TypeError() - result = _lib.X509_STORE_add_cert(self._store, cert._x509) - if not result: - _raise_current_error() + _openssl_assert(_lib.X509_STORE_add_cert(self._store, cert._x509) != 0) def add_crl(self, crl): """ @@ -1485,10 +1483,10 @@ def add_crl(self, crl): the associated flags are configured to check certificate revocation lists. - .. versionadded:: 0.17 + .. versionadded:: 16.1.0 :param CRL crl: The certificate revocation list to add to this store. - :return: :py:data:`None` if the certificate revocation list was added + :return: ``None`` if the certificate revocation list was added successfully. """ _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl._crl) != 0) @@ -1509,10 +1507,10 @@ def set_flags(self, flags): suitable CRL must be added to the store otherwise an error will be raised. - .. versionadded:: 0.17 + .. versionadded:: 16.1.0 :param int flags: The verification flags to set on this store. - :return: :py:data:`None` if the verification flags were + :return: ``None`` if the verification flags were successfully set. """ _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0) @@ -1541,7 +1539,7 @@ class X509StoreContext(object): An X.509 store context is used to carry out the actual verification process of a certificate in a described context. For describing such a context, see - :py:class:`X509Store`. + :class:`X509Store`. :ivar _store_ctx: The underlying X509_STORE_CTX structure used by this instance. It is dynamically allocated and automatically garbage @@ -1561,7 +1559,7 @@ def __init__(self, store, certificate): self._cert = certificate # Make the store context available for use after instantiating this # class by initializing it now. Per testing, subsequent calls to - # :py:meth:`_init` have no adverse affect. + # :meth:`_init` have no adverse affect. self._init() def _init(self): @@ -1578,8 +1576,7 @@ def _cleanup(self): """ Internally cleans up the store context. - The store context can then be reused with a new call to - :py:meth:`_init`. + The store context can then be reused with a new call to :meth:`_init`. """ _lib.X509_STORE_CTX_cleanup(self._store_ctx) @@ -1627,7 +1624,7 @@ def verify_certificate(self): indicate which certificate caused the error. """ # Always re-initialize the store context in case - # :py:meth:`verify_certificate` is called multiple times. + # :meth:`verify_certificate` is called multiple times. self._init() ret = _lib.X509_verify_cert(self._store_ctx) self._cleanup() @@ -1642,7 +1639,7 @@ def load_certificate(type, buffer): :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the certificate is stored in - :type buffer: :py:class:`bytes` + :type buffer: :class:`bytes` :return: The X509 object """ @@ -1731,7 +1728,7 @@ def dump_privatekey(type, pkey, cipher=None, passphrase=None): the passphrase to use, or a callback for providing the passphrase. :return: The buffer with the dumped key in - :rtype: :py:data:`bytes` + :rtype: :data:`bytes` """ bio = _new_mem_buf() @@ -1800,9 +1797,9 @@ def set_serial(self, hex_str): ASCII. :param hex_str: The new serial number. - :type hex_str: :py:class:`bytes` + :type hex_str: :class:`bytes` - :return: :py:const:`None` + :return: :const:`None` """ bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free) bignum_ptr = _ffi.new("BIGNUM**") @@ -1824,7 +1821,7 @@ def get_serial(self): ASCII. :return: The serial number. - :rtype: :py:class:`bytes` + :rtype: :class:`bytes` """ bio = _new_mem_buf() @@ -1847,16 +1844,16 @@ def set_reason(self, reason): """ Set the reason of this revocation. - If :py:data:`reason` is :py:const:`None`, delete the reason instead. + If :data:`reason` is :const:`None`, delete the reason instead. :param reason: The reason string. - :type reason: :py:class:`bytes` or :py:class:`NoneType` + :type reason: :class:`bytes` or :class:`NoneType` - :return: :py:const:`None` + :return: :const:`None` .. seealso:: - :py:meth:`all_reasons`, which gives you a list of all supported + :meth:`all_reasons`, which gives you a list of all supported reasons which you might pass to this method. """ if reason is None: @@ -1890,12 +1887,12 @@ def get_reason(self): """ Get the reason of this revocation. - :return: The reason, or :py:const:`None` if there is none. - :rtype: :py:class:`bytes` or :py:class:`NoneType` + :return: The reason, or :const:`None` if there is none. + :rtype: :class:`bytes` or :class:`NoneType` .. seealso:: - :py:meth:`all_reasons`, which gives you a list of all supported + :meth:`all_reasons`, which gives you a list of all supported reasons this method might return. """ for i in range(_lib.X509_REVOKED_get_ext_count(self._revoked)): @@ -1923,7 +1920,7 @@ def all_reasons(self): strings. :return: A list of reason strings. - :rtype: :py:class:`list` of :py:class:`bytes` + :rtype: :class:`list` of :class:`bytes` """ return self._crl_reasons[:] @@ -1932,8 +1929,8 @@ def set_rev_date(self, when): Set the revocation timestamp. :param when: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME. - :type when: :py:class:`bytes` - :return: :py:const:`None` + :type when: :class:`bytes` + :return: :const:`None` """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) return _set_asn1_time(dt, when) @@ -1943,7 +1940,7 @@ def get_rev_date(self): Get the revocation timestamp. :return: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME. - :rtype: :py:class:`bytes` + :rtype: :class:`bytes` """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) return _get_asn1_time(dt) @@ -1955,6 +1952,9 @@ class CRL(object): """ def __init__(self): + """ + Create a new empty certificate revocation list. + """ crl = _lib.X509_CRL_new() self._crl = _ffi.gc(crl, _lib.X509_CRL_free) @@ -1966,7 +1966,7 @@ def get_revoked(self): That means it's okay to mutate them: it won't affect this CRL. :return: The revocations in this CRL. - :rtype: :py:class:`tuple` of :py:class:`Revocation` + :rtype: :class:`tuple` of :class:`Revocation` """ results = [] revoked_stack = _lib.X509_CRL_get_REVOKED(self._crl) @@ -1988,7 +1988,7 @@ def add_revoked(self, revoked): this CRL. :param Revoked revoked: The new revocation. - :return: :py:const:`None` + :return: :const:`None` """ copy = _lib.Cryptography_X509_REVOKED_dup(revoked._revoked) if copy == _ffi.NULL: @@ -2004,9 +2004,9 @@ def get_issuer(self): """ Get the CRL's issuer. - .. versionadded:: 0.17 + .. versionadded:: 16.1.0 - :return: :py:class:`X509Name` + :return: :class:`X509Name` """ _issuer = _lib.X509_NAME_dup(_lib.X509_CRL_get_issuer(self._crl)) _openssl_assert(_issuer != _ffi.NULL) @@ -2019,10 +2019,10 @@ def set_version(self, version): """ Set the CRL version. - .. versionadded:: 0.17 + .. versionadded:: 16.1.0 :param int version: The version of the CRL. - :return: :py:const:`None` + :return: ``None`` """ _openssl_assert(_lib.X509_CRL_set_version(self._crl, version) != 0) @@ -2039,10 +2039,10 @@ def set_lastUpdate(self, when): YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm - .. versionadded:: 0.17 + .. versionadded:: 16.1.0 :param bytes when: A timestamp string. - :return: :py:const:`None` + :return: ``None` """ return self._set_boundary_time(_lib.X509_CRL_get_lastUpdate, when) @@ -2056,10 +2056,10 @@ def set_nextUpdate(self, when): YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm - .. versionadded:: 0.17 + .. versionadded:: 16.1.0 :param bytes when: A timestamp string. - :return: :py:const:`None` + :return: ``None`` """ return self._set_boundary_time(_lib.X509_CRL_get_nextUpdate, when) @@ -2074,7 +2074,7 @@ def sign(self, issuer_cert, issuer_key, digest='sha1'): This method implicitly sets the issuer's name based on the issuer certificate and private key used to sign the CRL. - .. versionadded:: 0.17 + .. versionadded:: 16.1.0 :param X509 issuer_cert: The issuer's certificate. :param PKey issuer_key: The issuer's private key. @@ -2095,15 +2095,15 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, :param X509 cert: The certificate used to sign the CRL. :param PKey key: The key used to sign the CRL. - :param int type: The export format, either :py:data:`FILETYPE_PEM`, - :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`. + :param int type: The export format, either :data:`FILETYPE_PEM`, + :data:`FILETYPE_ASN1`, or :data:`FILETYPE_TEXT`. :param int days: The number of days until the next update of this CRL. :param bytes digest: The name of the message digest to use (eg ``b"sha1"``). - :return: :py:data:`bytes` + :rtype: bytes """ - # TODO: fix this function to use functionality added in version 0.16. + # TODO: fix this function to use functionality added in version 16.1.0 # Doing this without changing the public API is tricky. Checking if # lastUpdate, nextUpdate, issuer or signing has happened is hard to do # without generating a segmentation fault. diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 8f07bab0e..13ea0adfe 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -3400,7 +3400,8 @@ def test_load_crl_bad_data(self): def test_get_issuer(self): """ - Load a known CRL and inspect its issuer's common name. + Load a known CRL and assert its issuer's common name is + what we expect from the encoded crlData string. """ crl = load_crl(FILETYPE_PEM, crlData) self.assertTrue(isinstance(crl.get_issuer(), X509Name)) @@ -3419,7 +3420,7 @@ def _make_test_crl(self, issuer_cert, issuer_key, certs=()): Create a CRL. :param list[X509] certs: A list of certificates to revoke. - :return: :py:class:`CRL` + :rtype: CRL """ crl = CRL() for cert in certs: @@ -3440,7 +3441,7 @@ def _make_test_crl(self, issuer_cert, issuer_key, certs=()): def test_verify_with_revoked(self): """ - :py:obj:`verify_certificate` raises error when an intermediate + :func:`verify_certificate` raises error when an intermediate certificate is revoked. """ store = X509Store() @@ -3460,7 +3461,7 @@ def test_verify_with_revoked(self): def test_verify_with_missing_crl(self): """ - :py:obj:`verify_certificate` raises error when an intermediate + :func:`verify_certificate` raises error when an intermediate certificate's CRL is missing. """ store = X509Store() @@ -3483,13 +3484,9 @@ class X509StoreContextTests(TestCase): Tests for :py:obj:`OpenSSL.crypto.X509StoreContext`. """ root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) - root_key = load_privatekey(FILETYPE_PEM, root_key_pem) intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) - intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem) intermediate_server_cert = load_certificate( FILETYPE_PEM, intermediate_server_cert_pem) - intermediate_server_key = load_privatekey( - FILETYPE_PEM, intermediate_server_key_pem) def test_valid(self): """ From 2dca3d98f27e0b272718a970c28cb97e193eaf56 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 13:18:24 -0700 Subject: [PATCH 04/18] More updates from Paul & Hynek. --- src/OpenSSL/crypto.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 29e11fa2c..3a015096c 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1439,6 +1439,10 @@ class X509Store(object): :class:`X509StoreContext`. """ + # I found that some of these flags were added in different versions + # of OpenSSL. How to conditionally add them if they're present? + # Do you have any suggestions on conditionally adding the flag if + # it is available in the underlying OpenSSL? CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL IGNORE_CRITICAL = _lib.X509_V_FLAG_IGNORE_CRITICAL @@ -1952,9 +1956,6 @@ class CRL(object): """ def __init__(self): - """ - Create a new empty certificate revocation list. - """ crl = _lib.X509_CRL_new() self._crl = _ffi.gc(crl, _lib.X509_CRL_free) @@ -2063,7 +2064,7 @@ def set_nextUpdate(self, when): """ return self._set_boundary_time(_lib.X509_CRL_get_nextUpdate, when) - def sign(self, issuer_cert, issuer_key, digest='sha1'): + def sign(self, issuer_cert, issuer_key, digest): """ Sign the CRL. From 5e4816bfb1a3ddf4bcf0fbcf781bddea3c7f75bd Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 13:29:23 -0700 Subject: [PATCH 05/18] Fix closing backtick. --- src/OpenSSL/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 3a015096c..7d8c44ee2 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -2043,7 +2043,7 @@ def set_lastUpdate(self, when): .. versionadded:: 16.1.0 :param bytes when: A timestamp string. - :return: ``None` + :return: ``None`` """ return self._set_boundary_time(_lib.X509_CRL_get_lastUpdate, when) From 2bc4bd82b9f0565c8fe23634906aae8c5198c0fd Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 14:53:45 -0700 Subject: [PATCH 06/18] Remove commented out constants. --- src/OpenSSL/crypto.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 7d8c44ee2..c7ff12613 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1439,10 +1439,6 @@ class X509Store(object): :class:`X509StoreContext`. """ - # I found that some of these flags were added in different versions - # of OpenSSL. How to conditionally add them if they're present? - # Do you have any suggestions on conditionally adding the flag if - # it is available in the underlying OpenSSL? CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL IGNORE_CRITICAL = _lib.X509_V_FLAG_IGNORE_CRITICAL @@ -1450,13 +1446,10 @@ class X509Store(object): ALLOW_PROXY_CERTS = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS POLICY_CHECK = _lib.X509_V_FLAG_POLICY_CHECK EXPLICIT_POLICY = _lib.X509_V_FLAG_EXPLICIT_POLICY - # FLAG_INHIBIT_ANY = _lib.X509_V_FLAG_FLAG_INHIBIT_ANY INHIBIT_MAP = _lib.X509_V_FLAG_INHIBIT_MAP NOTIFY_POLICY = _lib.X509_V_FLAG_NOTIFY_POLICY - # USE_DELTAS = _lib.X509_V_FLAG_USE_DELTAS CHECK_SS_SIGNATURE = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE CB_ISSUER_CHECK = _lib.X509_V_FLAG_CB_ISSUER_CHECK - # NO_ALT_CHAINS = _lib.X509_V_FLAG_NO_ALT_CHAINS def __init__(self): store = _lib.X509_STORE_new() From e3421c71b2b3a05a4edde4541708e51a48b01354 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 15:46:38 -0700 Subject: [PATCH 07/18] More doc updates. Changelog update. --- CHANGELOG.rst | 3 ++- src/OpenSSL/crypto.py | 16 +++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aeec693d7..b6401aac6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,7 +23,8 @@ Deprecations: Changes: ^^^^^^^^ -*none* +- Enable use of CRL (and more) in verify context. + `#483 `_ ---- diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index c7ff12613..2cf1a81d1 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1636,7 +1636,7 @@ def load_certificate(type, buffer): :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the certificate is stored in - :type buffer: :class:`bytes` + :type buffer: bytes :return: The X509 object """ @@ -1725,7 +1725,7 @@ def dump_privatekey(type, pkey, cipher=None, passphrase=None): the passphrase to use, or a callback for providing the passphrase. :return: The buffer with the dumped key in - :rtype: :data:`bytes` + :rtype: bytes """ bio = _new_mem_buf() @@ -1793,10 +1793,9 @@ def set_serial(self, hex_str): The serial number is formatted as a hexadecimal number encoded in ASCII. - :param hex_str: The new serial number. - :type hex_str: :class:`bytes` + :param bytes hex_str: The new serial number. - :return: :const:`None` + :return: ``None`` """ bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free) bignum_ptr = _ffi.new("BIGNUM**") @@ -1925,8 +1924,7 @@ def set_rev_date(self, when): """ Set the revocation timestamp. - :param when: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME. - :type when: :class:`bytes` + :param bytes when: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME. :return: :const:`None` """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) @@ -2000,7 +1998,7 @@ def get_issuer(self): .. versionadded:: 16.1.0 - :return: :class:`X509Name` + :rtype: X509Name """ _issuer = _lib.X509_NAME_dup(_lib.X509_CRL_get_issuer(self._crl)) _openssl_assert(_issuer != _ffi.NULL) @@ -2754,7 +2752,7 @@ def dump_crl(type, crl): :param CRL crl: The CRL to dump. :return: The buffer with the CRL. - :rtype: :data:`bytes` + :rtype: bytes """ bio = _new_mem_buf() From 11861151732f1498a309dfc16dc3fb6ebf2e022b Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 16:22:23 -0700 Subject: [PATCH 08/18] Getting closer? --- doc/api/crypto.rst | 8 ++++++++ src/OpenSSL/crypto.py | 33 ++++++++++++++++++++------------- tests/test_crypto.py | 14 +++++++++++--- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index 7597d7ff3..7962a50fe 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -206,6 +206,14 @@ X509StoreContext objects .. _openssl-pkey: +X509StoreFlags enums +----------------- + +.. autoclass:: X509StoreFlags + :members: + +.. _openssl-x509storeflags: + PKey objects ------------ diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 2cf1a81d1..597f8c2fc 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -9,6 +9,7 @@ integer_types as _integer_types, text_type as _text_type, PY3 as _PY3) +from enum import Enum from OpenSSL._util import ( ffi as _ffi, @@ -1425,6 +1426,23 @@ def get_extension(self, index): X509Type = X509 +class X509StoreFlags(Enum): + """ Flags for X509 verification + https://www.openssl.org/docs/manmaster/crypto/X509_VERIFY_PARAM_set_flags.html + """ + CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK + CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL + IGNORE_CRITICAL = _lib.X509_V_FLAG_IGNORE_CRITICAL + X509_STRICT = _lib.X509_V_FLAG_X509_STRICT + ALLOW_PROXY_CERTS = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS + POLICY_CHECK = _lib.X509_V_FLAG_POLICY_CHECK + EXPLICIT_POLICY = _lib.X509_V_FLAG_EXPLICIT_POLICY + INHIBIT_MAP = _lib.X509_V_FLAG_INHIBIT_MAP + NOTIFY_POLICY = _lib.X509_V_FLAG_NOTIFY_POLICY + CHECK_SS_SIGNATURE = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE + CB_ISSUER_CHECK = _lib.X509_V_FLAG_CB_ISSUER_CHECK + + class X509Store(object): """ An X.509 store. @@ -1439,18 +1457,6 @@ class X509Store(object): :class:`X509StoreContext`. """ - CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK - CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL - IGNORE_CRITICAL = _lib.X509_V_FLAG_IGNORE_CRITICAL - X509_STRICT = _lib.X509_V_FLAG_X509_STRICT - ALLOW_PROXY_CERTS = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS - POLICY_CHECK = _lib.X509_V_FLAG_POLICY_CHECK - EXPLICIT_POLICY = _lib.X509_V_FLAG_EXPLICIT_POLICY - INHIBIT_MAP = _lib.X509_V_FLAG_INHIBIT_MAP - NOTIFY_POLICY = _lib.X509_V_FLAG_NOTIFY_POLICY - CHECK_SS_SIGNATURE = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE - CB_ISSUER_CHECK = _lib.X509_V_FLAG_CB_ISSUER_CHECK - def __init__(self): store = _lib.X509_STORE_new() self._store = _ffi.gc(store, _lib.X509_STORE_free) @@ -1924,7 +1930,8 @@ def set_rev_date(self, when): """ Set the revocation timestamp. - :param bytes when: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME. + :param bytes when: The timestamp of the revocation, + as ASN.1 GENERALIZEDTIME. :return: :const:`None` """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 13ea0adfe..1a9a718a8 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -21,7 +21,11 @@ from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType from OpenSSL.crypto import ( - X509Store, X509StoreType, X509StoreContext, X509StoreContextError + X509Store, + X509StoreFlags, + X509StoreType, + X509StoreContext, + X509StoreContextError ) from OpenSSL.crypto import X509Req, X509ReqType from OpenSSL.crypto import X509Extension, X509ExtensionType @@ -3453,7 +3457,9 @@ def test_verify_with_revoked(self): self.intermediate_cert, self.intermediate_key, certs=[]) store.add_crl(root_crl) store.add_crl(intermediate_crl) - store.set_flags(store.CRL_CHECK | store.CRL_CHECK_ALL) + store.set_flags( + X509StoreFlags.CRL_CHECK.value | + X509StoreFlags.CRL_CHECK_ALL.value) store_ctx = X509StoreContext(store, self.intermediate_server_cert) e = self.assertRaises( X509StoreContextError, store_ctx.verify_certificate) @@ -3470,7 +3476,9 @@ def test_verify_with_missing_crl(self): root_crl = self._make_test_crl( self.root_cert, self.root_key, certs=[self.intermediate_cert]) store.add_crl(root_crl) - store.set_flags(store.CRL_CHECK | store.CRL_CHECK_ALL) + store.set_flags( + X509StoreFlags.CRL_CHECK.value | + X509StoreFlags.CRL_CHECK_ALL.value) store_ctx = X509StoreContext(store, self.intermediate_server_cert) e = self.assertRaises( X509StoreContextError, store_ctx.verify_certificate) From 7fed8930317ff7f00ccf1e2f6c5bebf82af16f3b Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 16:30:31 -0700 Subject: [PATCH 09/18] Sphinx fix. --- doc/api/crypto.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index 7962a50fe..cf0186bec 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -207,7 +207,7 @@ X509StoreContext objects .. _openssl-pkey: X509StoreFlags enums ------------------ +-------------------- .. autoclass:: X509StoreFlags :members: From 7cfd528c12e845a9264987ff429f55b10b844a3a Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 16:32:28 -0700 Subject: [PATCH 10/18] Indent docstring. --- src/OpenSSL/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 597f8c2fc..0a8271bf1 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1931,7 +1931,7 @@ def set_rev_date(self, when): Set the revocation timestamp. :param bytes when: The timestamp of the revocation, - as ASN.1 GENERALIZEDTIME. + as ASN.1 GENERALIZEDTIME. :return: :const:`None` """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) From 4a142554a2f9655a924d024c2a7a8f73eb6fb46a Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 16:55:59 -0700 Subject: [PATCH 11/18] More Sphinx updates. --- doc/api/crypto.rst | 13 ++++++++++++- src/OpenSSL/crypto.py | 10 ++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index cf0186bec..1ef536336 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -210,7 +210,18 @@ X509StoreFlags enums -------------------- .. autoclass:: X509StoreFlags - :members: + + .. data:: CRL_CHECK + .. data:: CRL_CHECK_ALL + .. data:: IGNORE_CRITICAL + .. data:: X509_STRICT + .. data:: ALLOW_PROXY_CERTS + .. data:: POLICY_CHECK + .. data:: EXPLICIT_POLICY + .. data:: INHIBIT_MAP + .. data:: NOTIFY_POLICY + .. data:: CHECK_SS_SIGNATURE + .. data:: CB_ISSUER_CHECK .. _openssl-x509storeflags: diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 0a8271bf1..846f7ddd1 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1427,8 +1427,14 @@ def get_extension(self, index): class X509StoreFlags(Enum): - """ Flags for X509 verification - https://www.openssl.org/docs/manmaster/crypto/X509_VERIFY_PARAM_set_flags.html + """ + Flags for X509 verification, used to change the behavior of + :class:`X509Store`. + + See `OpenSSL Verification Flags`_ for details. + + .. _OpenSSL Verification Flags: + https://www.openssl.org/docs/manmaster/crypto/X509_VERIFY_PARAM_set_flags.html """ CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL From e3ce93d08fee678c769a710939bbd98734562bba Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 17:10:03 -0700 Subject: [PATCH 12/18] Type updates for docstrings. --- src/OpenSSL/crypto.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 846f7ddd1..870ea3714 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1647,8 +1647,7 @@ def load_certificate(type, buffer): :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the certificate is stored in - :type buffer: bytes + :param bytes buffer: The buffer the certificate is stored in :return: The X509 object """ @@ -1829,7 +1828,7 @@ def get_serial(self): ASCII. :return: The serial number. - :rtype: :class:`bytes` + :rtype: bytes """ bio = _new_mem_buf() From 55af14e4a32c4ee34f88f579eb5bddfdb34b10c9 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 17:16:39 -0700 Subject: [PATCH 13/18] Remove Enum subclassing. --- doc/api/crypto.rst | 4 ++-- src/OpenSSL/crypto.py | 3 +-- tests/test_crypto.py | 6 ++---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index 1ef536336..5f7df24cb 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -206,8 +206,8 @@ X509StoreContext objects .. _openssl-pkey: -X509StoreFlags enums --------------------- +X509StoreFlags constants +------------------------ .. autoclass:: X509StoreFlags diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 870ea3714..c38ddc1a9 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -9,7 +9,6 @@ integer_types as _integer_types, text_type as _text_type, PY3 as _PY3) -from enum import Enum from OpenSSL._util import ( ffi as _ffi, @@ -1426,7 +1425,7 @@ def get_extension(self, index): X509Type = X509 -class X509StoreFlags(Enum): +class X509StoreFlags(object): """ Flags for X509 verification, used to change the behavior of :class:`X509Store`. diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 1a9a718a8..9c7ccd878 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -3458,8 +3458,7 @@ def test_verify_with_revoked(self): store.add_crl(root_crl) store.add_crl(intermediate_crl) store.set_flags( - X509StoreFlags.CRL_CHECK.value | - X509StoreFlags.CRL_CHECK_ALL.value) + X509StoreFlags.CRL_CHECK | X509StoreFlags.CRL_CHECK_ALL) store_ctx = X509StoreContext(store, self.intermediate_server_cert) e = self.assertRaises( X509StoreContextError, store_ctx.verify_certificate) @@ -3477,8 +3476,7 @@ def test_verify_with_missing_crl(self): self.root_cert, self.root_key, certs=[self.intermediate_cert]) store.add_crl(root_crl) store.set_flags( - X509StoreFlags.CRL_CHECK.value | - X509StoreFlags.CRL_CHECK_ALL.value) + X509StoreFlags.CRL_CHECK | X509StoreFlags.CRL_CHECK_ALL) store_ctx = X509StoreContext(store, self.intermediate_server_cert) e = self.assertRaises( X509StoreContextError, store_ctx.verify_certificate) From c5dc568f4922ec80476f1041f000a33a4c6ad221 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 17:29:29 -0700 Subject: [PATCH 14/18] Docstring update. --- src/OpenSSL/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index c38ddc1a9..aea043db8 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1946,7 +1946,7 @@ def get_rev_date(self): Get the revocation timestamp. :return: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME. - :rtype: :class:`bytes` + :rtype: bytes """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) return _get_asn1_time(dt) From f0fcf90f31d857854ffe029d38159f28584df597 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 17:34:59 -0700 Subject: [PATCH 15/18] Docstrings updates. --- src/OpenSSL/crypto.py | 81 ++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index aea043db8..0fce297bc 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -183,7 +183,7 @@ def generate_key(self, type, bits): of the appropriate type. :raises ValueError: If the number of bits isn't an integer of the appropriate size. - :return: :py:const:`None` + :return: ``None`` """ if not isinstance(type, int): raise TypeError("type must be an integer") @@ -816,7 +816,7 @@ def set_pubkey(self, pkey): :param pkey: The public key to use. :type pkey: :py:class:`PKey` - :return: :py:const:`None` + :return: ``None`` """ set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey) if not set_result: @@ -845,7 +845,7 @@ def set_version(self, version): request. :param int version: The version number. - :return: :py:const:`None` + :return: ``None`` """ set_result = _lib.X509_REQ_set_version(self._req, version) if not set_result: @@ -891,7 +891,7 @@ def add_extensions(self, extensions): :param extensions: The X.509 extensions to add. :type extensions: iterable of :py:class:`X509Extension` - :return: :py:const:`None` + :return: ``None`` """ stack = _lib.sk_X509_EXTENSION_new_null() if stack == _ffi.NULL: @@ -938,7 +938,7 @@ def sign(self, pkey, digest): :param digest: The name of the message digest to use for the signature, e.g. :py:data:`b"sha1"`. :type digest: :py:class:`bytes` - :return: :py:const:`None` + :return: ``None`` """ if pkey._only_public: raise ValueError("Key has only public part") @@ -996,7 +996,7 @@ def set_version(self, version): :param version: The version number of the certificate. :type version: :py:class:`int` - :return: :py:const:`None` + :return: ``None`` """ if not isinstance(version, int): raise TypeError("version must be an integer") @@ -1174,7 +1174,7 @@ def get_serial_number(self): Return the serial number of this certificate. :return: The serial number. - :rtype: :py:class:`int` + :rtype: int """ asn1_serial = _lib.X509_get_serialNumber(self._x509) bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL) @@ -1193,10 +1193,9 @@ def gmtime_adj_notAfter(self, amount): """ Adjust the time stamp on which the certificate stops being valid. - :param amount: The number of seconds by which to adjust the timestamp. - :type amount: :py:class:`int` - - :return: :py:const:`None` + :param int amount: The number of seconds by which to adjust the + timestamp. + :return: ``None`` """ if not isinstance(amount, int): raise TypeError("amount must be an integer") @@ -1209,7 +1208,7 @@ def gmtime_adj_notBefore(self, amount): Adjust the timestamp on which the certificate starts being valid. :param amount: The number of seconds by which to adjust the timestamp. - :return: :py:const:`None` + :return: ``None`` """ if not isinstance(amount, int): raise TypeError("amount must be an integer") @@ -1221,9 +1220,8 @@ def has_expired(self): """ Check whether the certificate has expired. - :return: :py:const:`True` if the certificate has expired, - :py:const:`False` otherwise. - :rtype: :py:class:`bool` + :return: ``True`` if the certificate has expired, ``False`` otherwise. + :rtype: bool """ time_string = _native(self.get_notAfter()) not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") @@ -1243,8 +1241,8 @@ def get_notBefore(self): YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm - :return: A timestamp string, or :py:const:`None` if there is none. - :rtype: :py:class:`bytes` or :py:const:`None` + :return: A timestamp string, or ``None`` if there is none. + :rtype: bytes or NoneType """ return self._get_boundary_time(_lib.X509_get_notBefore) @@ -1261,10 +1259,8 @@ def set_notBefore(self, when): YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm - :param when: A timestamp string. - :type when: :py:class:`bytes` - - :return: :py:const:`None` + :param bytes when: A timestamp string. + :return: ``None`` """ return self._set_boundary_time(_lib.X509_get_notBefore, when) @@ -1278,8 +1274,8 @@ def get_notAfter(self): YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm - :return: A timestamp string, or :py:const:`None` if there is none. - :rtype: :py:class:`bytes` or :py:const:`None` + :return: A timestamp string, or ``None`` if there is none. + :rtype: bytes or NoneType """ return self._get_boundary_time(_lib.X509_get_notAfter) @@ -1293,10 +1289,8 @@ def set_notAfter(self, when): YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm - :param when: A timestamp string. - :type when: :py:class:`bytes` - - :return: :py:const:`None` + :param bytes when: A timestamp string. + :return: ``None`` """ return self._set_boundary_time(_lib.X509_get_notAfter, when) @@ -1342,7 +1336,7 @@ def set_issuer(self, issuer): :param issuer: The issuer. :type issuer: :py:class:`X509Name` - :return: :py:const:`None` + :return: ``None`` """ return self._set_name(_lib.X509_set_issuer_name, issuer) @@ -1367,7 +1361,7 @@ def set_subject(self, subject): :param subject: The subject. :type subject: :py:class:`X509Name` - :return: :py:const:`None` + :return: ``None`` """ return self._set_name(_lib.X509_set_subject_name, subject) @@ -1388,7 +1382,7 @@ def add_extensions(self, extensions): :param extensions: The extensions to add. :type extensions: An iterable of :py:class:`X509Extension` objects. - :return: :py:const:`None` + :return: ``None`` """ for ext in extensions: if not isinstance(ext, X509Extension): @@ -1850,12 +1844,12 @@ def set_reason(self, reason): """ Set the reason of this revocation. - If :data:`reason` is :const:`None`, delete the reason instead. + If :data:`reason` is ``None``, delete the reason instead. :param reason: The reason string. :type reason: :class:`bytes` or :class:`NoneType` - :return: :const:`None` + :return: ``None`` .. seealso:: @@ -1893,8 +1887,8 @@ def get_reason(self): """ Get the reason of this revocation. - :return: The reason, or :const:`None` if there is none. - :rtype: :class:`bytes` or :class:`NoneType` + :return: The reason, or ``None`` if there is none. + :rtype: bytes or NoneType .. seealso:: @@ -1936,7 +1930,7 @@ def set_rev_date(self, when): :param bytes when: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME. - :return: :const:`None` + :return: ``None`` """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) return _set_asn1_time(dt, when) @@ -1991,7 +1985,7 @@ def add_revoked(self, revoked): this CRL. :param Revoked revoked: The new revocation. - :return: :const:`None` + :return: ``None`` """ copy = _lib.Cryptography_X509_REVOKED_dup(revoked._revoked) if copy == _ffi.NULL: @@ -2244,7 +2238,7 @@ def set_certificate(self, cert): :param cert: The new certificate, or :py:const:`None` to unset it. :type cert: :py:class:`X509` or :py:const:`None` - :return: :py:const:`None` + :return: ``None`` """ if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") @@ -2266,7 +2260,7 @@ def set_privatekey(self, pkey): :param pkey: The new private key, or :py:const:`None` to unset it. :type pkey: :py:class:`PKey` or :py:const:`None` - :return: :py:const:`None` + :return: ``None`` """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") @@ -2291,7 +2285,7 @@ def set_ca_certificates(self, cacerts): them. :type cacerts: An iterable of :py:class:`X509` or :py:const:`None` - :return: :py:const:`None` + :return: ``None`` """ if cacerts is None: self._cacerts = None @@ -2311,7 +2305,7 @@ def set_friendlyname(self, name): :param name: The new friendly name, or :py:const:`None` to unset. :type name: :py:class:`bytes` or :py:const:`None` - :return: :py:const:`None` + :return: ``None`` """ if name is None: self._friendlyname = None @@ -2413,7 +2407,7 @@ def sign(self, pkey, digest): :param digest: The message digest to use. :type digest: :py:class:`bytes` - :return: :py:const:`None` + :return: ``None`` """ if pkey._only_public: raise ValueError("Key has only public part") @@ -2483,7 +2477,7 @@ def set_pubkey(self, pkey): Set the public key of the certificate :param pkey: The public key - :return: :py:const:`None` + :return: ``None`` """ set_result = _lib.NETSCAPE_SPKI_set_pubkey(self._spki, pkey._pkey) if not set_result: @@ -2726,8 +2720,7 @@ def verify(cert, signature, data, digest): :param signature: signature returned by sign function :param data: data to be verified :param digest: message digest to use - :return: :py:const:`None` if the signature is correct, raise exception - otherwise + :return: ``None`` if the signature is correct, raise exception otherwise. """ data = _text_to_bytes_and_warn("data", data) From 8842733c518a3ecde09dfe63f34d06edc03c180f Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 17:42:08 -0700 Subject: [PATCH 16/18] Round and round it goes. Where it ends, nobody knows. --- src/OpenSSL/crypto.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 0fce297bc..476c0c301 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1512,8 +1512,8 @@ def set_flags(self, flags): .. versionadded:: 16.1.0 :param int flags: The verification flags to set on this store. - :return: ``None`` if the verification flags were - successfully set. + See :class:`X509StoreFlags` for available constants. + :return: ``None`` if the verification flags were successfully set. """ _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0) @@ -1551,7 +1551,6 @@ class X509StoreContext(object): :param X509Store store: The certificates which will be trusted for the purposes of any verifications. :param X509 certificate: The certificate to be verified. - """ def __init__(self, store, certificate): From 0317e554d3f5b4eb462327fb6d11b1f3b77e0e70 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 17:51:32 -0700 Subject: [PATCH 17/18] Remove TODO comment. Need @sholsapp to follow up. --- src/OpenSSL/crypto.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 476c0c301..ab46b455e 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -2099,11 +2099,6 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, :rtype: bytes """ - # TODO: fix this function to use functionality added in version 16.1.0 - # Doing this without changing the public API is tricky. Checking if - # lastUpdate, nextUpdate, issuer or signing has happened is hard to do - # without generating a segmentation fault. - if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") if not isinstance(key, PKey): From cd5f04c24881cd9b65f6815c3dd811bab1c4e8f7 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Sat, 4 Jun 2016 17:57:31 -0700 Subject: [PATCH 18/18] Time for lots of alcohol. --- src/OpenSSL/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index ab46b455e..4c96d88e3 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -2074,7 +2074,7 @@ def sign(self, issuer_cert, issuer_key, digest): :param X509 issuer_cert: The issuer's certificate. :param PKey issuer_key: The issuer's private key. - :param str digest: The digest method to sign the CRL with. + :param bytes digest: The digest method to sign the CRL with. """ digest_obj = _lib.EVP_get_digestbyname(digest) _openssl_assert(digest_obj != _ffi.NULL)