Skip to content

Commit bbcee8d

Browse files
authored
Add support for cryptography CRLs to X509Store (#1252)
* Fix type annotations for deprecated classes * Make `X509Store.add_crl()` accept cryptography CRLs
1 parent f2068f1 commit bbcee8d

File tree

3 files changed

+100
-14
lines changed

3 files changed

+100
-14
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ Deprecations:
3030
Changes:
3131
^^^^^^^^
3232

33+
- Changed ``OpenSSL.crypto.X509Store.add_crl`` to also accept
34+
``cryptography``'s ``X509.CertificateRevocationList`` arguments in addition
35+
to the now deprecated ``OpenSSL.crypto.CRL`` arguments.
36+
3337
23.2.0 (2023-05-30)
3438
-------------------
3539

src/OpenSSL/crypto.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,7 +1720,9 @@ def add_cert(self, cert: X509) -> None:
17201720
res = _lib.X509_STORE_add_cert(self._store, cert._x509)
17211721
_openssl_assert(res == 1)
17221722

1723-
def add_crl(self, crl: "CRL") -> None:
1723+
def add_crl(
1724+
self, crl: Union["_CRLInternal", x509.CertificateRevocationList]
1725+
) -> None:
17241726
"""
17251727
Add a certificate revocation list to this store.
17261728
@@ -1730,11 +1732,29 @@ def add_crl(self, crl: "CRL") -> None:
17301732
17311733
.. versionadded:: 16.1.0
17321734
1733-
:param CRL crl: The certificate revocation list to add to this store.
1735+
:param crl: The certificate revocation list to add to this store.
1736+
:type crl: ``Union[CRL, cryptography.x509.CertificateRevocationList]``
17341737
:return: ``None`` if the certificate revocation list was added
17351738
successfully.
17361739
"""
1737-
_openssl_assert(_lib.X509_STORE_add_crl(self._store, crl._crl) != 0)
1740+
if isinstance(crl, x509.CertificateRevocationList):
1741+
from cryptography.hazmat.primitives.serialization import Encoding
1742+
1743+
bio = _new_mem_buf(crl.public_bytes(Encoding.DER))
1744+
openssl_crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL)
1745+
if openssl_crl == _ffi.NULL:
1746+
_raise_current_error()
1747+
1748+
crl = _ffi.gc(openssl_crl, _lib.X509_CRL_free)
1749+
elif isinstance(crl, _CRLInternal):
1750+
crl = crl._crl
1751+
else:
1752+
raise TypeError(
1753+
"CRL must be of type OpenSSL.crypto.CRL or "
1754+
"cryptography.x509.CertificateRevocationList"
1755+
)
1756+
1757+
_openssl_assert(_lib.X509_STORE_add_crl(self._store, crl) != 0)
17381758

17391759
def set_flags(self, flags: int) -> None:
17401760
"""
@@ -2404,7 +2424,7 @@ def to_cryptography(self) -> x509.CertificateRevocationList:
24042424
@classmethod
24052425
def from_cryptography(
24062426
cls, crypto_crl: x509.CertificateRevocationList
2407-
) -> "CRL":
2427+
) -> "_CRLInternal":
24082428
"""
24092429
Construct based on a ``cryptography`` *crypto_crl*.
24102430
@@ -2445,7 +2465,7 @@ def get_revoked(self) -> Optional[Tuple[_RevokedInternal, ...]]:
24452465
return tuple(results)
24462466
return None
24472467

2448-
def add_revoked(self, revoked: Revoked) -> None:
2468+
def add_revoked(self, revoked: _RevokedInternal) -> None:
24492469
"""
24502470
Add a revoked (by value not reference) to the CRL structure
24512471
@@ -3222,7 +3242,7 @@ def verify(
32223242
)
32233243

32243244

3225-
def dump_crl(type: int, crl: CRL) -> bytes:
3245+
def dump_crl(type: int, crl: _CRLInternal) -> bytes:
32263246
"""
32273247
Dump a certificate revocation list to a buffer.
32283248
@@ -3264,7 +3284,7 @@ def dump_crl(type: int, crl: CRL) -> bytes:
32643284
)
32653285

32663286

