diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 10ba9ec38455..35c00e613350 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -504,6 +504,26 @@ X.509 CRL (Certificate Revocation List) Object The extensions encoded in the CRL. + .. attribute:: signature + + .. versionadded:: 1.2 + + :type: bytes + + The bytes of the CRL's signature. + + .. attribute:: tbs_certlist_bytes + + .. versionadded:: 1.2 + + :type: bytes + + The DER encoded bytes payload (as defined by :rfc:`5280`) that is hashed + and then signed by the private key of the CRL's issuer. This data may be + used to validate a signature, but use extreme caution as CRL validation + is a complex problem that involves much more than just signature checks. + + X.509 Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index 2024101bacab..ebb78a3198d1 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -65,6 +65,7 @@ typedef struct { X509_CRL_INFO *crl; X509_ALGOR *sig_alg; + ASN1_BIT_STRING *signature; ...; } X509_CRL; @@ -259,6 +260,8 @@ MACROS = """ int i2d_X509_CINF(X509_CINF *, unsigned char **); +int i2d_X509_CRL_INFO(X509_CRL_INFO *, unsigned char **); + long X509_get_version(X509 *); ASN1_TIME *X509_get_notBefore(X509 *); diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 3afbc40f8842..8fa43ea8030f 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -818,6 +818,21 @@ def last_update(self): self._backend.openssl_assert(lu != self._backend._ffi.NULL) return self._backend._parse_asn1_time(lu) + @property + def signature(self): + return self._backend._asn1_string_to_bytes(self._x509_crl.signature) + + @property + def tbs_certlist_bytes(self): + pp = self._backend._ffi.new("unsigned char **") + # the X509_CRL_INFO struct holds the tbsCertList data + res = self._backend._lib.i2d_X509_CRL_INFO(self._x509_crl.crl, pp) + self._backend.openssl_assert(res > 0) + pp = self._backend._ffi.gc( + pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) + ) + return self._backend._ffi.buffer(pp[0], res)[:] + def _revoked_certificates(self): revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) self._backend.openssl_assert(revoked != self._backend._ffi.NULL) diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index ad561b94d744..6c2386f62944 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -194,6 +194,18 @@ def extensions(self): Returns an Extensions object containing a list of CRL extensions. """ + @abc.abstractproperty + def signature(self): + """ + Returns the signature bytes. + """ + + @abc.abstractproperty + def tbs_certlist_bytes(self): + """ + Returns the tbsCertList payload bytes as defined in RFC 5280. + """ + @abc.abstractmethod def __eq__(self, other): """ diff --git a/tests/test_x509.py b/tests/test_x509.py index c4457239e1f4..9b5dda6969c8 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -184,6 +184,44 @@ def test_extensions(self, backend): with pytest.raises(NotImplementedError): crl.extensions + def test_signature(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert crl.signature == binascii.unhexlify( + b"536a5a0794f68267361e7bc2f19167a3e667a2ab141535616855d8deb2ba1af" + b"9fd4546b1fe76b454eb436af7b28229fedff4634dfc9dd92254266219ae0ea8" + b"75d9ff972e9a2da23d5945f073da18c50a4265bfed9ca16586347800ef49dd1" + b"6856d7265f4f3c498a57f04dc04404e2bd2e2ada1f5697057aacef779a18371" + b"c621edc9a5c2b8ec1716e8fa22feeb7fcec0ce9156c8d344aa6ae8d1a5d99d0" + b"9386df36307df3b63c83908f4a61a0ff604c1e292ad63b349d1082ddd7ae1b7" + b"c178bba995523ec6999310c54da5706549797bfb1230f5593ba7b4353dade4f" + b"d2be13a57580a6eb20b5c4083f000abac3bf32cd8b75f23e4c8f4b3a79e1e2d" + b"58a472b0" + ) + + def test_tbs_certlist_bytes(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + ca_cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + verifier = ca_cert.public_key().verifier( + crl.signature, padding.PKCS1v15(), crl.signature_hash_algorithm + ) + verifier.update(crl.tbs_certlist_bytes) + verifier.verify() + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestRevokedCertificate(object):