diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 36288e3b4..9c721d904 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ The third digit is only for regressions. Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Removed deprecated ``OpenSSL.crypto.X509Req``, ``OpenSSL.crypto.dump_certificate_request``, and ``OpenSSL.crypto.load_certificate_request``. ``cryptography.x509`` should be used instead. - ``OpenSSL.SSL.Connection.set_session`` now raises ``ValueError`` if the ``Session`` was obtained from a ``Connection`` that was using a different ``Context`` than this one. OpenSSL requires (but does not verify) that sessions only be re-used with a compatible ``SSL_CTX``, so this contract is now enforced. Deprecations: diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index f84e5d0a7..9f8b2de94 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -12,7 +12,7 @@ `pyca/cryptography`_ is likely a better choice than using this module. It contains a complete set of cryptographic primitives as well as a significantly better and more powerful X509 API. - If necessary you can convert to and from cryptography objects using the ``to_cryptography`` and ``from_cryptography`` methods on ``X509``, ``X509Req``, ``CRL``, and ``PKey``. + If necessary you can convert to and from cryptography objects using the ``to_cryptography`` and ``from_cryptography`` methods on ``X509``, ``CRL``, and ``PKey``. Elliptic curves @@ -42,13 +42,6 @@ Certificates .. autofunction:: load_certificate -Certificate signing requests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autofunction:: dump_certificate_request - -.. autofunction:: load_certificate_request - Private keys ~~~~~~~~~~~~ @@ -82,16 +75,6 @@ X509Name objects :special-members: :exclude-members: __repr__, __getattr__, __weakref__ -.. _openssl-x509req: - -X509Req objects ---------------- - -.. autoclass:: X509Req - :members: - :special-members: - :exclude-members: __weakref__ - .. _openssl-x509store: X509Store objects diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index a9eb219ec..d51396936 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -63,19 +63,16 @@ def deprecated(msg: str, **kwargs: object) -> Callable[[_T], _T]: "Error", "PKey", "X509Name", - "X509Req", "X509Store", "X509StoreContext", "X509StoreContextError", "X509StoreFlags", "dump_certificate", - "dump_certificate_request", "dump_privatekey", "dump_publickey", "get_elliptic_curve", "get_elliptic_curves", "load_certificate", - "load_certificate_request", "load_privatekey", "load_publickey", ] @@ -782,181 +779,6 @@ def get_components(self) -> list[tuple[bytes, bytes]]: return result -@deprecated( - "CSR support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." -) -class X509Req: - """ - An X.509 certificate signing requests. - - .. deprecated:: 24.2.0 - Use `cryptography.x509.CertificateSigningRequest` instead. - """ - - def __init__(self) -> None: - req = _lib.X509_REQ_new() - self._req = _ffi.gc(req, _lib.X509_REQ_free) - # Default to version 0. - self.set_version(0) - - def to_cryptography(self) -> x509.CertificateSigningRequest: - """ - Export as a ``cryptography`` certificate signing request. - - :rtype: ``cryptography.x509.CertificateSigningRequest`` - - .. versionadded:: 17.1.0 - """ - from cryptography.x509 import load_der_x509_csr - - der = _dump_certificate_request_internal(FILETYPE_ASN1, self) - - return load_der_x509_csr(der) - - @classmethod - def from_cryptography( - cls, crypto_req: x509.CertificateSigningRequest - ) -> X509Req: - """ - Construct based on a ``cryptography`` *crypto_req*. - - :param crypto_req: A ``cryptography`` X.509 certificate signing request - :type crypto_req: ``cryptography.x509.CertificateSigningRequest`` - - :rtype: X509Req - - .. versionadded:: 17.1.0 - """ - if not isinstance(crypto_req, x509.CertificateSigningRequest): - raise TypeError("Must be a certificate signing request") - - from cryptography.hazmat.primitives.serialization import Encoding - - der = crypto_req.public_bytes(Encoding.DER) - return _load_certificate_request_internal(FILETYPE_ASN1, der) - - def set_pubkey(self, pkey: PKey) -> None: - """ - Set the public key of the certificate signing request. - - :param pkey: The public key to use. - :type pkey: :py:class:`PKey` - - :return: ``None`` - """ - set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey) - _openssl_assert(set_result == 1) - - def get_pubkey(self) -> PKey: - """ - Get the public key of the certificate signing request. - - :return: The public key. - :rtype: :py:class:`PKey` - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.X509_REQ_get_pubkey(self._req) - _openssl_assert(pkey._pkey != _ffi.NULL) - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - def set_version(self, version: int) -> None: - """ - Set the version subfield (RFC 2986, section 4.1) of the certificate - request. - - :param int version: The version number. - :return: ``None`` - """ - if not isinstance(version, int): - raise TypeError("version must be an int") - if version != 0: - raise ValueError( - "Invalid version. The only valid version for X509Req is 0." - ) - set_result = _lib.X509_REQ_set_version(self._req, version) - _openssl_assert(set_result == 1) - - def get_version(self) -> int: - """ - Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate - request. - - :return: The value of the version subfield. - :rtype: :py:class:`int` - """ - return _lib.X509_REQ_get_version(self._req) - - def get_subject(self) -> X509Name: - """ - Return the subject of this certificate signing request. - - This creates a new :class:`X509Name` that wraps the underlying subject - name field on the certificate signing request. Modifying it will modify - the underlying signing request, and will have the effect of modifying - any other :class:`X509Name` that refers to this subject. - - :return: The subject of this certificate signing request. - :rtype: :class:`X509Name` - """ - name = X509Name.__new__(X509Name) - name._name = _lib.X509_REQ_get_subject_name(self._req) - _openssl_assert(name._name != _ffi.NULL) - - # The name is owned by the X509Req structure. As long as the X509Name - # Python object is alive, keep the X509Req Python object alive. - name._owner = self - - return name - - def sign(self, pkey: PKey, digest: str) -> None: - """ - Sign the certificate signing request with this key and digest type. - - :param pkey: The key pair to sign with. - :type pkey: :py:class:`PKey` - :param digest: The name of the message digest to use for the signature, - e.g. :py:data:`"sha256"`. - :type digest: :py:class:`str` - :return: ``None`` - """ - if pkey._only_public: - raise ValueError("Key has only public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj) - _openssl_assert(sign_result > 0) - - def verify(self, pkey: PKey) -> bool: - """ - Verifies the signature on this certificate signing request. - - :param PKey key: A public key. - - :return: ``True`` if the signature is correct. - :rtype: bool - - :raises OpenSSL.crypto.Error: If the signature is invalid or there is a - problem verifying the signature. - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - result = _lib.X509_REQ_verify(self._req, pkey._pkey) - if result <= 0: - _raise_current_error() - - return result - - class X509: """ An X.509 certificate. @@ -2117,95 +1939,3 @@ def load_privatekey( pkey = PKey.__new__(PKey) pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) return pkey - - -def dump_certificate_request(type: int, req: X509Req) -> bytes: - """ - Dump the certificate request *req* into a buffer string encoded with the - type *type*. - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param req: The certificate request to dump - :return: The buffer with the dumped certificate request in - - - .. deprecated:: 24.2.0 - Use `cryptography.x509.CertificateSigningRequest` instead. - """ - bio = _new_mem_buf() - - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_X509_REQ(bio, req._req) - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_X509_REQ_bio(bio, req._req) - elif type == FILETYPE_TEXT: - result_code = _lib.X509_REQ_print_ex(bio, req._req, 0, 0) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT" - ) - - _openssl_assert(result_code != 0) - - return _bio_to_string(bio) - - -_dump_certificate_request_internal = dump_certificate_request - -utils.deprecated( - dump_certificate_request, - __name__, - ( - "CSR support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, - name="dump_certificate_request", -) - - -def load_certificate_request(type: int, buffer: bytes) -> X509Req: - """ - Load a certificate request (X509Req) from the string *buffer* encoded with - the type *type*. - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the certificate request is stored in - :return: The X509Req object - - .. deprecated:: 24.2.0 - Use `cryptography.x509.load_der_x509_csr` or - `cryptography.x509.load_pem_x509_csr` instead. - """ - if isinstance(buffer, str): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - req = _lib.PEM_read_bio_X509_REQ(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - req = _lib.d2i_X509_REQ_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - _openssl_assert(req != _ffi.NULL) - - x509req = X509Req.__new__(X509Req) - x509req._req = _ffi.gc(req, _lib.X509_REQ_free) - return x509req - - -_load_certificate_request_internal = load_certificate_request - -utils.deprecated( - load_certificate_request, - __name__, - ( - "CSR support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, - name="load_certificate_request", -) diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 9fd63dbbf..61a0775cc 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -37,7 +37,6 @@ Error, PKey, X509Name, - X509Req, X509Store, X509StoreContext, X509StoreContextError, @@ -46,13 +45,11 @@ _Key, _PrivateKey, dump_certificate, - dump_certificate_request, dump_privatekey, dump_publickey, get_elliptic_curve, get_elliptic_curves, load_certificate, - load_certificate_request, load_privatekey, load_publickey, ) @@ -491,19 +488,6 @@ def utcnow() -> datetime: """ ) -cleartextCertificateRequestPEM = b"""-----BEGIN CERTIFICATE REQUEST----- -MIIBnjCCAQcCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQH -EwdDaGljYWdvMRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEXMBUGA1UEAxMORnJl -ZGVyaWNrIERlYW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANp6Y17WzKSw -BsUWkXdqg6tnXy8H8hA1msCMWpc+/2KJ4mbv5NyD6UD+/SqagQqulPbF/DFea9nA -E0zhmHJELcM8gUTIlXv/cgDWnmK4xj8YkjVUiCdqKRAKeuzLG1pGmwwF5lGeJpXN -xQn5ecR0UYSOWj6TTGXB9VyUMQzCClcBAgMBAAGgADANBgkqhkiG9w0BAQUFAAOB -gQAAJGuF/R/GGbeC7FbFW+aJgr9ee0Xbl6nlhu7pTe67k+iiKT2dsl2ti68MVTnu -Vrb3HUNqOkiwsJf6kCtq5oPn3QVYzTa76Dt2y3Rtzv6boRSlmlfrgS92GNma8JfR -oICQk3nAudi6zl1Dix3BCv1pUp5KMtGn3MeDEi6QFGy2rA== ------END CERTIFICATE REQUEST----- -""" - encryptedPrivateKeyPEM = b"""-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,9573604A18579E9E @@ -805,13 +789,10 @@ def x509_data() -> tuple[PKey, X509]: # Basic setup stuff to generate a certificate pkey = PKey() pkey.generate_key(TYPE_RSA, 2048) - req = X509Req() - req.set_pubkey(pkey) - # Authority good you have. - req.get_subject().commonName = "Yoda root CA" x509 = X509() subject = x509.get_subject() - subject.commonName = req.get_subject().commonName + # Authority good you have. + subject.commonName = "Yoda root CA" x509.set_issuer(subject) x509.set_pubkey(pkey) now = datetime.now() @@ -1391,170 +1372,6 @@ def test_set_attribute_failure(self) -> None: setattr(name, "O", b"x" * 512) -class TestX509Req: - """ - Tests for `OpenSSL.crypto.X509Req`. - """ - - def test_sign_with_ungenerated(self) -> None: - """ - `X509Req.sign` raises `ValueError` when passed a `PKey` with no parts. - """ - request = X509Req() - key = PKey() - with pytest.raises(ValueError): - request.sign(key, GOOD_DIGEST) - - def test_sign_with_public_key(self) -> None: - """ - `X509Req.sign` raises `ValueError` when passed a `PKey` with no private - part as the signing key. - """ - request = X509Req() - key = PKey() - key.generate_key(TYPE_RSA, 2048) - request.set_pubkey(key) - pub = request.get_pubkey() - with pytest.raises(ValueError): - request.sign(pub, GOOD_DIGEST) - - def test_sign_with_unknown_digest(self) -> None: - """ - `X509Req.sign` raises `ValueError` when passed a digest name which is - not known. - """ - request = X509Req() - key = PKey() - key.generate_key(TYPE_RSA, 2048) - with pytest.raises(ValueError): - request.sign(key, BAD_DIGEST) - - def test_sign(self) -> None: - """ - `X509Req.sign` succeeds when passed a private key object and a - valid digest function. `X509Req.verify` can be used to check - the signature. - """ - request = X509Req() - key = PKey() - key.generate_key(TYPE_RSA, 2048) - request.set_pubkey(key) - request.sign(key, GOOD_DIGEST) - # If the type has a verify method, cover that too. - if getattr(request, "verify", None) is not None: - pub = request.get_pubkey() - assert request.verify(pub) - # Make another key that won't verify. - key = PKey() - key.generate_key(TYPE_RSA, 2048) - with pytest.raises(Error): - request.verify(key) - - def test_construction(self) -> None: - """ - `X509Req` takes no arguments and returns an `X509Req` instance. - """ - request = X509Req() - assert isinstance(request, X509Req) - - def test_version(self) -> None: - """ - `X509Req.set_version` sets the X.509 version of the certificate - request. `X509Req.get_version` returns the X.509 version of the - certificate request. The only defined version is 0. - """ - request = X509Req() - assert request.get_version() == 0 - request.set_version(0) - assert request.get_version() == 0 - - def test_version_wrong_args(self) -> None: - """ - `X509Req.set_version` raises `TypeError` if called with a non-`int` - argument. - """ - request = X509Req() - with pytest.raises(TypeError): - request.set_version("foo") # type: ignore[arg-type] - with pytest.raises(ValueError): - request.set_version(2) - - def test_get_subject(self) -> None: - """ - `X509Req.get_subject` returns an `X509Name` for the subject of the - request and which is valid even after the request object is - otherwise dead. - """ - request = X509Req() - subject = request.get_subject() - assert isinstance(subject, X509Name) - subject.commonName = "foo" - assert request.get_subject().commonName == "foo" - del request - subject.commonName = "bar" - assert subject.commonName == "bar" - - def test_verify_wrong_args(self) -> None: - """ - `X509Req.verify` raises `TypeError` if passed anything other than a - `PKey` instance as its single argument. - """ - request = X509Req() - with pytest.raises(TypeError): - request.verify(object()) # type: ignore[arg-type] - - def test_verify_uninitialized_key(self) -> None: - """ - `X509Req.verify` raises `OpenSSL.crypto.Error` if called with a - `OpenSSL.crypto.PKey` which contains no key data. - """ - request = X509Req() - pkey = PKey() - with pytest.raises(Error): - request.verify(pkey) - - def test_verify_wrong_key(self) -> None: - """ - `X509Req.verify` raises `OpenSSL.crypto.Error` if called with a - `OpenSSL.crypto.PKey` which does not represent the public part of the - key which signed the request. - """ - request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, root_key_pem) - request.set_pubkey(pkey) - request.sign(pkey, GOOD_DIGEST) - another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem) - with pytest.raises(Error): - request.verify(another_pkey) - - def test_verify_success(self) -> None: - """ - `X509Req.verify` returns `True` if called with a `OpenSSL.crypto.PKey` - which represents the public part of the key which signed the request. - """ - request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, root_key_pem) - request.set_pubkey(pkey) - request.sign(pkey, GOOD_DIGEST) - assert request.verify(pkey) - - def test_convert_from_cryptography(self) -> None: - crypto_req = x509.load_pem_x509_csr(cleartextCertificateRequestPEM) - req = X509Req.from_cryptography(crypto_req) - assert isinstance(req, X509Req) - - def test_convert_from_cryptography_unsupported_type(self) -> None: - with pytest.raises(TypeError): - X509Req.from_cryptography(object()) # type: ignore[arg-type] - - def test_convert_to_cryptography_key(self) -> None: - req = load_certificate_request( - FILETYPE_PEM, cleartextCertificateRequestPEM - ) - crypto_req = req.to_cryptography() - assert isinstance(crypto_req, x509.CertificateSigningRequest) - - class TestX509: """ Tests for `OpenSSL.crypto.X509`. @@ -1598,8 +1415,7 @@ def test_sign_with_unknown_digest(self) -> None: def test_sign(self) -> None: """ `X509.sign` succeeds when passed a private key object and a - valid digest function. `X509Req.verify` can be used to check - the signature. + valid digest function. """ cert = X509() key = PKey() @@ -2528,26 +2344,6 @@ def test_dump_publickey_invalid_type(self) -> None: with pytest.raises(ValueError): dump_publickey(FILETYPE_TEXT, key) - def test_dump_certificate_request(self) -> None: - """ - `dump_certificate_request` writes a PEM, DER, and text. - """ - req = load_certificate_request( - FILETYPE_PEM, cleartextCertificateRequestPEM - ) - dumped_pem = dump_certificate_request(FILETYPE_PEM, req) - assert dumped_pem == cleartextCertificateRequestPEM - dumped_der = dump_certificate_request(FILETYPE_ASN1, req) - good_der = _runopenssl(dumped_pem, b"req", b"-outform", b"DER") - assert dumped_der == good_der - req2 = load_certificate_request(FILETYPE_ASN1, dumped_der) - dumped_pem2 = dump_certificate_request(FILETYPE_PEM, req2) - assert dumped_pem2 == cleartextCertificateRequestPEM - dumped_text = dump_certificate_request(FILETYPE_TEXT, req) - assert len(dumped_text) > 500 - with pytest.raises(ValueError): - dump_certificate_request(100, req) - def test_dump_privatekey_passphrase_callback(self) -> None: """ `dump_privatekey` writes an encrypted PEM when given a callback @@ -2636,16 +2432,14 @@ def test_load_privatekey_truncated(self) -> None: class TestLoadCertificate: """ - Tests for `load_certificate_request`. + Tests for `load_certificate`. """ def test_bad_file_type(self) -> None: """ - If the file type passed to `load_certificate_request` is neither + If the file type passed to `load_certificate` is neither `FILETYPE_PEM` nor `FILETYPE_ASN1` then `ValueError` is raised. """ - with pytest.raises(ValueError): - load_certificate_request(object(), b"") # type: ignore[arg-type] with pytest.raises(ValueError): load_certificate(object(), b"") # type: ignore[arg-type]