Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/development/test-vectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ Custom X.509 Vectors
* ``ca/ca.pem`` - A self-signed certificate with ``basicConstraints`` set to
true. Its private key is ``ca/ca_key.pem``. This certificate is encoded in
several of the PKCS12 custom vectors.
* ``issued.pem`` - A certificate signed by ``ca/ca.pem``.

* ``negative_serial.pem`` - A certificate with a serial number that is a
negative number.
* ``rsa_pss.pem`` - A certificate with an RSA PSS signature.
Expand Down
40 changes: 15 additions & 25 deletions docs/x509/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -459,31 +459,7 @@ X.509 Certificate Object
certificate validation is a complex problem that involves much more
than just signature checks.

To validate the signature on a certificate you can do the following.
Note: This only verifies that the certificate was signed with the
private key associated with the public key provided and does not
perform any of the other checks needed for secure certificate
validation. Additionally, this example will only work for RSA public
keys with ``PKCS1v15`` signatures, and so it can't be used for general
purpose signature verification.

.. doctest::

>>> from cryptography.hazmat.primitives.serialization import load_pem_public_key
>>> from cryptography.hazmat.primitives.asymmetric import padding
>>> issuer_public_key = load_pem_public_key(pem_issuer_public_key)
>>> cert_to_check = x509.load_pem_x509_certificate(pem_data_to_check)
>>> issuer_public_key.verify(
... cert_to_check.signature,
... cert_to_check.tbs_certificate_bytes,
... # Depends on the algorithm used to create the certificate
... padding.PKCS1v15(),
... cert_to_check.signature_hash_algorithm,
... )

An
:class:`~cryptography.exceptions.InvalidSignature`
exception will be raised if the signature fails to verify.
To validate the signature on a certificate, :meth:`check_issued` can be called.

.. method:: public_bytes(encoding)

Expand All @@ -496,6 +472,16 @@ X.509 Certificate Object
:return bytes: The data that can be written to a file or sent
over the network to be verified by clients.

.. method:: check_issued(issuer)

.. versionadded:: 3.1

This function checks if certificate subject was issued using CA certificate issuer.

:param issuer: The :class:`~cryptography.x509.Certificate` that is checked to be the issuer.

An :class:`~cryptography.x509.CheckIssuedFail` exception will be raised if the test fails.

X.509 CRL (Certificate Revocation List) Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -3282,6 +3268,10 @@ Exceptions

Returns the OID.

.. class:: CheckIssuedFail

This is raised when calling :meth:`Certificate.check_issued` and the certificate issued check fails.

.. class:: UnsupportedGeneralNameType

This is raised when a certificate contains an unsupported general name
Expand Down
1 change: 1 addition & 0 deletions src/_cffi_src/openssl/x509v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
void GENERAL_NAMES_free(GENERAL_NAMES *);
void *X509V3_EXT_d2i(X509_EXTENSION *);
int X509_check_ca(X509 *);
int X509_check_issued(X509 *issuer, X509 *subject);
/* X509 became a const arg in 1.1.0 */
void *X509_get_ext_d2i(X509 *, int, int *, int *);
/* The last two char * args became const char * in 1.1.0 */
Expand Down
14 changes: 14 additions & 0 deletions src/cryptography/hazmat/backends/openssl/x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@ def public_bytes(self, encoding: serialization.Encoding) -> bytes:
self._backend.openssl_assert(res == 1)
return self._backend._read_mem_bio(bio)

def check_issued(self, issuer):
if not isinstance(issuer, x509.Certificate):
raise TypeError("issuer must be a Certificate")
res = self._backend._lib.X509_check_issued(issuer._x509, self._x509)
if res != self._backend._lib.X509_V_OK:
error = self._backend._lib.X509_verify_cert_error_string(res)
self._backend.openssl_assert(error != self._backend._ffi.NULL)
error_str = self._backend._ffi.string(error).decode()
raise x509.CheckIssuedFail(
msg="Check issued of %s against %s failed: %s"
% (self.subject, issuer.subject, error_str),
err_code=res,
)


