From 9561acaf33b6a577730d005d1f774df6e755d229 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 25 Oct 2024 00:19:28 -0400 Subject: [PATCH] Remove APIs that have been deprecated for a year --- CHANGELOG.rst | 3 + doc/api/crypto.rst | 32 --- src/OpenSSL/crypto.py | 633 +----------------------------------------- tests/test_crypto.py | 577 +------------------------------------- 4 files changed, 11 insertions(+), 1234 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a433a4ec..007b1c60 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,9 @@ The third digit is only for regressions. Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Removed the deprecated ``OpenSSL.crypto.CRL``, ``OpenSSL.crypto.Revoked``, ``OpenSSL.crypto.dump_crl``, and ``OpenSSL.crypto.load_crl``. +- Removed the deprecated ``OpenSSL.crypto.sign`` and ``OpenSSL.crypto.verify``. + Deprecations: ^^^^^^^^^^^^^ diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index 926ae580..f48b6cfe 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -61,20 +61,6 @@ Public keys .. autofunction:: load_publickey -Certificate revocation lists -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autofunction:: dump_crl - -.. autofunction:: load_crl - -Signing and verifying signatures --------------------------------- - -.. autofunction:: sign - -.. autofunction:: verify - .. _openssl-x509: @@ -170,24 +156,6 @@ X509Extension objects :special-members: :exclude-members: __weakref__ -.. _crl: - -CRL objects ------------ - -.. autoclass:: CRL - :members: - :special-members: - :exclude-members: __weakref__ - -.. _revoked: - -Revoked objects ---------------- - -.. autoclass:: Revoked - :members: - Exceptions ---------- diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index f62d5bde..ef75d9d3 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -25,9 +25,6 @@ rsa, ) -from OpenSSL._util import ( - UNSPECIFIED as _UNSPECIFIED, -) from OpenSSL._util import ( byte_string as _byte_string, ) @@ -46,12 +43,8 @@ from OpenSSL._util import ( path_bytes as _path_bytes, ) -from OpenSSL._util import ( - text_to_bytes_and_warn as _text_to_bytes_and_warn, -) __all__ = [ - "CRL", "FILETYPE_ASN1", "FILETYPE_PEM", "FILETYPE_TEXT", @@ -60,7 +53,6 @@ "X509", "Error", "PKey", - "Revoked", "X509Extension", "X509Name", "X509Req", @@ -70,18 +62,14 @@ "X509StoreFlags", "dump_certificate", "dump_certificate_request", - "dump_crl", "dump_privatekey", "dump_publickey", "get_elliptic_curve", "get_elliptic_curves", "load_certificate", "load_certificate_request", - "load_crl", "load_privatekey", "load_publickey", - "sign", - "verify", ] @@ -1787,9 +1775,7 @@ def add_cert(self, cert: X509) -> None: res = _lib.X509_STORE_add_cert(self._store, cert._x509) _openssl_assert(res == 1) - def add_crl( - self, crl: _CRLInternal | x509.CertificateRevocationList - ) -> None: + def add_crl(self, crl: x509.CertificateRevocationList) -> None: """ Add a certificate revocation list to this store. @@ -1800,7 +1786,7 @@ def add_crl( .. versionadded:: 16.1.0 :param crl: The certificate revocation list to add to this store. - :type crl: ``Union[CRL, cryptography.x509.CertificateRevocationList]`` + :type crl: ``cryptography.x509.CertificateRevocationList`` :return: ``None`` if the certificate revocation list was added successfully. """ @@ -1811,11 +1797,9 @@ def add_crl( openssl_crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) _openssl_assert(openssl_crl != _ffi.NULL) crl = _ffi.gc(openssl_crl, _lib.X509_CRL_free) - elif isinstance(crl, _CRLInternal): - crl = crl._crl else: raise TypeError( - "CRL must be of type OpenSSL.crypto.CRL or " + "CRL must be of type " "cryptography.x509.CertificateRevocationList" ) @@ -2245,441 +2229,6 @@ def dump_privatekey( return _bio_to_string(bio) -class Revoked: - """ - A certificate revocation. - """ - - # https://www.openssl.org/docs/manmaster/man5/x509v3_config.html#CRL-distribution-points - # which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches - # OCSP_crl_reason_str. We use the latter, just like the command line - # program. - _crl_reasons: typing.ClassVar[list[bytes]] = [ - b"unspecified", - b"keyCompromise", - b"CACompromise", - b"affiliationChanged", - b"superseded", - b"cessationOfOperation", - b"certificateHold", - # b"removeFromCRL", - ] - - def __init__(self) -> None: - revoked = _lib.X509_REVOKED_new() - self._revoked = _ffi.gc(revoked, _lib.X509_REVOKED_free) - - def set_serial(self, hex_str: bytes) -> None: - """ - Set the serial number. - - The serial number is formatted as a hexadecimal number encoded in - ASCII. - - :param bytes hex_str: The new serial number. - - :return: ``None`` - """ - bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free) - bignum_ptr = _ffi.new("BIGNUM**") - bignum_ptr[0] = bignum_serial - bn_result = _lib.BN_hex2bn(bignum_ptr, hex_str) - if not bn_result: - raise ValueError("bad hex string") - - asn1_serial = _ffi.gc( - _lib.BN_to_ASN1_INTEGER(bignum_serial, _ffi.NULL), - _lib.ASN1_INTEGER_free, - ) - _lib.X509_REVOKED_set_serialNumber(self._revoked, asn1_serial) - - def get_serial(self) -> bytes: - """ - Get the serial number. - - The serial number is formatted as a hexadecimal number encoded in - ASCII. - - :return: The serial number. - :rtype: bytes - """ - bio = _new_mem_buf() - - asn1_int = _lib.X509_REVOKED_get0_serialNumber(self._revoked) - _openssl_assert(asn1_int != _ffi.NULL) - result = _lib.i2a_ASN1_INTEGER(bio, asn1_int) - _openssl_assert(result >= 0) - return _bio_to_string(bio) - - def _delete_reason(self) -> None: - for i in range(_lib.X509_REVOKED_get_ext_count(self._revoked)): - ext = _lib.X509_REVOKED_get_ext(self._revoked, i) - obj = _lib.X509_EXTENSION_get_object(ext) - if _lib.OBJ_obj2nid(obj) == _lib.NID_crl_reason: - _lib.X509_EXTENSION_free(ext) - _lib.X509_REVOKED_delete_ext(self._revoked, i) - break - - def set_reason(self, reason: bytes | None) -> None: - """ - Set the reason of this revocation. - - If :data:`reason` is ``None``, delete the reason instead. - - :param reason: The reason string. - :type reason: :class:`bytes` or :class:`NoneType` - - :return: ``None`` - - .. seealso:: - - :meth:`all_reasons`, which gives you a list of all supported - reasons which you might pass to this method. - """ - if reason is None: - self._delete_reason() - elif not isinstance(reason, bytes): - raise TypeError("reason must be None or a byte string") - else: - reason = reason.lower().replace(b" ", b"") - reason_code = [r.lower() for r in self._crl_reasons].index(reason) - - new_reason_ext = _lib.ASN1_ENUMERATED_new() - _openssl_assert(new_reason_ext != _ffi.NULL) - new_reason_ext = _ffi.gc(new_reason_ext, _lib.ASN1_ENUMERATED_free) - - set_result = _lib.ASN1_ENUMERATED_set(new_reason_ext, reason_code) - _openssl_assert(set_result != _ffi.NULL) - - self._delete_reason() - add_result = _lib.X509_REVOKED_add1_ext_i2d( - self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0 - ) - _openssl_assert(add_result == 1) - - def get_reason(self) -> bytes | None: - """ - Get the reason of this revocation. - - :return: The reason, or ``None`` if there is none. - :rtype: bytes or NoneType - - .. seealso:: - - :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)): - ext = _lib.X509_REVOKED_get_ext(self._revoked, i) - obj = _lib.X509_EXTENSION_get_object(ext) - if _lib.OBJ_obj2nid(obj) == _lib.NID_crl_reason: - bio = _new_mem_buf() - - print_result = _lib.X509V3_EXT_print(bio, ext, 0, 0) - if not print_result: - print_result = _lib.M_ASN1_OCTET_STRING_print( - bio, _lib.X509_EXTENSION_get_data(ext) - ) - _openssl_assert(print_result != 0) - - return _bio_to_string(bio) - return None - - def all_reasons(self) -> list[bytes]: - """ - Return a list of all the supported reason strings. - - This list is a copy; modifying it does not change the supported reason - strings. - - :return: A list of reason strings. - :rtype: :class:`list` of :class:`bytes` - """ - return self._crl_reasons[:] - - def set_rev_date(self, when: bytes) -> None: - """ - Set the revocation timestamp. - - :param bytes when: The timestamp of the revocation, - as ASN.1 TIME. - :return: ``None`` - """ - revocationDate = _new_asn1_time(when) - ret = _lib.X509_REVOKED_set_revocationDate( - self._revoked, revocationDate - ) - _openssl_assert(ret == 1) - - def get_rev_date(self) -> bytes | None: - """ - Get the revocation timestamp. - - :return: The timestamp of the revocation, as ASN.1 TIME. - :rtype: bytes - """ - dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) - return _get_asn1_time(dt) - - -_RevokedInternal = Revoked -utils.deprecated( - Revoked, - __name__, - ( - "CRL support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, - name="Revoked", -) - - -class CRL: - """ - A certificate revocation list. - """ - - def __init__(self) -> None: - crl = _lib.X509_CRL_new() - self._crl = _ffi.gc(crl, _lib.X509_CRL_free) - - def to_cryptography(self) -> x509.CertificateRevocationList: - """ - Export as a ``cryptography`` CRL. - - :rtype: ``cryptography.x509.CertificateRevocationList`` - - .. versionadded:: 17.1.0 - """ - from cryptography.x509 import load_der_x509_crl - - der = _dump_crl_internal(FILETYPE_ASN1, self) - return load_der_x509_crl(der) - - @classmethod - def from_cryptography( - cls, crypto_crl: x509.CertificateRevocationList - ) -> _CRLInternal: - """ - Construct based on a ``cryptography`` *crypto_crl*. - - :param crypto_crl: A ``cryptography`` certificate revocation list - :type crypto_crl: ``cryptography.x509.CertificateRevocationList`` - - :rtype: CRL - - .. versionadded:: 17.1.0 - """ - if not isinstance(crypto_crl, x509.CertificateRevocationList): - raise TypeError("Must be a certificate revocation list") - - from cryptography.hazmat.primitives.serialization import Encoding - - der = crypto_crl.public_bytes(Encoding.DER) - return _load_crl_internal(FILETYPE_ASN1, der) - - def get_revoked(self) -> tuple[_RevokedInternal, ...] | None: - """ - Return the revocations in this certificate revocation list. - - These revocations will be provided by value, not by reference. - That means it's okay to mutate them: it won't affect this CRL. - - :return: The revocations in this CRL. - :rtype: :class:`tuple` of :class:`Revocation` - """ - results = [] - revoked_stack = _lib.X509_CRL_get_REVOKED(self._crl) - for i in range(_lib.sk_X509_REVOKED_num(revoked_stack)): - revoked = _lib.sk_X509_REVOKED_value(revoked_stack, i) - revoked_copy = _lib.X509_REVOKED_dup(revoked) - pyrev = _RevokedInternal.__new__(_RevokedInternal) - pyrev._revoked = _ffi.gc(revoked_copy, _lib.X509_REVOKED_free) - results.append(pyrev) - if results: - return tuple(results) - return None - - def add_revoked(self, revoked: _RevokedInternal) -> None: - """ - Add a revoked (by value not reference) to the CRL structure - - This revocation will be added by value, not by reference. That - means it's okay to mutate it after adding: it won't affect - this CRL. - - :param Revoked revoked: The new revocation. - :return: ``None`` - """ - copy = _lib.X509_REVOKED_dup(revoked._revoked) - _openssl_assert(copy != _ffi.NULL) - - add_result = _lib.X509_CRL_add0_revoked(self._crl, copy) - _openssl_assert(add_result != 0) - - def get_issuer(self) -> X509Name: - """ - Get the CRL's issuer. - - .. versionadded:: 16.1.0 - - :rtype: 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 - - def set_version(self, version: int) -> None: - """ - Set the CRL version. - - .. versionadded:: 16.1.0 - - :param int version: The version of the CRL. - :return: ``None`` - """ - _openssl_assert(_lib.X509_CRL_set_version(self._crl, version) != 0) - - def set_lastUpdate(self, when: bytes) -> None: - """ - Set when the CRL was last updated. - - The timestamp is formatted as an ASN.1 TIME:: - - YYYYMMDDhhmmssZ - - .. versionadded:: 16.1.0 - - :param bytes when: A timestamp string. - :return: ``None`` - """ - lastUpdate = _new_asn1_time(when) - ret = _lib.X509_CRL_set1_lastUpdate(self._crl, lastUpdate) - _openssl_assert(ret == 1) - - def set_nextUpdate(self, when: bytes) -> None: - """ - Set when the CRL will next be updated. - - The timestamp is formatted as an ASN.1 TIME:: - - YYYYMMDDhhmmssZ - - .. versionadded:: 16.1.0 - - :param bytes when: A timestamp string. - :return: ``None`` - """ - nextUpdate = _new_asn1_time(when) - ret = _lib.X509_CRL_set1_nextUpdate(self._crl, nextUpdate) - _openssl_assert(ret == 1) - - def sign(self, issuer_cert: X509, issuer_key: PKey, digest: bytes) -> None: - """ - 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:: 16.1.0 - - :param X509 issuer_cert: The issuer's certificate. - :param PKey issuer_key: The issuer's private key. - :param bytes 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) - result = _lib.X509_CRL_sign(self._crl, issuer_key._pkey, digest_obj) - _openssl_assert(result != 0) - - def export( - self, - cert: X509, - key: PKey, - type: int = FILETYPE_PEM, - days: int = 100, - digest: bytes = _UNSPECIFIED, # type: ignore - ) -> bytes: - """ - 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 :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"sha256"``). - :rtype: bytes - """ - - if not isinstance(cert, X509): - raise TypeError("cert must be an X509 instance") - if not isinstance(key, PKey): - raise TypeError("key must be a PKey instance") - if not isinstance(type, int): - raise TypeError("type must be an integer") - - if digest is _UNSPECIFIED: - raise TypeError("digest must be provided") - - digest_obj = _lib.EVP_get_digestbyname(digest) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - # A scratch time object to give different values to different CRL - # fields - sometime = _lib.ASN1_TIME_new() - _openssl_assert(sometime != _ffi.NULL) - sometime = _ffi.gc(sometime, _lib.ASN1_TIME_free) - - ret = _lib.X509_gmtime_adj(sometime, 0) - _openssl_assert(ret != _ffi.NULL) - ret = _lib.X509_CRL_set1_lastUpdate(self._crl, sometime) - _openssl_assert(ret == 1) - - ret = _lib.X509_gmtime_adj(sometime, days * 24 * 60 * 60) - _openssl_assert(ret != _ffi.NULL) - ret = _lib.X509_CRL_set1_nextUpdate(self._crl, sometime) - _openssl_assert(ret == 1) - - ret = _lib.X509_CRL_set_issuer_name( - self._crl, _lib.X509_get_subject_name(cert._x509) - ) - _openssl_assert(ret == 1) - - sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, digest_obj) - if not sign_result: - _raise_current_error() - - return _dump_crl_internal(type, self) - - -_CRLInternal = CRL -utils.deprecated( - CRL, - __name__, - ( - "CRL support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, - name="CRL", -) - - class _PassphraseHelper: def __init__( self, @@ -2915,179 +2464,3 @@ def load_certificate_request(type: int, buffer: bytes) -> X509Req: DeprecationWarning, name="load_certificate_request", ) - - -def sign(pkey: PKey, data: str | bytes, digest: str) -> bytes: - """ - Sign a data string using the given key and message digest. - - :param pkey: PKey to sign with - :param data: data to be signed - :param digest: message digest to use - :return: signature - - .. versionadded:: 0.11 - """ - data = _text_to_bytes_and_warn("data", data) - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - md_ctx = _lib.EVP_MD_CTX_new() - md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_free) - - _lib.EVP_SignInit(md_ctx, digest_obj) - _lib.EVP_SignUpdate(md_ctx, data, len(data)) - - length = _lib.EVP_PKEY_size(pkey._pkey) - _openssl_assert(length > 0) - signature_buffer = _ffi.new("unsigned char[]", length) - signature_length = _ffi.new("unsigned int *") - final_result = _lib.EVP_SignFinal( - md_ctx, signature_buffer, signature_length, pkey._pkey - ) - _openssl_assert(final_result == 1) - - return _ffi.buffer(signature_buffer, signature_length[0])[:] - - -utils.deprecated( - sign, - __name__, - "sign() is deprecated. Use the equivalent APIs in cryptography.", - DeprecationWarning, - name="sign", -) - - -def verify( - cert: X509, signature: bytes, data: str | bytes, digest: str -) -> None: - """ - Verify the signature for a data string. - - :param cert: signing certificate (X509 object) corresponding to the - private key which generated the signature. - :param signature: signature returned by sign function - :param data: data to be verified - :param digest: message digest to use - :return: ``None`` if the signature is correct, raise exception otherwise. - - .. versionadded:: 0.11 - """ - data = _text_to_bytes_and_warn("data", data) - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - pkey = _lib.X509_get_pubkey(cert._x509) - _openssl_assert(pkey != _ffi.NULL) - pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) - - md_ctx = _lib.EVP_MD_CTX_new() - md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_free) - - _lib.EVP_VerifyInit(md_ctx, digest_obj) - _lib.EVP_VerifyUpdate(md_ctx, data, len(data)) - verify_result = _lib.EVP_VerifyFinal( - md_ctx, signature, len(signature), pkey - ) - - if verify_result != 1: - _raise_current_error() - - -utils.deprecated( - verify, - __name__, - "verify() is deprecated. Use the equivalent APIs in cryptography.", - DeprecationWarning, - name="verify", -) - - -def dump_crl(type: int, crl: _CRLInternal) -> bytes: - """ - Dump a certificate revocation list to a buffer. - - :param type: The file type (one of ``FILETYPE_PEM``, ``FILETYPE_ASN1``, or - ``FILETYPE_TEXT``). - :param CRL crl: The CRL to dump. - - :return: The buffer with the CRL. - :rtype: bytes - """ - bio = _new_mem_buf() - - if type == FILETYPE_PEM: - ret = _lib.PEM_write_bio_X509_CRL(bio, crl._crl) - elif type == FILETYPE_ASN1: - ret = _lib.i2d_X509_CRL_bio(bio, crl._crl) - elif type == FILETYPE_TEXT: - ret = _lib.X509_CRL_print(bio, crl._crl) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT" - ) - - _openssl_assert(ret == 1) - return _bio_to_string(bio) - - -_dump_crl_internal = dump_crl -utils.deprecated( - dump_crl, - __name__, - ( - "CRL support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, - name="dump_crl", -) - - -def load_crl(type: int, buffer: str | bytes) -> _CRLInternal: - """ - Load Certificate Revocation List (CRL) data from a string *buffer*. - *buffer* encoded with the type *type*. - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the CRL is stored in - - :return: The CRL object - """ - if isinstance(buffer, str): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - crl = _lib.PEM_read_bio_X509_CRL(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if crl == _ffi.NULL: - _raise_current_error() - - result = _CRLInternal.__new__(_CRLInternal) - result._crl = _ffi.gc(crl, _lib.X509_CRL_free) - return result - - -_load_crl_internal = load_crl -utils.deprecated( - load_crl, - __name__, - ( - "CRL support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, - name="load_crl", -) diff --git a/tests/test_crypto.py b/tests/test_crypto.py index c1db014c..cddeca94 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -7,7 +7,6 @@ import base64 import sys -import warnings from datetime import datetime, timedelta, timezone from subprocess import PIPE, Popen @@ -49,22 +48,15 @@ load_certificate_request, load_privatekey, load_publickey, - sign, - verify, ) with pytest.warns(DeprecationWarning): from OpenSSL.crypto import ( - CRL, - Revoked, X509Extension, - dump_crl, - load_crl, ) from .util import ( NON_ASCII, - WARNING_TYPE_EXPECTED, EqualityTestsMixin, is_consistent_type, ) @@ -2958,116 +2950,6 @@ def test_bad_certificate(self): load_certificate(FILETYPE_ASN1, b"lol") -class TestRevoked: - """ - Tests for `OpenSSL.crypto.Revoked`. - """ - - def test_ignores_unsupported_revoked_cert_extension_get_reason(self): - """ - The get_reason method on the Revoked class checks to see if the - extension is NID_crl_reason and should skip it otherwise. This test - loads a CRL with extensions it should ignore. - """ - crl = load_crl(FILETYPE_PEM, crlDataUnsupportedExtension) - revoked = crl.get_revoked() - reason = revoked[1].get_reason() - assert reason == b"Unspecified" - - def test_ignores_unsupported_revoked_cert_extension_set_new_reason(self): - crl = load_crl(FILETYPE_PEM, crlDataUnsupportedExtension) - revoked = crl.get_revoked() - revoked[1].set_reason(None) - reason = revoked[1].get_reason() - assert reason is None - - def test_construction(self): - """ - Confirm we can create `OpenSSL.crypto.Revoked`. Check that it is - empty. - """ - revoked = Revoked() - assert isinstance(revoked, Revoked) - assert type(revoked) is Revoked - assert revoked.get_serial() == b"00" - assert revoked.get_rev_date() is None - assert revoked.get_reason() is None - - def test_serial(self): - """ - Confirm we can set and get serial numbers from - `OpenSSL.crypto.Revoked`. Confirm errors are handled with grace. - """ - revoked = Revoked() - ret = revoked.set_serial(b"10b") - assert ret is None - ser = revoked.get_serial() - assert ser == b"010B" - - revoked.set_serial(b"31ppp") # a type error would be nice - ser = revoked.get_serial() - assert ser == b"31" - - with pytest.raises(ValueError): - revoked.set_serial(b"pqrst") - with pytest.raises(TypeError): - revoked.set_serial(100) - - def test_date(self): - """ - Confirm we can set and get revocation dates from - `OpenSSL.crypto.Revoked`. Confirm errors are handled with grace. - """ - revoked = Revoked() - date = revoked.get_rev_date() - assert date is None - - now = datetime.now().strftime("%Y%m%d%H%M%SZ").encode("ascii") - ret = revoked.set_rev_date(now) - assert ret is None - date = revoked.get_rev_date() - assert date == now - - def test_reason(self): - """ - Confirm we can set and get revocation reasons from - `OpenSSL.crypto.Revoked`. The "get" need to work as "set". - Likewise, each reason of all_reasons() must work. - """ - revoked = Revoked() - for r in revoked.all_reasons(): - for x in range(2): - ret = revoked.set_reason(r) - assert ret is None - reason = revoked.get_reason() - assert reason.lower().replace(b" ", b"") == r.lower().replace( - b" ", b"" - ) - r = reason # again with the resp of get - - revoked.set_reason(None) - assert revoked.get_reason() is None - - @pytest.mark.parametrize("reason", [object(), 1.0, "foo"]) - def test_set_reason_wrong_args(self, reason): - """ - `Revoked.set_reason` raises `TypeError` if called with an argument - which is neither `None` nor a byte string. - """ - revoked = Revoked() - with pytest.raises(TypeError): - revoked.set_reason(reason) - - def test_set_reason_invalid_reason(self): - """ - Calling `OpenSSL.crypto.Revoked.set_reason` with an argument which - isn't a valid reason results in `ValueError` being raised. - """ - revoked = Revoked() - with pytest.raises(ValueError): - revoked.set_reason(b"blue") - - class TestCRL: """ Tests for `OpenSSL.crypto.CRL`. @@ -3087,291 +2969,6 @@ class TestCRL: FILETYPE_PEM, intermediate_server_key_pem ) - def test_construction(self): - """ - Confirm we can create `OpenSSL.crypto.CRL`. Check - that it is empty - """ - crl = CRL() - assert isinstance(crl, CRL) - assert crl.get_revoked() is None - - def _get_crl(self): - """ - Get a new ``CRL`` with a revocation. - """ - crl = CRL() - revoked = Revoked() - now = datetime.now().strftime("%Y%m%d%H%M%SZ").encode("ascii") - revoked.set_rev_date(now) - revoked.set_serial(b"3ab") - revoked.set_reason(b"sUpErSeDEd") - crl.add_revoked(revoked) - return crl - - def test_export_pem(self): - """ - If not passed a format, ``CRL.export`` returns a "PEM" format string - representing a serial number, a revoked reason, and certificate issuer - information. - """ - # PEM format - dumped_crl = self._get_crl().export( - self.cert, self.pkey, days=20, digest=b"sha256" - ) - crl = x509.load_pem_x509_crl(dumped_crl) - revoked = crl.get_revoked_certificate_by_serial_number(0x03AB) - assert revoked is not None - assert crl.issuer == x509.Name( - [ - x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "US"), - x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, "IL"), - x509.NameAttribute(x509.NameOID.LOCALITY_NAME, "Chicago"), - x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, "Testing"), - x509.NameAttribute( - x509.NameOID.COMMON_NAME, "Testing Root CA" - ), - ] - ) - - def test_export_der(self): - """ - If passed ``FILETYPE_ASN1`` for the format, ``CRL.export`` returns a - "DER" format string representing a serial number, a revoked reason, and - certificate issuer information. - """ - crl = self._get_crl() - - # DER format - dumped_crl = self._get_crl().export( - self.cert, self.pkey, FILETYPE_ASN1, digest=b"sha256" - ) - crl = x509.load_der_x509_crl(dumped_crl) - revoked = crl.get_revoked_certificate_by_serial_number(0x03AB) - assert revoked is not None - assert crl.issuer == x509.Name( - [ - x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "US"), - x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, "IL"), - x509.NameAttribute(x509.NameOID.LOCALITY_NAME, "Chicago"), - x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, "Testing"), - x509.NameAttribute( - x509.NameOID.COMMON_NAME, "Testing Root CA" - ), - ] - ) - - def test_export_text(self): - """ - If passed ``FILETYPE_TEXT`` for the format, ``CRL.export`` returns a - text format string like the one produced by the openssl command line - tool. - """ - crl = self._get_crl() - - # text format - dumped_text = crl.export( - self.cert, self.pkey, type=FILETYPE_TEXT, digest=b"sha256" - ) - assert len(dumped_text) > 500 - - def test_export_custom_digest(self): - """ - If passed the name of a digest function, ``CRL.export`` uses a - signature algorithm based on that digest function. - """ - crl = self._get_crl() - dumped_crl = crl.export(self.cert, self.pkey, digest=b"sha384") - text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") - text.index(b"Signature Algorithm: sha384") - - def test_export_md5_digest(self): - """ - If passed md5 as the digest function, ``CRL.export`` uses md5 and does - not emit a deprecation warning. - """ - crl = self._get_crl() - with warnings.catch_warnings(record=True) as catcher: - warnings.simplefilter("always") - assert 0 == len(catcher) - dumped_crl = crl.export(self.cert, self.pkey, digest=b"md5") - text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") - text.index(b"Signature Algorithm: md5") - - def test_export_default_digest(self): - """ - If not passed the name of a digest function, ``CRL.export`` raises a - ``TypeError``. - """ - crl = self._get_crl() - with pytest.raises(TypeError): - crl.export(self.cert, self.pkey) - - def test_export_invalid(self): - """ - If `CRL.export` is used with an uninitialized `X509` instance, - `OpenSSL.crypto.Error` is raised. - """ - crl = CRL() - with pytest.raises(Error): - crl.export(X509(), PKey(), digest=b"sha256") - - def test_add_revoked_keyword(self): - """ - `OpenSSL.CRL.add_revoked` accepts its single argument as the - ``revoked`` keyword argument. - """ - crl = CRL() - revoked = Revoked() - revoked.set_serial(b"01") - revoked.set_rev_date(b"20160310020145Z") - crl.add_revoked(revoked=revoked) - assert isinstance(crl.get_revoked()[0], Revoked) - - def test_export_wrong_args(self): - """ - Calling `OpenSSL.CRL.export` with arguments other than the certificate, - private key, integer file type, and integer number of days it - expects, results in a `TypeError` being raised. - """ - crl = CRL() - with pytest.raises(TypeError): - crl.export(None, self.pkey, FILETYPE_PEM, 10) - with pytest.raises(TypeError): - crl.export(self.cert, None, FILETYPE_PEM, 10) - with pytest.raises(TypeError): - crl.export(self.cert, self.pkey, None, 10) - with pytest.raises(TypeError): - crl.export(self.cert, FILETYPE_PEM, None) - - def test_export_unknown_filetype(self): - """ - Calling `OpenSSL.CRL.export` with a file type other than - `FILETYPE_PEM`, `FILETYPE_ASN1`, or - `FILETYPE_TEXT` results in a `ValueError` being raised. - """ - crl = CRL() - with pytest.raises(ValueError): - crl.export(self.cert, self.pkey, 100, 10, digest=b"sha256") - - def test_export_unknown_digest(self): - """ - Calling `OpenSSL.CRL.export` with an unsupported digest results - in a `ValueError` being raised. - """ - crl = CRL() - with pytest.raises(ValueError): - crl.export( - self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest" - ) - - def test_get_revoked(self): - """ - Use python to create a simple CRL with two revocations. Get back the - `Revoked` using `OpenSSL.CRL.get_revoked` and verify them. - """ - crl = CRL() - - revoked = Revoked() - now = datetime.now().strftime("%Y%m%d%H%M%SZ").encode("ascii") - revoked.set_rev_date(now) - revoked.set_serial(b"3ab") - crl.add_revoked(revoked) - revoked.set_serial(b"100") - revoked.set_reason(b"sUpErSeDEd") - crl.add_revoked(revoked) - - revs = crl.get_revoked() - assert len(revs) == 2 - assert type(revs[0]) is Revoked - assert type(revs[1]) is Revoked - assert revs[0].get_serial() == b"03AB" - assert revs[1].get_serial() == b"0100" - assert revs[0].get_rev_date() == now - assert revs[1].get_rev_date() == now - - def test_load_crl(self): - """ - Load a known CRL and inspect its revocations. Both EM and DER formats - are loaded. - """ - crl = load_crl(FILETYPE_PEM, crlData) - revs = crl.get_revoked() - assert len(revs) == 2 - assert revs[0].get_serial() == b"03AB" - assert revs[0].get_reason() is None - assert revs[1].get_serial() == b"0100" - assert revs[1].get_reason() == b"Superseded" - - der = _runopenssl(crlData, b"crl", b"-outform", b"DER") - crl = load_crl(FILETYPE_ASN1, der) - revs = crl.get_revoked() - assert len(revs) == 2 - assert revs[0].get_serial() == b"03AB" - assert revs[0].get_reason() is None - assert revs[1].get_serial() == b"0100" - assert revs[1].get_reason() == b"Superseded" - - def test_load_crl_bad_filetype(self): - """ - Calling `OpenSSL.crypto.load_crl` with an unknown file type raises a - `ValueError`. - """ - with pytest.raises(ValueError): - load_crl(100, crlData) - - def test_load_crl_bad_data(self): - """ - Calling `OpenSSL.crypto.load_crl` with file data which can't be loaded - raises a `OpenSSL.crypto.Error`. - """ - with pytest.raises(Error): - load_crl(FILETYPE_PEM, b"hello, world") - - def test_get_issuer(self): - """ - 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) - assert isinstance(crl.get_issuer(), X509Name) - assert crl.get_issuer().CN == "Testing Root CA" - - def test_dump_crl(self): - """ - The dumped CRL matches the original input. - """ - crl = load_crl(FILETYPE_PEM, crlData) - buf = dump_crl(FILETYPE_PEM, crl) - assert buf == crlData - - @staticmethod - def _make_test_crl(issuer_cert, issuer_key, certs=()): - """ - Create a CRL. - - :param list[X509] certs: A list of certificates to revoke. - :rtype: 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") - # The year 5000 is far into the future so that this CRL isn't - # considered to have expired. - crl.set_nextUpdate(b"50000601000000Z") - crl.sign(issuer_cert, issuer_key, digest=b"sha512") - return crl - @staticmethod def _make_test_crl_cryptography(issuer_cert, issuer_key, certs=()): """ @@ -3407,20 +3004,7 @@ def _make_test_crl_cryptography(issuer_cert, issuer_key, certs=()): ) return crl - @pytest.mark.parametrize( - "create_crl", - [ - pytest.param( - _make_test_crl.__func__, - id="pyOpenSSL CRL", - ), - pytest.param( - _make_test_crl_cryptography.__func__, - id="cryptography CRL", - ), - ], - ) - def test_verify_with_revoked(self, create_crl): + def test_verify_with_revoked(self): """ `verify_certificate` raises error when an intermediate certificate is revoked. @@ -3428,10 +3012,10 @@ def test_verify_with_revoked(self, create_crl): store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) - root_crl = create_crl( + root_crl = self._make_test_crl_cryptography( self.root_cert, self.root_key, certs=[self.intermediate_cert] ) - intermediate_crl = create_crl( + intermediate_crl = self._make_test_crl_cryptography( self.intermediate_cert, self.intermediate_key, certs=[] ) store.add_crl(root_crl) @@ -3444,20 +3028,7 @@ def test_verify_with_revoked(self, create_crl): store_ctx.verify_certificate() assert str(err.value) == "certificate revoked" - @pytest.mark.parametrize( - "create_crl", - [ - pytest.param( - _make_test_crl.__func__, - id="pyOpenSSL CRL", - ), - pytest.param( - _make_test_crl_cryptography.__func__, - id="cryptography CRL", - ), - ], - ) - def test_verify_with_missing_crl(self, create_crl): + def test_verify_with_missing_crl(self): """ `verify_certificate` raises error when an intermediate certificate's CRL is missing. @@ -3465,7 +3036,7 @@ def test_verify_with_missing_crl(self, create_crl): store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) - root_crl = create_crl( + root_crl = self._make_test_crl_cryptography( self.root_cert, self.root_key, certs=[self.intermediate_cert] ) store.add_crl(root_crl) @@ -3478,20 +3049,6 @@ def test_verify_with_missing_crl(self, create_crl): assert str(err.value) == "unable to get certificate CRL" assert err.value.certificate.get_subject().CN == "intermediate-service" - def test_convert_from_cryptography(self): - crypto_crl = x509.load_pem_x509_crl(crlData) - crl = CRL.from_cryptography(crypto_crl) - assert isinstance(crl, CRL) - - def test_convert_from_cryptography_unsupported_type(self): - with pytest.raises(TypeError): - CRL.from_cryptography(object()) - - def test_convert_to_cryptography_key(self): - crl = load_crl(FILETYPE_PEM, crlData) - crypto_crl = crl.to_cryptography() - assert isinstance(crypto_crl, x509.CertificateRevocationList) - class TestX509StoreContext: """ @@ -3878,130 +3435,6 @@ def test_verify_with_partial_chain(self): assert store_ctx.verify_certificate() is None -class TestSignVerify: - """ - Tests for `OpenSSL.crypto.sign` and `OpenSSL.crypto.verify`. - """ - - def test_sign_verify(self): - """ - `sign` generates a cryptographic signature which `verify` can check. - """ - content = ( - b"It was a bright cold day in April, and the clocks were striking " - b"thirteen. Winston Smith, his chin nuzzled into his breast in an " - b"effort to escape the vile wind, slipped quickly through the " - b"glass doors of Victory Mansions, though not quickly enough to " - b"prevent a swirl of gritty dust from entering along with him." - ) - - # sign the content with this private key - priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) - # verify the content with this cert - good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) - # certificate unrelated to priv_key, used to trigger an error - bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem) - - for digest in ["md5", "sha1", "sha256"]: - sig = sign(priv_key, content, digest) - - # Verify the signature of content, will throw an exception if - # error. - verify(good_cert, sig, content, digest) - - # This should fail because the certificate doesn't match the - # private key that was used to sign the content. - with pytest.raises(Error): - verify(bad_cert, sig, content, digest) - - # This should fail because we've "tainted" the content after - # signing it. - with pytest.raises(Error): - verify(good_cert, sig, content + b"tainted", digest) - - # test that unknown digest types fail - with pytest.raises(ValueError): - sign(priv_key, content, "strange-digest") - with pytest.raises(ValueError): - verify(good_cert, sig, content, "strange-digest") - - def test_sign_verify_with_text(self): - """ - `sign` generates a cryptographic signature which - `verify` can check. Deprecation warnings raised because using - text instead of bytes as content - """ - content = ( - b"It was a bright cold day in April, and the clocks were striking " - b"thirteen. Winston Smith, his chin nuzzled into his breast in an " - b"effort to escape the vile wind, slipped quickly through the " - b"glass doors of Victory Mansions, though not quickly enough to " - b"prevent a swirl of gritty dust from entering along with him." - ).decode("ascii") - - priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) - cert = load_certificate(FILETYPE_PEM, root_cert_pem) - for digest in ["md5", "sha1", "sha256"]: - with pytest.warns(DeprecationWarning) as w: - warnings.simplefilter("always") - sig = sign(priv_key, content, digest) - assert ( - f"{WARNING_TYPE_EXPECTED} for data is no longer accepted, " - f"use bytes" - ) == str(w[-1].message) - - with pytest.warns(DeprecationWarning) as w: - warnings.simplefilter("always") - verify(cert, sig, content, digest) - assert ( - f"{WARNING_TYPE_EXPECTED} for data is no longer accepted, " - f"use bytes" - ) == str(w[-1].message) - - def test_sign_verify_ecdsa(self): - """ - `sign` generates a cryptographic signature which `verify` can check. - ECDSA Signatures in the X9.62 format may have variable length, - different from the length of the private key. - """ - content = ( - b"It was a bright cold day in April, and the clocks were striking " - b"thirteen. Winston Smith, his chin nuzzled into his breast in an " - b"effort to escape the vile wind, slipped quickly through the " - b"glass doors of Victory Mansions, though not quickly enough to " - b"prevent a swirl of gritty dust from entering along with him." - ) - priv_key = load_privatekey(FILETYPE_PEM, ec_root_key_pem) - cert = load_certificate(FILETYPE_PEM, ec_root_cert_pem) - sig = sign(priv_key, content, "sha256") - verify(cert, sig, content, "sha256") - - def test_sign_nulls(self): - """ - `sign` produces a signature for a string with embedded nulls. - """ - content = b"Watch out! \0 Did you see it?" - priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) - good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) - sig = sign(priv_key, content, "sha256") - verify(good_cert, sig, content, "sha256") - - def test_sign_with_large_key(self): - """ - `sign` produces a signature for a string when using a long key. - """ - content = ( - b"It was a bright cold day in April, and the clocks were striking " - b"thirteen. Winston Smith, his chin nuzzled into his breast in an " - b"effort to escape the vile wind, slipped quickly through the " - b"glass doors of Victory Mansions, though not quickly enough to " - b"prevent a swirl of gritty dust from entering along with him." - ) - - priv_key = load_privatekey(FILETYPE_PEM, large_key_pem) - sign(priv_key, content, "sha256") - - class TestEllipticCurve: """ Tests for `_EllipticCurve`, `get_elliptic_curve`, and