Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b750adae9 | ||
|
|
57fef7df1a | ||
|
|
41ea7d2167 | ||
|
|
6f3eb0d2bc | ||
|
|
9b769cc478 | ||
|
|
1c0025495a | ||
|
|
0f30c67152 | ||
|
|
db95d6e893 | ||
|
|
18011e458f | ||
|
|
5656a80f0e |
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user