class _RevokedCertificate(x509.RevokedCertificate):
def __init__(self, backend, crl, x509_revoked):
Expand Down
11 changes: 11 additions & 0 deletions src/cryptography/x509/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def __init__(self, msg, oid):
self.oid = oid


class CheckIssuedFail(Exception):
def __init__(self, msg, err_code):
super(CheckIssuedFail, self).__init__(msg, err_code)


def _reject_duplicate_extension(
extension: Extension, extensions: typing.List[Extension]
):
Expand Down Expand Up @@ -183,6 +188,12 @@ def public_bytes(self, encoding: serialization.Encoding) -> bytes:
Serializes the certificate to PEM or DER format.
"""

@abc.abstractmethod
def check_issued(self, issuer):
"""
Verifies that the certificate was issued by the given issuer.
"""


class RevokedCertificate(metaclass=abc.ABCMeta):
@abc.abstractproperty
Expand Down
34 changes: 34 additions & 0 deletions tests/x509/test_x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -1821,6 +1821,40 @@ def read_next_rdn_value_tag(reader):
assert read_next_rdn_value_tag(issuer) == PRINTABLE_STRING


class TestCertificate(object):
@pytest.mark.requires_backend_interface(interface=X509Backend)
def test_check_issued(self, backend):
ca = _load_cert(
os.path.join("x509", "custom", "ca", "ca.pem"),
x509.load_pem_x509_certificate,
backend,
)
issuer = _load_cert(
os.path.join("x509", "custom", "issued.pem"),
x509.load_pem_x509_certificate,
backend,
)
assert ca.check_issued(ca) is None
assert issuer.check_issued(ca) is None
with pytest.raises(x509.CheckIssuedFail) as exc:
assert not ca.check_issued(issuer)
expected = (
"Check issued of <Name(C=US,CN=cryptography CA)> against "
"<Name(C=US,ST=Some-State,O=Internet Widgits Pty Ltd,"
"CN=cryptography CA)> failed: subject issuer mismatch"
)
assert str(exc.value) == expected
with pytest.raises(x509.CheckIssuedFail) as exc:
assert not issuer.check_issued(issuer)
expected = (
"Check issued of <Name(C=US,ST=Some-State,O=Internet "
"Widgits Pty Ltd,CN=cryptography CA)> against <Name(C=US,"
"ST=Some-State,O=Internet Widgits Pty Ltd,CN=cryptography "
"CA)> failed: subject issuer mismatch"
)
assert str(exc.value) == expected


class TestCertificateBuilder(object):
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
Expand Down
15 changes: 15 additions & 0 deletions vectors/cryptography_vectors/x509/custom/issued.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICSzCCAfICFElVAs6E6S9mglY+RZeAMVXWBBsTMAoGCCqGSM49BAMCMCcxCzAJ
BgNVBAYTAlVTMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkgQ0EwHhcNMjAwODAxMTQ1
MDAxWhcNMjAwODMxMTQ1MDAxWjBfMQswCQYDVQQGEwJVUzETMBEGA1UECAwKU29t
ZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRgwFgYD
VQQDDA9jcnlwdG9ncmFwaHkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDPUffRLgARF+WtPcbnijnIxOXd7zDzf4hXIvkVmwMiHEAXFZ03J5a9hrXB
o9ZG6c9btmjrDFQeoiaCAQRM8lnS6xo38uWVCk9+0nXNMQ37S6z/pWFBsVIDk7NH
EXzC8qtvSffk1QOHT+iftOuLHCK6y2GlvKDipoUVq+7lq+xMsF7kR26PdcvXmtoB
fsmlsn2C4aRKhj72ZaUG79wun4moFbMClPnH7Zbkwgk/dGdOp2kWvV70lsv1mD7Z
+8BGEQy5UJc0NpFK53Jp4o5dR+7QXOSrKX1W25rOdZs7bej/pVy/y+XGIaOiPXsA
9ph696wLf6uYV0y+UFWB6StEx7IdAgMBAAEwCgYIKoZIzj0EAwIDRwAwRAIgLOzh
yR1JMtA76DbDJq7jHENFOsFCoyB3I5taP0bZmx4CICHAnFsvak7sdUFyQjmQY9Er
v9JGjMQQUWRJehAl4jXY
-----END CERTIFICATE-----