Compare commits

...

10 Commits

Author SHA1 Message Date
Will Greenberg
7b750adae9 rm trailing whitespace 2024-03-25 16:57:23 -07:00
Will Greenberg
57fef7df1a Remove unsupported union type syntax 2024-03-25 15:47:06 -07:00
Will Greenberg
41ea7d2167 acme: remove kluge for older cryptography support 2024-03-25 15:46:52 -07:00
Will Greenberg
6f3eb0d2bc Update oldest pyOpenSSL, which also bumps cryptography 2024-03-25 15:46:33 -07:00
Will Greenberg
9b769cc478 Update oldest cryptography version to 36.0.0 2024-03-25 15:09:13 -07:00
Will Greenberg
1c0025495a acme: use kluge to avoid much newer cryptography version 2024-03-25 14:51:03 -07:00
Will Greenberg
0f30c67152 acme: appease mypy 2024-03-25 12:37:36 -07:00
Will Greenberg
db95d6e893 Remove unnecessary test, remove subtype that doesn't work on 3.8 2024-03-21 18:11:39 -07:00
Will Greenberg
18011e458f Remove usage of deprecated pyOpenSSL API
Also generally switch to using `cryptography` types internally where
possible, and switch to the new make_self_signed_cert function.
2024-03-21 18:01:00 -07:00
Will Greenberg
5656a80f0e acme.crypto_util: deprecate gen_ss_cert, add make_self_signed_cert
gen_ss_cert includes deprecated pyOpenSSL API in its function signature,
so we can't update it without breaking backwards compatibility. A new
function, make_self_signed_cert, with nearly identical signature (except
for its use of cryptography's extension types) is added for users to
migrate to.
2024-03-21 18:01:00 -07:00
9 changed files with 286 additions and 163 deletions

View File

@@ -8,9 +8,11 @@ import threading
import time
from typing import List
import unittest
import warnings
import josepy as jose
import OpenSSL
from cryptography import x509
import pytest
from acme import errors
@@ -219,10 +221,59 @@ class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase):
['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']
class GenMakeSelfSignedCertTest(unittest.TestCase):
"""Test for make_self_signed_cert."""
def setUp(self):
self.cert_count = 5
self.serial_num: List[int] = []
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
def test_sn_collisions(self):
from acme.crypto_util import make_self_signed_cert
for _ in range(self.cert_count):
cert = make_self_signed_cert(self.key, ['dummy'], force_san=True,
ips=[ipaddress.ip_address("10.10.10.10")])
self.serial_num.append(cert.get_serial_number())
assert len(set(self.serial_num)) >= self.cert_count
def test_no_name(self):
from acme.crypto_util import make_self_signed_cert
with pytest.raises(AssertionError):
make_self_signed_cert(self.key, ips=[ipaddress.ip_address("1.1.1.1")])
make_self_signed_cert(self.key)
def test_fail_with_public_key(self):
from acme.crypto_util import make_self_signed_cert
from acme.errors import Error
pubkey_bytes = OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_PEM, self.key)
pubkey = OpenSSL.crypto.load_publickey(OpenSSL.crypto.FILETYPE_PEM, pubkey_bytes)
with pytest.raises(Error):
make_self_signed_cert(pubkey, ips=[ipaddress.ip_address("1.1.1.1")])
def test_extensions(self):
from acme.crypto_util import make_self_signed_cert
extension_type = x509.TLSFeature([x509.TLSFeatureType.status_request])
extension = x509.Extension(
x509.TLSFeature.oid,
False,
extension_type
)
cert = make_self_signed_cert(
self.key,
ips=[ipaddress.ip_address("1.1.1.1")],
extensions=[extension]
)
# Since extensions in pyOpenSSL are deprecated, convert back to a
# cryptography type to check our extensions
cryptography_cert = cert.to_cryptography()
self.assertIn(extension, cryptography_cert.extensions)
class GenSsCertTest(unittest.TestCase):
"""Test for gen_ss_cert (generation of self-signed cert)."""
def setUp(self):
self.cert_count = 5
self.serial_num: List[int] = []
@@ -231,18 +282,27 @@ class GenSsCertTest(unittest.TestCase):
def test_sn_collisions(self):
from acme.crypto_util import gen_ss_cert
for _ in range(self.cert_count):
cert = gen_ss_cert(self.key, ['dummy'], force_san=True,
ips=[ipaddress.ip_address("10.10.10.10")])
self.serial_num.append(cert.get_serial_number())
assert len(set(self.serial_num)) >= self.cert_count
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
for _ in range(self.cert_count):
cert = gen_ss_cert(self.key, ['dummy'], force_san=True,
ips=[ipaddress.ip_address("10.10.10.10")])
self.serial_num.append(cert.get_serial_number())
assert len(set(self.serial_num)) >= self.cert_count
def test_no_ips(self):
from acme.crypto_util import gen_ss_cert
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
gen_ss_cert(self.key, domains=['a.example'])
def test_no_name(self):
from acme.crypto_util import gen_ss_cert
with pytest.raises(AssertionError):
gen_ss_cert(self.key, ips=[ipaddress.ip_address("1.1.1.1")])
gen_ss_cert(self.key)
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
with pytest.raises(AssertionError):
gen_ss_cert(self.key, ips=[ipaddress.ip_address("1.1.1.1")])
gen_ss_cert(self.key)
class MakeCSRTest(unittest.TestCase):
@@ -260,70 +320,42 @@ class MakeCSRTest(unittest.TestCase):
csr_pem = self._call_with_key(["a.example", "b.example"])
assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem
assert b'--END CERTIFICATE REQUEST--' in csr_pem
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
# In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
assert len(csr.get_extensions()) == 1
assert csr.get_extensions()[0].get_data() == \
OpenSSL.crypto.X509Extension(
b'subjectAltName',
critical=False,
value=b'DNS:a.example, DNS:b.example',
).get_data()
csr = x509.load_pem_x509_csr(csr_pem)
assert len(csr.extensions) == 1
self.assertEqual(
csr.extensions[0].value,
x509.SubjectAlternativeName([
x509.DNSName('a.example'),
x509.DNSName('b.example'),
])
)
def test_make_csr_ip(self):
csr_pem = self._call_with_key(["a.example"], False, [ipaddress.ip_address('127.0.0.1'), ipaddress.ip_address('::1')])
assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem
assert b'--END CERTIFICATE REQUEST--' in csr_pem
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
# In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
assert len(csr.get_extensions()) == 1
assert csr.get_extensions()[0].get_data() == \
OpenSSL.crypto.X509Extension(
b'subjectAltName',
critical=False,
value=b'DNS:a.example, IP:127.0.0.1, IP:::1',
).get_data()
# for IP san it's actually need to be octet-string,
# but somewhere downstream thankfully handle it for us
csr = x509.load_pem_x509_csr(csr_pem)
assert len(csr.extensions) == 1
self.assertEqual(
csr.extensions[0].value,
x509.SubjectAlternativeName([
x509.DNSName('a.example'),
x509.IPAddress(ipaddress.ip_address('127.0.0.1')),
x509.IPAddress(ipaddress.ip_address('::1')),
])
)
def test_make_csr_must_staple(self):
csr_pem = self._call_with_key(["a.example"], must_staple=True)
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
# In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
assert len(csr.get_extensions()) == 2
# NOTE: Ideally we would filter by the TLS Feature OID, but
# OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID,
# and the shortname field is just "UNDEF"
must_staple_exts = [e for e in csr.get_extensions()
if e.get_data() == b"0\x03\x02\x01\x05"]
assert len(must_staple_exts) == 1, \
"Expected exactly one Must Staple extension"
csr = x509.load_pem_x509_csr(csr_pem)
assert len(csr.extensions) == 2
ext = csr.extensions.get_extension_for_class(x509.TLSFeature)
self.assertEqual(ext.value, x509.TLSFeature([x509.TLSFeatureType.status_request]))
def test_make_csr_without_hostname(self):
with pytest.raises(ValueError):
self._call_with_key()
def test_make_csr_correct_version(self):
csr_pem = self._call_with_key(["a.example"])
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
assert csr.get_version() == 0, \
"Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4"
class DumpPyopensslChainTest(unittest.TestCase):
"""Test for dump_pyopenssl_chain."""

