diff --git a/acme/acme/_internal/tests/crypto_util_test.py b/acme/acme/_internal/tests/crypto_util_test.py index bd3151820..342b09d67 100644 --- a/acme/acme/_internal/tests/crypto_util_test.py +++ b/acme/acme/_internal/tests/crypto_util_test.py @@ -20,6 +20,13 @@ from acme import errors from acme._internal.tests import test_util +class FormatTest(unittest.TestCase): + def test_to_cryptography_encoding(self): + from acme.crypto_util import Format + assert Format.DER.to_cryptography_encoding() == serialization.Encoding.DER + assert Format.PEM.to_cryptography_encoding() == serialization.Encoding.PEM + + class SSLSocketAndProbeSNITest(unittest.TestCase): """Tests for acme.crypto_util.SSLSocket/probe_sni.""" diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 988d5c478..fb5428e87 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -1,6 +1,7 @@ """Crypto utilities.""" import binascii import contextlib +import enum import ipaddress import logging import os @@ -37,6 +38,24 @@ logger = logging.getLogger(__name__) _DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD +class Format(enum.IntEnum): + """File format to be used when parsing or serializing X.509 structures. + + Backwards compatible with the `FILETYPE_ASN1` and `FILETYPE_PEM` constants + from pyOpenSSL. + """ + DER = crypto.FILETYPE_ASN1 + PEM = crypto.FILETYPE_PEM + + def to_cryptography_encoding(self) -> serialization.Encoding: + """Converts the Format to the corresponding cryptography `Encoding`. + """ + if self == Format.DER: + return serialization.Encoding.DER + else: + return serialization.Encoding.PEM + + class _DefaultCertSelection: def __init__(self, certs: Mapping[bytes, Tuple[crypto.PKey, crypto.X509]]): self.certs = certs @@ -444,7 +463,7 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None, def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], List[crypto.X509]], - filetype: int = crypto.FILETYPE_PEM) -> bytes: + filetype: Union[Format, int] = Format.PEM) -> bytes: """Dump certificate chain into a bundle. :param list chain: List of `OpenSSL.crypto.X509` (or wrapped in @@ -457,12 +476,14 @@ def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], List[crypto.X50 # XXX: returns empty string when no chain is available, which # shuts up RenewableCert, but might not be the best solution... + filetype = Format(filetype) def _dump_cert(cert: Union[jose.ComparableX509, crypto.X509]) -> bytes: if isinstance(cert, jose.ComparableX509): if isinstance(cert.wrapped, crypto.X509Req): raise errors.Error("Unexpected CSR provided.") # pragma: no cover cert = cert.wrapped - return crypto.dump_certificate(filetype, cert) + + return cert.to_cryptography().public_bytes(filetype.to_cryptography_encoding()) # assumes that OpenSSL.crypto.dump_certificate includes ending # newline character diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index 90fc9b600..20b5f2d83 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -482,8 +482,10 @@ def get_names_from_req(csr: bytes, typ: int = crypto.FILETYPE_PEM) -> List[str]: return _get_names_from_cert_or_req(csr, crypto.load_certificate_request, typ) -def dump_pyopenssl_chain(chain: Union[List[crypto.X509], List[josepy.ComparableX509]], - filetype: int = crypto.FILETYPE_PEM) -> bytes: +def dump_pyopenssl_chain( + chain: Union[List[crypto.X509], List[josepy.ComparableX509]], + filetype: Union[acme_crypto_util.Format, int] = acme_crypto_util.Format.PEM, +) -> bytes: """Dump certificate chain into a bundle. :param list chain: List of `crypto.X509` (or wrapped in