3267-
def load_crl(type: int, buffer: Union[str, bytes]) -> CRL:
3287+
def load_crl(type: int, buffer: Union[str, bytes]) -> _CRLInternal:
32683288
"""
32693289
Load Certificate Revocation List (CRL) data from a string *buffer*.
32703290
*buffer* encoded with the type *type*.

tests/test_crypto.py

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import flaky
1414
import pytest
1515
from cryptography import x509
16-
from cryptography.hazmat.primitives import serialization
16+
from cryptography.hazmat.primitives import hashes, serialization
1717
from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa
1818

1919
from OpenSSL._util import ffi as _ffi
@@ -3505,7 +3505,8 @@ def test_dump_crl(self):
35053505
buf = dump_crl(FILETYPE_PEM, crl)
35063506
assert buf == crlData
35073507

3508-
def _make_test_crl(self, issuer_cert, issuer_key, certs=()):
3508+
@staticmethod
3509+
def _make_test_crl(issuer_cert, issuer_key, certs=()):
35093510
"""
35103511
Create a CRL.
35113512
@@ -3531,18 +3532,66 @@ def _make_test_crl(self, issuer_cert, issuer_key, certs=()):
35313532
crl.sign(issuer_cert, issuer_key, digest=b"sha512")
35323533
return crl
35333534

3534-
def test_verify_with_revoked(self):
3535+
@staticmethod
3536+
def _make_test_crl_cryptography(issuer_cert, issuer_key, certs=()):
3537+
"""
3538+
Create a CRL using cryptography's API.
3539+
3540+
:param list[X509] certs: A list of certificates to revoke.
3541+
:rtype: ``cryptography.x509.CertificateRevocationList``
3542+
"""
3543+
from cryptography.x509.extensions import CRLReason, ReasonFlags
3544+
3545+
builder = x509.CertificateRevocationListBuilder()
3546+
builder = builder.issuer_name(
3547+
X509.to_cryptography(issuer_cert).subject
3548+
)
3549+
for cert in certs:
3550+
revoked = (
3551+
x509.RevokedCertificateBuilder()
3552+
.serial_number(cert.get_serial_number())
3553+
.revocation_date(datetime(2014, 6, 1, 0, 0, 0))
3554+
.add_extension(CRLReason(ReasonFlags.unspecified), False)
3555+
.build()
3556+
)
3557+
builder = builder.add_revoked_certificate(revoked)
3558+
3559+
builder = builder.last_update(datetime(2014, 6, 1, 0, 0, 0))
3560+
# The year 5000 is far into the future so that this CRL isn't
3561+
# considered to have expired.
3562+
builder = builder.next_update(datetime(5000, 6, 1, 0, 0, 0))
3563+
3564+
crl = builder.sign(
3565+
private_key=PKey.to_cryptography_key(issuer_key),
3566+
algorithm=hashes.SHA512(),
3567+
)
3568+
return crl
3569+
3570+
@pytest.mark.parametrize(
3571+
"create_crl",
3572+
[
3573+
pytest.param(
3574+
_make_test_crl.__func__,
3575+
id="pyOpenSSL CRL",
3576+
),
3577+
pytest.param(
3578+
_make_test_crl_cryptography.__func__,
3579+
id="cryptography CRL",
3580+
),
3581+
],
3582+
)
3583+
def test_verify_with_revoked(self, create_crl):
35353584
"""
35363585
`verify_certificate` raises error when an intermediate certificate is
35373586
revoked.
35383587
"""
35393588
store = X509Store()
35403589
store.add_cert(self.root_cert)
35413590
store.add_cert(self.intermediate_cert)
3542-
root_crl = self._make_test_crl(
3591+
root_crl = create_crl(
35433592
self.root_cert, self.root_key, certs=[self.intermediate_cert]
35443593
)
3545-
intermediate_crl = self._make_test_crl(
3594+
intermediate_crl = create_crl(
35463595
self.intermediate_cert, self.intermediate_key, certs=[]
35473596
)
35483597
store.add_crl(root_crl)
@@ -3555,15 +3604,28 @@ def test_verify_with_revoked(self):
35553604
store_ctx.verify_certificate()
35563605
assert str(err.value) == "certificate revoked"
35573606

3558-
def test_verify_with_missing_crl(self):
3607+
@pytest.mark.parametrize(
3608+
"create_crl",
3609+
[
3610+
pytest.param(
3611+
_make_test_crl.__func__,
3612+
id="pyOpenSSL CRL",
3613+
),
3614+
pytest.param(
3615+
_make_test_crl_cryptography.__func__,
3616+
id="cryptography CRL",
3617+
),
3618+
],
3619+
)
3620+
def test_verify_with_missing_crl(self, create_crl):
35593621
"""
35603622
`verify_certificate` raises error when an intermediate certificate's
35613623
CRL is missing.
35623624
"""
35633625
store = X509Store()
35643626
store.add_cert(self.root_cert)
35653627
store.add_cert(self.intermediate_cert)
3566-
root_crl = self._make_test_crl(
3628+
root_crl = create_crl(
35673629
self.root_cert, self.root_key, certs=[self.intermediate_cert]
35683630
)
35693631
store.add_crl(root_crl)

0 commit comments

Comments
 (0)