View File

@@ -16,6 +16,7 @@ from typing import TypeVar
from typing import Union
from cryptography.hazmat.primitives import hashes
from cryptography import x509
import josepy as jose
from OpenSSL import crypto
from OpenSSL import SSL
@@ -410,7 +411,7 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse):
"""
ID_PE_ACME_IDENTIFIER_V1 = b"1.3.6.1.5.5.7.1.30.1"
ID_PE_ACME_IDENTIFIER_V1 = "1.3.6.1.5.5.7.1.30.1"
ACME_TLS_1_PROTOCOL = b"acme-tls/1"
@property
@@ -436,10 +437,14 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse):
key.generate_key(crypto.TYPE_RSA, bits)
der_value = b"DER:" + codecs.encode(self.h, 'hex')
acme_extension = crypto.X509Extension(self.ID_PE_ACME_IDENTIFIER_V1,
critical=True, value=der_value)
oid = x509.ObjectIdentifier(self.ID_PE_ACME_IDENTIFIER_V1)
acme_extension = x509.Extension(
oid,
critical=True,
value=x509.UnrecognizedExtension(oid, der_value)
)
return crypto_util.gen_ss_cert(key, [domain], force_san=True,
return crypto_util.make_self_signed_cert(key, [domain], force_san=True,
extensions=[acme_extension]), key
def probe_cert(self, domain: str, host: Optional[str] = None,
@@ -479,13 +484,15 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse):
if len(names) != 1 or names[0].lower() != domain.lower():
return False
for i in range(cert.get_extension_count()):
ext = cert.get_extension(i)
# FIXME: assume this is the ACME extension. Currently there is no
# way to get full OID of an unknown extension from pyopenssl.
if ext.get_short_name() == b'UNDEF':
data = ext.get_data()
return data == self.h
crypto_cert = cert.to_cryptography()
oid = x509.ObjectIdentifier(self.ID_PE_ACME_IDENTIFIER_V1)
der_value = b"DER:" + codecs.encode(self.h, 'hex')
try:
ext = crypto_cert.extensions.get_extension_for_oid(oid)
if ext is not None:
return ext.value.public_bytes() == der_value
except x509.ExtensionNotFound:
pass
return False

View File

@@ -1,6 +1,7 @@
"""Crypto utilities."""
import binascii
import contextlib
from datetime import datetime
import ipaddress
import logging
import os
@@ -8,6 +9,7 @@ import re
import socket
from typing import Any
from typing import Callable
from typing import cast
from typing import List
from typing import Mapping
from typing import Optional
@@ -15,7 +17,15 @@ from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Union
import warnings
from cryptography import x509
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.asymmetric.dsa import DSAPrivateKey
from cryptography.hazmat.primitives.asymmetric.types import CertificateIssuerPrivateKeyTypes
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import josepy as jose
from OpenSSL import crypto
from OpenSSL import SSL
@@ -237,10 +247,15 @@ def make_csr(private_key_pem: bytes, domains: Optional[Union[Set[str], List[str]
params ordered this way for backward competablity when called by positional argument.
:returns: buffer PEM-encoded Certificate Signing Request.
"""
private_key = crypto.load_privatekey(
crypto.FILETYPE_PEM, private_key_pem)
csr = crypto.X509Req()
sanlist = []
# builder.sign(...) below will error out if this isn't the correct private key type
private_key: CertificateIssuerPrivateKeyTypes = cast(
CertificateIssuerPrivateKeyTypes,
load_pem_private_key(private_key_pem, None)
)
builder = x509.CertificateSigningRequestBuilder()
# set an empty subject name
builder = builder.subject_name(x509.Name([]))
sanlist: List[x509.GeneralName] = []
# if domain or ip list not supplied make it empty list so it's easier to iterate
if domains is None:
domains = []
@@ -249,32 +264,18 @@ def make_csr(private_key_pem: bytes, domains: Optional[Union[Set[str], List[str]
if len(domains)+len(ipaddrs) == 0:
raise ValueError("At least one of domains or ipaddrs parameter need to be not empty")
for address in domains:
sanlist.append('DNS:' + address)
sanlist.append(x509.DNSName(address))
for ips in ipaddrs:
sanlist.append('IP:' + ips.exploded)
# make sure its ascii encoded
san_string = ', '.join(sanlist).encode('ascii')
# for IP san it's actually need to be octet-string,
# but somewhere downsteam thankfully handle it for us
extensions = [
crypto.X509Extension(
b'subjectAltName',
critical=False,
value=san_string
),
]
sanlist.append(x509.IPAddress(ips))
builder = builder.add_extension(x509.SubjectAlternativeName(sanlist), critical=False)
if must_staple:
extensions.append(crypto.X509Extension(
b"1.3.6.1.5.5.7.1.24",
critical=False,
value=b"DER:30:03:02:01:05"))
csr.add_extensions(extensions)
csr.set_pubkey(private_key)
# RFC 2986 Section 4.1 only defines version 0
csr.set_version(0)
csr.sign(private_key, 'sha256')
return crypto.dump_certificate_request(
crypto.FILETYPE_PEM, csr)
builder = builder.add_extension(
x509.TLSFeature([x509.TLSFeatureType.status_request]),
critical=False
)
csr = builder.sign(private_key, SHA256())
return csr.public_bytes(Encoding.PEM)
def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req: Union[crypto.X509, crypto.X509Req]
@@ -366,6 +367,78 @@ def _pyopenssl_extract_san_list_raw(cert_or_req: Union[crypto.X509, crypto.X509R
return sans_parts
def make_self_signed_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
not_before: Optional[int] = None,
validity: int = (7 * 24 * 60 * 60), force_san: bool = True,
extensions: Optional[List[x509.Extension]] = None,
ips: Optional[List[Union[ipaddress.IPv4Address,
ipaddress.IPv6Address]]] = None
) -> crypto.X509:
"""Generate new self-signed certificate.
:type domains: `list` of `str`
:param OpenSSL.crypto.PKey key:
:param bool force_san:
:param extensions: List of additional extensions to include in the cert.
:type extensions: `list` of `x509.Extension[x509.ExtensionType]`
:type ips: `list` of (`ipaddress.IPv4Address` or `ipaddress.IPv6Address`)
If more than one domain is provided, all of the domains are put into
``subjectAltName`` X.509 extension and first domain is set as the
subject CN. If only one domain is provided no ``subjectAltName``
extension is used, unless `force_san` is ``True``.
"""
assert domains or ips, "Must provide one or more hostnames or IPs for the cert."
builder = x509.CertificateBuilder()
builder = builder.serial_number(int(binascii.hexlify(os.urandom(16)), 16))
if extensions is not None:
for ext in extensions:
builder = builder.add_extension(ext.value, ext.critical)
if domains is None:
domains = []
if ips is None:
ips = []
builder = builder.add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True)
name_attrs = []
if len(domains) > 0:
name_attrs.append(x509.NameAttribute(
x509.OID_COMMON_NAME,
domains[0]
))
builder = builder.subject_name(x509.Name(name_attrs))
builder = builder.issuer_name(x509.Name(name_attrs))
sanlist: List[x509.GeneralName] = []
for address in domains:
sanlist.append(x509.DNSName(address))
for ip in ips:
sanlist.append(x509.IPAddress(ip))
if force_san or len(domains) > 1 or len(ips) > 0:
builder = builder.add_extension(
x509.SubjectAlternativeName(sanlist),
critical=False
)
builder = builder.not_valid_before(datetime.fromtimestamp(
0 if not_before is None else not_before
))
builder = builder.not_valid_after(datetime.fromtimestamp(validity))
cryptography_priv = key.to_cryptography_key()
if not isinstance(cryptography_priv, (DSAPrivateKey, RSAPrivateKey)):
raise errors.Error("key must be a private key")
cryptography_pub = cryptography_priv.public_key()
builder = builder.public_key(cryptography_pub)
cryptography_cert = builder.sign(cryptography_priv, SHA256())
return crypto.X509.from_cryptography(cryptography_cert)
def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
not_before: Optional[int] = None,
validity: int = (7 * 24 * 60 * 60), force_san: bool = True,
@@ -386,7 +459,14 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
subject CN. If only one domain is provided no ``subjectAltName``
extension is used, unless `force_san` is ``True``.
.. deprecated: 2.10.0
"""
warnings.warn(
"acme.crypto_util.gen_ss_cert is deprecated and will be removed in the "
"next major release of Certbot. Please use "
"acme.crypto_util.make_self_signed_cert instead.", DeprecationWarning,
stacklevel=2
)
assert domains or ips, "Must provide one or more hostnames or IPs for the cert."
cert = crypto.X509()

View File

@@ -21,15 +21,17 @@ from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
import warnings
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import NoEncryption
from cryptography.hazmat.primitives.serialization import PrivateFormat
from cryptography.x509 import Certificate
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.primitives.serialization import NoEncryption
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.hashes import SHA256
from OpenSSL import crypto
import requests
@@ -210,35 +212,36 @@ def generate_csr(domains: Iterable[str], key_path: str, csr_path: str,
:param str csr_path: path to the CSR that will be generated
:param str key_type: type of the key (misc.RSA_KEY_TYPE or misc.ECDSA_KEY_TYPE)
"""
key: Union[ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey]
if key_type == RSA_KEY_TYPE:
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
# using public exponent 65537 as per
# https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
elif key_type == ECDSA_KEY_TYPE:
with warnings.catch_warnings():
# Ignore a warning on some old versions of cryptography
warnings.simplefilter('ignore', category=PendingDeprecationWarning)
_key = ec.generate_private_key(ec.SECP384R1(), default_backend())
_bytes = _key.private_bytes(encoding=Encoding.PEM,
format=PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=NoEncryption())
key = crypto.load_privatekey(crypto.FILETYPE_PEM, _bytes)
key = ec.generate_private_key(ec.SECP384R1(), default_backend())
else:
raise ValueError('Invalid key type: {0}'.format(key_type))
with open(key_path, 'wb') as file_h:
file_h.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
# TODO ensure this is exactly the same format that pyOpenSSL uses
file_h.write(key.private_bytes(
encoding=Encoding.PEM,
format=PrivateFormat.PKCS8,
encryption_algorithm=NoEncryption()
))
req = crypto.X509Req()
san = ', '.join('DNS:{0}'.format(item) for item in domains)
san_constraint = crypto.X509Extension(b'subjectAltName', False, san.encode('utf-8'))
req.add_extensions([san_constraint])
builder = x509.CertificateSigningRequestBuilder()
builder = builder.subject_name(x509.Name([]))
sans = [x509.DNSName(domain) for domain in domains]
builder = builder.add_extension(x509.SubjectAlternativeName(sans), critical=False)
req.set_pubkey(key)
req.set_version(0)
req.sign(key, 'sha256')
csr = builder.sign(key, SHA256())
with open(csr_path, 'wb') as file_h:
file_h.write(crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req))
file_h.write(csr.public_bytes(Encoding.DER))
def read_certificate(cert_path: str) -> str:
@@ -303,7 +306,7 @@ def echo(keyword: str, path: Optional[str] = None) -> str:
os.path.basename(sys.executable), keyword, ' >> "{0}"'.format(path) if path else '')
def get_acme_issuers(context: IntegrationTestsContext) -> List[Certificate]:
def get_acme_issuers(context: IntegrationTestsContext) -> List[x509.Certificate]:
"""Gets the list of one or more issuer certificates from the ACME server used by the
context.
:param context: the testing context.
@@ -320,6 +323,6 @@ def get_acme_issuers(context: IntegrationTestsContext) -> List[Certificate]:
request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediates/{}'.format(i),
verify=False,
timeout=10)
issuers.append(load_pem_x509_certificate(request.content, default_backend()))
issuers.append(x509.load_pem_x509_certificate(request.content, default_backend()))
return issuers

View File

@@ -147,7 +147,7 @@ def test_installer(args: argparse.Namespace, plugin: common.Proxy, config: str,
def test_deploy_cert(plugin: common.Proxy, temp_dir: str, domains: List[str]) -> bool:
"""Tests deploy_cert returning True if the tests are successful"""
cert = crypto_util.gen_ss_cert(util.KEY, domains)
cert = crypto_util.make_self_signed_cert(util.KEY, domains)
cert_path = os.path.join(temp_dir, "cert.pem")
with open(cert_path, "wb") as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))

View File

@@ -706,7 +706,7 @@ class NginxConfigurator(common.Configurator):
assert le_key.file is not None
key = OpenSSL.crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM, le_key.pem)
cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()])
cert = acme_crypto_util.make_self_signed_cert(key, domains=[socket.gethostname()])
cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert)
cert_file, cert_path = util.unique_file(

View File

@@ -6,11 +6,13 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
### Added
*
* Added `acme.crypto_util.make_self_signed_cert` to replace `acme.crypto_util.gen_ss_cert`, which
has the same behavior but accepts `cryptography`'s `x509.Extension` types.
### Changed
*
* Deprecated `acme.crypto_util.gen_ss_cert` due to its use of deprecated types from `pyOpenSSL` and
will be removed in the next major release.
### Fixed

View File

@@ -2,98 +2,97 @@
# that script.
apacheconfig==0.3.2 ; python_version >= "3.8" and python_version < "3.9"
asn1crypto==0.24.0 ; python_version >= "3.8" and python_version < "3.9"
astroid==3.0.1 ; python_version >= "3.8" and python_version < "3.9"
beautifulsoup4==4.12.2 ; python_version >= "3.8" and python_version < "3.9"
astroid==3.1.0 ; python_version >= "3.8" and python_version < "3.9"
beautifulsoup4==4.12.3 ; python_version >= "3.8" and python_version < "3.9"
boto3==1.15.15 ; python_version >= "3.8" and python_version < "3.9"
botocore==1.18.15 ; python_version >= "3.8" and python_version < "3.9"
cachetools==5.3.2 ; python_version >= "3.8" and python_version < "3.9"
certifi==2023.11.17 ; python_version >= "3.8" and python_version < "3.9"
cachetools==5.3.3 ; python_version >= "3.8" and python_version < "3.9"
certifi==2024.2.2 ; python_version >= "3.8" and python_version < "3.9"
cffi==1.12.3 ; python_version >= "3.8" and python_version < "3.9"
chardet==3.0.4 ; python_version >= "3.8" and python_version < "3.9"
cloudflare==1.5.1 ; python_version >= "3.8" and python_version < "3.9"
colorama==0.4.6 ; python_version >= "3.8" and python_version < "3.9" and sys_platform == "win32"
configargparse==1.5.3 ; python_version >= "3.8" and python_version < "3.9"
configobj==5.0.6 ; python_version >= "3.8" and python_version < "3.9"
coverage==7.3.2 ; python_version >= "3.8" and python_version < "3.9"
cryptography==3.2.1 ; python_version >= "3.8" and python_version < "3.9"
cython==0.29.36 ; python_version >= "3.8" and python_version < "3.9"
dill==0.3.7 ; python_version >= "3.8" and python_version < "3.9"
distlib==0.3.7 ; python_version >= "3.8" and python_version < "3.9"
coverage==7.4.4 ; python_version >= "3.8" and python_version < "3.9"
cryptography==41.0.5 ; python_version >= "3.8" and python_version < "3.9"
cython==0.29.37 ; python_version >= "3.8" and python_version < "3.9"
dill==0.3.8 ; python_version >= "3.8" and python_version < "3.9"
distlib==0.3.8 ; python_version >= "3.8" and python_version < "3.9"
distro==1.0.1 ; python_version >= "3.8" and python_version < "3.9"
dns-lexicon==3.15.1 ; python_version >= "3.8" and python_version < "3.9"
dnspython==1.15.0 ; python_version >= "3.8" and python_version < "3.9"
exceptiongroup==1.2.0 ; python_version >= "3.8" and python_version < "3.9"
execnet==2.0.2 ; python_version >= "3.8" and python_version < "3.9"
filelock==3.13.1 ; python_version >= "3.8" and python_version < "3.9"
filelock==3.13.3 ; python_version >= "3.8" and python_version < "3.9"
funcsigs==0.4 ; python_version >= "3.8" and python_version < "3.9"
future==0.18.3 ; python_version >= "3.8" and python_version < "3.9"
future==1.0.0 ; python_version >= "3.8" and python_version < "3.9"
google-api-python-client==1.6.5 ; python_version >= "3.8" and python_version < "3.9"
google-auth==2.16.0 ; python_version >= "3.8" and python_version < "3.9"
httplib2==0.9.2 ; python_version >= "3.8" and python_version < "3.9"
idna==2.6 ; python_version >= "3.8" and python_version < "3.9"
importlib-metadata==4.6.4 ; python_version >= "3.8" and python_version < "3.9"
importlib-resources==6.1.1 ; python_version >= "3.8" and python_version < "3.9"
importlib-resources==6.4.0 ; python_version >= "3.8" and python_version < "3.9"
iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "3.9"
ipaddress==1.0.16 ; python_version >= "3.8" and python_version < "3.9"
isort==5.12.0 ; python_version >= "3.8" and python_version < "3.9"
isort==5.13.2 ; python_version >= "3.8" and python_version < "3.9"
jmespath==0.10.0 ; python_version >= "3.8" and python_version < "3.9"
josepy==1.14.0 ; python_version >= "3.8" and python_version < "3.9"
logger==1.4 ; python_version >= "3.8" and python_version < "3.9"
mccabe==0.7.0 ; python_version >= "3.8" and python_version < "3.9"
mypy-extensions==1.0.0 ; python_version >= "3.8" and python_version < "3.9"
mypy==1.7.1 ; python_version >= "3.8" and python_version < "3.9"
mypy==1.9.0 ; python_version >= "3.8" and python_version < "3.9"
ndg-httpsclient==0.3.2 ; python_version >= "3.8" and python_version < "3.9"
oauth2client==4.1.3 ; python_version >= "3.8" and python_version < "3.9"
packaging==23.2 ; python_version >= "3.8" and python_version < "3.9"
packaging==24.0 ; python_version >= "3.8" and python_version < "3.9"
parsedatetime==2.4 ; python_version >= "3.8" and python_version < "3.9"
pbr==1.8.0 ; python_version >= "3.8" and python_version < "3.9"
pip==23.3.1 ; python_version >= "3.8" and python_version < "3.9"
platformdirs==4.0.0 ; python_version >= "3.8" and python_version < "3.9"
pluggy==1.3.0 ; python_version >= "3.8" and python_version < "3.9"
pip==24.0 ; python_version >= "3.8" and python_version < "3.9"
platformdirs==4.2.0 ; python_version >= "3.8" and python_version < "3.9"
pluggy==1.4.0 ; python_version >= "3.8" and python_version < "3.9"
ply==3.4 ; python_version >= "3.8" and python_version < "3.9"
py==1.11.0 ; python_version >= "3.8" and python_version < "3.9"
pyasn1-modules==0.3.0 ; python_version >= "3.8" and python_version < "3.9"
pyasn1==0.4.8 ; python_version >= "3.8" and python_version < "3.9"
pycparser==2.14 ; python_version >= "3.8" and python_version < "3.9"
pylint==3.0.2 ; python_version >= "3.8" and python_version < "3.9"
pyopenssl==17.5.0 ; python_version >= "3.8" and python_version < "3.9"
pylint==3.1.0 ; python_version >= "3.8" and python_version < "3.9"
pyopenssl==24.1.0 ; python_version >= "3.8" and python_version < "3.9"
pyotp==2.9.0 ; python_version >= "3.8" and python_version < "3.9"
pyparsing==2.2.1 ; python_version >= "3.8" and python_version < "3.9"
pyrfc3339==1.0 ; python_version >= "3.8" and python_version < "3.9"
pytest-cov==4.1.0 ; python_version >= "3.8" and python_version < "3.9"
pytest-cov==5.0.0 ; python_version >= "3.8" and python_version < "3.9"
pytest-xdist==3.5.0 ; python_version >= "3.8" and python_version < "3.9"
pytest==7.4.3 ; python_version >= "3.8" and python_version < "3.9"
pytest==8.1.1 ; python_version >= "3.8" and python_version < "3.9"
python-augeas==0.5.0 ; python_version >= "3.8" and python_version < "3.9"
python-dateutil==2.8.2 ; python_version >= "3.8" and python_version < "3.9"
python-dateutil==2.9.0.post0 ; python_version >= "3.8" and python_version < "3.9"
python-digitalocean==1.11 ; python_version >= "3.8" and python_version < "3.9"
pytz==2019.3 ; python_version >= "3.8" and python_version < "3.9"
pywin32==306 ; python_version >= "3.8" and python_version < "3.9" and sys_platform == "win32"
pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "3.9"
requests-file==1.5.1 ; python_version >= "3.8" and python_version < "3.9"
requests-file==2.0.0 ; python_version >= "3.8" and python_version < "3.9"
requests==2.20.0 ; python_version >= "3.8" and python_version < "3.9"
rsa==4.9 ; python_version >= "3.8" and python_version < "3.9"
s3transfer==0.3.7 ; python_version >= "3.8" and python_version < "3.9"
setuptools==41.6.0 ; python_version >= "3.8" and python_version < "3.9"
six==1.11.0 ; python_version >= "3.8" and python_version < "3.9"
soupsieve==2.5 ; python_version >= "3.8" and python_version < "3.9"
tldextract==5.1.1 ; python_version >= "3.8" and python_version < "3.9"
tldextract==5.1.2 ; python_version >= "3.8" and python_version < "3.9"
tomli==2.0.1 ; python_version >= "3.8" and python_version < "3.9"
tomlkit==0.12.3 ; python_version >= "3.8" and python_version < "3.9"
tomlkit==0.12.4 ; python_version >= "3.8" and python_version < "3.9"
tox==1.9.2 ; python_version >= "3.8" and python_version < "3.9"
types-cryptography==3.3.23.2 ; python_version >= "3.8" and python_version < "3.9"
types-httplib2==0.22.0.2 ; python_version >= "3.8" and python_version < "3.9"
types-pyopenssl==23.0.0.0 ; python_version >= "3.8" and python_version < "3.9"
types-httplib2==0.22.0.20240310 ; python_version >= "3.8" and python_version < "3.9"
types-pyopenssl==24.0.0.20240311 ; python_version >= "3.8" and python_version < "3.9"
types-pyrfc3339==1.1.1.5 ; python_version >= "3.8" and python_version < "3.9"
types-python-dateutil==2.8.19.14 ; python_version >= "3.8" and python_version < "3.9"
types-pytz==2023.3.1.1 ; python_version >= "3.8" and python_version < "3.9"
types-pywin32==306.0.0.6 ; python_version >= "3.8" and python_version < "3.9"
types-python-dateutil==2.9.0.20240316 ; python_version >= "3.8" and python_version < "3.9"
types-pytz==2024.1.0.20240203 ; python_version >= "3.8" and python_version < "3.9"
types-pywin32==306.0.0.20240319 ; python_version >= "3.8" and python_version < "3.9"
types-requests==2.31.0.6 ; python_version >= "3.8" and python_version < "3.9"
types-setuptools==69.0.0.0 ; python_version >= "3.8" and python_version < "3.9"
types-six==1.16.21.9 ; python_version >= "3.8" and python_version < "3.9"
types-setuptools==69.2.0.20240317 ; python_version >= "3.8" and python_version < "3.9"
types-six==1.16.21.20240311 ; python_version >= "3.8" and python_version < "3.9"
types-urllib3==1.26.25.14 ; python_version >= "3.8" and python_version < "3.9"
typing-extensions==4.8.0 ; python_version >= "3.8" and python_version < "3.9"
typing-extensions==4.10.0 ; python_version >= "3.8" and python_version < "3.9"
uritemplate==3.0.1 ; python_version >= "3.8" and python_version < "3.9"
urllib3==1.24.2 ; python_version >= "3.8" and python_version < "3.9"
virtualenv==20.25.0 ; python_version >= "3.8" and python_version < "3.9"
virtualenv==20.25.1 ; python_version >= "3.8" and python_version < "3.9"
wheel==0.33.6 ; python_version >= "3.8" and python_version < "3.9"
zipp==3.17.0 ; python_version >= "3.8" and python_version < "3.9"
zipp==3.18.1 ; python_version >= "3.8" and python_version < "3.9"

View File

@@ -52,7 +52,7 @@ cffi = "1.12.3"
chardet = "3.0.4"
cloudflare = "1.5.1"
configobj = "5.0.6"
cryptography = "3.2.1"
cryptography = "41.0.5"
distro = "1.0.1"
dns-lexicon = "3.15.1"
dnspython = "1.15.0"
@@ -67,7 +67,7 @@ ndg-httpsclient = "0.3.2"
parsedatetime = "2.4"
pbr = "1.8.0"
ply = "3.4"
pyOpenSSL = "17.5.0"
pyOpenSSL = "24.1.0"
pyRFC3339 = "1.0"
pyasn1 = "0.4.8"
pycparser = "2.14"