Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-acme for openSUSE:Factory checked in at 2021-12-21 18:40:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-acme (Old) and /work/SRC/openSUSE:Factory/.python-acme.new.2520 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-acme" Tue Dec 21 18:40:53 2021 rev:55 rq:941879 version:1.20.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-acme/python-acme.changes 2021-08-11 11:49:27.129585202 +0200 +++ /work/SRC/openSUSE:Factory/.python-acme.new.2520/python-acme.changes 2021-12-21 18:41:33.741923967 +0100 @@ -1,0 +2,11 @@ +Mon Dec 13 17:27:51 UTC 2021 - Ferdinand Thiessen <r...@fthiessen.de> + +- Update to version 1.20.0 + * The acme library now supports requesting certificates for + IP addresses. + * Removed the dependency on chardet from the library. + Except for when downloading a certificate in an alternate format, + the acme library now assumes all server responses are UTF-8 + encoded which is required by RFC 8555. + +------------------------------------------------------------------- Old: ---- acme-1.18.0.tar.gz acme-1.18.0.tar.gz.asc New: ---- acme-1.20.0.tar.gz acme-1.20.0.tar.gz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-acme.spec ++++++ --- /var/tmp/diff_new_pack.65rXBD/_old 2021-12-21 18:41:34.285924455 +0100 +++ /var/tmp/diff_new_pack.65rXBD/_new 2021-12-21 18:41:34.285924455 +0100 @@ -20,7 +20,7 @@ %define skip_python2 1 %define libname acme Name: python-%{libname} -Version: 1.18.0 +Version: 1.20.0 Release: 0 Summary: Python library for the ACME protocol License: Apache-2.0 @@ -29,7 +29,7 @@ Source1: https://files.pythonhosted.org/packages/source/a/%{libname}/%{libname}-%{version}.tar.gz.asc Source2: %{name}.keyring BuildRequires: %{python_module cryptography >= 2.1.4} -BuildRequires: %{python_module josepy >= 1.1.0} +BuildRequires: %{python_module josepy >= 1.9.0} BuildRequires: %{python_module pyOpenSSL >= 17.3.0} BuildRequires: %{python_module pyRFC3339} BuildRequires: %{python_module pytest} @@ -40,7 +40,7 @@ BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-cryptography >= 2.1.4 -Requires: python-josepy >= 1.1.0 +Requires: python-josepy >= 1.9.0 Requires: python-pyOpenSSL >= 17.3.0 Requires: python-pyRFC3339 Requires: python-pytz ++++++ acme-1.18.0.tar.gz -> acme-1.20.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/PKG-INFO new/acme-1.20.0/PKG-INFO --- old/acme-1.18.0/PKG-INFO 2021-08-03 22:13:30.525709000 +0200 +++ new/acme-1.20.0/PKG-INFO 2021-10-05 15:53:03.645527800 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: acme -Version: 1.18.0 +Version: 1.20.0 Summary: ACME protocol implementation in Python Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme/challenges.py new/acme-1.20.0/acme/challenges.py --- old/acme-1.18.0/acme/challenges.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/acme/challenges.py 2021-10-05 15:52:55.000000000 +0200 @@ -7,10 +7,10 @@ import socket from typing import Type -from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.hazmat.primitives import hashes import josepy as jose from OpenSSL import crypto -from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 +from OpenSSL import SSL import requests from acme import crypto_util diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme/client.py new/acme-1.20.0/acme/client.py --- old/acme-1.18.0/acme/client.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/acme/client.py 2021-10-05 15:52:55.000000000 +0200 @@ -1,4 +1,7 @@ """ACME client API.""" +# pylint: disable=too-many-lines +# This pylint disable can be deleted once the deprecated ACMEv1 code is +# removed. import base64 import collections import datetime @@ -7,7 +10,9 @@ import http.client as http_client import logging import re +import sys import time +from types import ModuleType from typing import cast from typing import Dict from typing import List @@ -250,8 +255,6 @@ URI from which the resource will be downloaded. """ - warnings.warn("acme.client.Client (ACMEv1) is deprecated, " - "use acme.client.ClientV2 instead.", PendingDeprecationWarning) self.key = key if net is None: net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) @@ -655,11 +658,15 @@ csr = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr_pem) # pylint: disable=protected-access dnsNames = crypto_util._pyopenssl_cert_or_req_all_names(csr) - + ipNames = crypto_util._pyopenssl_cert_or_req_san_ip(csr) + # ipNames is now []string identifiers = [] for name in dnsNames: identifiers.append(messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=name)) + for ips in ipNames: + identifiers.append(messages.Identifier(typ=messages.IDENTIFIER_IP, + value=ips)) order = messages.NewOrder(identifiers=identifiers) response = self._post(self.directory['newOrder'], order) body = messages.Order.from_json(response.json()) @@ -830,8 +837,6 @@ """ def __init__(self, net, key, server): - warnings.warn("acme.client.BackwardsCompatibleClientV2 is deprecated, use " - "acme.client.ClientV2 instead.", PendingDeprecationWarning) directory = messages.Directory.from_json(net.get(server).json()) self.acme_version = self._acme_version_from_directory(directory) self.client: Union[Client, ClientV2] @@ -1149,13 +1154,23 @@ host, path, _err_no, err_msg = m.groups() raise ValueError("Requesting {0}{1}:{2}".format(host, path, err_msg)) - # If content is DER, log the base64 of it instead of raw bytes, to keep - # binary data out of the logs. + # If the Content-Type is DER or an Accept header was sent in the + # request, the response may not be UTF-8 encoded. In this case, we + # don't set response.encoding and log the base64 response instead of + # raw bytes to keep binary data out of the logs. This code can be + # simplified to only check for an Accept header in the request when + # ACMEv1 support is dropped. debug_content: Union[bytes, str] - if response.headers.get("Content-Type") == DER_CONTENT_TYPE: + if (response.headers.get("Content-Type") == DER_CONTENT_TYPE or + "Accept" in kwargs["headers"]): debug_content = base64.b64encode(response.content) else: - debug_content = response.content.decode("utf-8") + # We set response.encoding so response.text knows the response is + # UTF-8 encoded instead of trying to guess the encoding that was + # used which is error prone. This setting affects all future + # accesses of .text made on the returned response object as well. + response.encoding = "utf-8" + debug_content = response.text logger.debug('Received response:\nHTTP %d\n%s\n\n%s', response.status_code, "\n".join("{0}: {1}".format(k, v) @@ -1225,3 +1240,35 @@ response = self._check_response(response, content_type=content_type) self._add_nonce(response) return response + + +# This class takes a similar approach to the cryptography project to deprecate attributes +# in public modules. See the _ModuleWithDeprecation class here: +# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 +class _ClientDeprecationModule: + """ + Internal class delegating to a module, and displaying warnings when attributes + related to deprecated attributes in the acme.client module. + """ + def __init__(self, module): + self.__dict__['_module'] = module + + def __getattr__(self, attr): + if attr in ('Client', 'BackwardsCompatibleClientV2'): + warnings.warn('The {0} attribute in acme.client is deprecated ' + 'and will be removed soon.'.format(attr), + DeprecationWarning, stacklevel=2) + return getattr(self._module, attr) + + def __setattr__(self, attr, value): # pragma: no cover + setattr(self._module, attr, value) + + def __delattr__(self, attr): # pragma: no cover + delattr(self._module, attr) + + def __dir__(self): # pragma: no cover + return ['_module'] + dir(self._module) + + +# Patching ourselves to warn about deprecation and planned removal of some elements in the module. +sys.modules[__name__] = cast(ModuleType, _ClientDeprecationModule(sys.modules[__name__])) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme/crypto_util.py new/acme-1.20.0/acme/crypto_util.py --- old/acme-1.18.0/acme/crypto_util.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/acme/crypto_util.py 2021-10-05 15:52:55.000000000 +0200 @@ -11,7 +11,7 @@ import josepy as jose from OpenSSL import crypto -from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 +from OpenSSL import SSL from acme import errors @@ -24,7 +24,7 @@ # https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni # should be changed to use "set_options" to disable SSLv2 and SSLv3, # in case it's used for things other than probing/serving! -_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore +_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD class _DefaultCertSelection: @@ -169,7 +169,7 @@ ) if any(source_address) else "" ) socket_tuple: Tuple[str, int] = (host, port) - sock = socket.create_connection(socket_tuple, **socket_kwargs) # type: ignore + sock = socket.create_connection(socket_tuple, **socket_kwargs) except socket.error as error: raise errors.Error(error) @@ -187,23 +187,42 @@ return client_ssl.get_peer_certificate() -def make_csr(private_key_pem, domains, must_staple=False): - """Generate a CSR containing a list of domains as subjectAltNames. +def make_csr(private_key_pem, domains=None, must_staple=False, ipaddrs=None): + """Generate a CSR containing domains or IPs as subjectAltNames. :param buffer private_key_pem: Private key, in PEM PKCS#8 format. :param list domains: List of DNS names to include in subjectAltNames of CSR. :param bool must_staple: Whether to include the TLS Feature extension (aka OCSP Must Staple: https://tools.ietf.org/html/rfc7633). + :param list ipaddrs: List of IPaddress(type ipaddress.IPv4Address or ipaddress.IPv6Address) + names to include in subbjectAltNames of CSR. + 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 = [] + # if domain or ip list not supplied make it empty list so it's easier to iterate + if domains is None: + domains = [] + if ipaddrs is None: + ipaddrs = [] + 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) + 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=', '.join('DNS:' + d for d in domains).encode('ascii') + value=san_string ), ] if must_staple: @@ -220,6 +239,7 @@ def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req): + # unlike its name this only outputs DNS names, other type of idents will ignored common_name = loaded_cert_or_req.get_subject().CN sans = _pyopenssl_cert_or_req_san(loaded_cert_or_req) @@ -239,21 +259,57 @@ :param cert_or_req: Certificate or CSR. :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. - :returns: A list of Subject Alternative Names. + :returns: A list of Subject Alternative Names that is DNS. :rtype: `list` of `unicode` """ - # This function finds SANs by dumping the certificate/CSR to text and - # searching for "X509v3 Subject Alternative Name" in the text. This method - # is used to support PyOpenSSL version 0.13 where the - # `_subjectAltNameString` and `get_extensions` methods are not available - # for CSRs. + # This function finds SANs with dns name # constants based on PyOpenSSL certificate/CSR text dump part_separator = ":" - parts_separator = ", " prefix = "DNS" + part_separator + sans_parts = _pyopenssl_extract_san_list_raw(cert_or_req) + + return [part.split(part_separator)[1] + for part in sans_parts if part.startswith(prefix)] + + +def _pyopenssl_cert_or_req_san_ip(cert_or_req): + """Get Subject Alternative Names IPs from certificate or CSR using pyOpenSSL. + + :param cert_or_req: Certificate or CSR. + :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. + + :returns: A list of Subject Alternative Names that are IP Addresses. + :rtype: `list` of `unicode`. note that this returns as string, not IPaddress object + + """ + + # constants based on PyOpenSSL certificate/CSR text dump + part_separator = ":" + prefix = "IP Address" + part_separator + + sans_parts = _pyopenssl_extract_san_list_raw(cert_or_req) + + return [part[len(prefix):] for part in sans_parts if part.startswith(prefix)] + + +def _pyopenssl_extract_san_list_raw(cert_or_req): + """Get raw SAN string from cert or csr, parse it as UTF-8 and return. + + :param cert_or_req: Certificate or CSR. + :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. + + :returns: raw san strings, parsed byte as utf-8 + :rtype: `list` of `unicode` + + """ + # This function finds SANs by dumping the certificate/CSR to text and + # searching for "X509v3 Subject Alternative Name" in the text. This method + # is used to because in PyOpenSSL version <0.17 `_subjectAltNameString` methods are + # not able to Parse IP Addresses in subjectAltName string. + if isinstance(cert_or_req, crypto.X509): # pylint: disable=line-too-long func: Union[Callable[[int, crypto.X509Req], bytes], Callable[[int, crypto.X509], bytes]] = crypto.dump_certificate @@ -262,17 +318,17 @@ text = func(crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8") # WARNING: this function does not support multiple SANs extensions. # Multiple X509v3 extensions of the same type is disallowed by RFC 5280. - match = re.search(r"X509v3 Subject Alternative Name:(?: critical)?\s*(.*)", text) + raw_san = re.search(r"X509v3 Subject Alternative Name:(?: critical)?\s*(.*)", text) + + parts_separator = ", " # WARNING: this function assumes that no SAN can include # parts_separator, hence the split! - sans_parts = [] if match is None else match.group(1).split(parts_separator) - - return [part.split(part_separator)[1] - for part in sans_parts if part.startswith(prefix)] + sans_parts = [] if raw_san is None else raw_san.group(1).split(parts_separator) + return sans_parts -def gen_ss_cert(key, domains, not_before=None, - validity=(7 * 24 * 60 * 60), force_san=True, extensions=None): +def gen_ss_cert(key, domains=None, not_before=None, + validity=(7 * 24 * 60 * 60), force_san=True, extensions=None, ips=None): """Generate new self-signed certificate. :type domains: `list` of `unicode` @@ -280,6 +336,7 @@ :param bool force_san: :param extensions: List of additional extensions to include in the cert. :type extensions: `list` of `OpenSSL.crypto.X509Extension` + :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 @@ -287,28 +344,39 @@ extension is used, unless `force_san` is ``True``. """ - assert domains, "Must provide one or more hostnames for the cert." + assert domains or ips, "Must provide one or more hostnames or IPs for the cert." + cert = crypto.X509() cert.set_serial_number(int(binascii.hexlify(os.urandom(16)), 16)) cert.set_version(2) if extensions is None: extensions = [] - + if domains is None: + domains = [] + if ips is None: + ips = [] extensions.append( crypto.X509Extension( b"basicConstraints", True, b"CA:TRUE, pathlen:0"), ) - cert.get_subject().CN = domains[0] + if len(domains) > 0: + cert.get_subject().CN = domains[0] # TODO: what to put into cert.get_subject()? cert.set_issuer(cert.get_subject()) - if force_san or len(domains) > 1: + sanlist = [] + for address in domains: + sanlist.append('DNS:' + address) + for ip in ips: + sanlist.append('IP:' + ip.exploded) + san_string = ', '.join(sanlist).encode('ascii') + if force_san or len(domains) > 1 or len(ips) > 0: extensions.append(crypto.X509Extension( b"subjectAltName", critical=False, - value=b", ".join(b"DNS:" + d.encode() for d in domains) + value=san_string )) cert.add_extensions(extensions) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme/magic_typing.py new/acme-1.20.0/acme/magic_typing.py --- old/acme-1.18.0/acme/magic_typing.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/acme/magic_typing.py 2021-10-05 15:52:55.000000000 +0200 @@ -6,7 +6,7 @@ """ import warnings from typing import * # pylint: disable=wildcard-import, unused-wildcard-import -from typing import Collection, IO # type: ignore +from typing import Collection, IO warnings.warn("acme.magic_typing is deprecated and will be removed in a future release.", DeprecationWarning) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme/messages.py new/acme-1.20.0/acme/messages.py --- old/acme-1.18.0/acme/messages.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/acme/messages.py 2021-10-05 15:52:55.000000000 +0200 @@ -126,7 +126,7 @@ if part is not None).decode() -class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore +class _Constant(jose.JSONDeSerializable, Hashable): """ACME constant.""" __slots__ = ('name',) POSSIBLE_NAMES: Dict[str, '_Constant'] = NotImplemented @@ -172,7 +172,9 @@ class IdentifierType(_Constant): """ACME identifier type.""" POSSIBLE_NAMES: Dict[str, 'IdentifierType'] = {} + # class def ends here IDENTIFIER_FQDN = IdentifierType('dns') # IdentifierDNS in Boulder +IDENTIFIER_IP = IdentifierType('ip') # IdentifierIP in pebble - not in Boulder yet class Identifier(jose.JSONObjectWithFields): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme/util.py new/acme-1.20.0/acme/util.py --- old/acme-1.18.0/acme/util.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/acme/util.py 2021-10-05 15:52:55.000000000 +0200 @@ -1,6 +1,5 @@ """ACME utilities.""" - def map_keys(dikt, func): """Map dictionary keys.""" return {func(key): value for key, value in dikt.items()} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme.egg-info/PKG-INFO new/acme-1.20.0/acme.egg-info/PKG-INFO --- old/acme-1.18.0/acme.egg-info/PKG-INFO 2021-08-03 22:13:30.000000000 +0200 +++ new/acme-1.20.0/acme.egg-info/PKG-INFO 2021-10-05 15:53:03.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: acme -Version: 1.18.0 +Version: 1.20.0 Summary: ACME protocol implementation in Python Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme.egg-info/SOURCES.txt new/acme-1.20.0/acme.egg-info/SOURCES.txt --- old/acme-1.18.0/acme.egg-info/SOURCES.txt 2021-08-03 22:13:30.000000000 +0200 +++ new/acme-1.20.0/acme.egg-info/SOURCES.txt 2021-10-05 15:53:03.000000000 +0200 @@ -39,7 +39,6 @@ docs/api/standalone.rst docs/man/jws.rst examples/http01_example.py -examples/standalone/README tests/challenges_test.py tests/client_test.py tests/crypto_util_test.py @@ -55,6 +54,8 @@ tests/testdata/README tests/testdata/cert-100sans.pem tests/testdata/cert-idnsans.pem +tests/testdata/cert-ipsans.pem +tests/testdata/cert-ipv6sans.pem tests/testdata/cert-nocn.der tests/testdata/cert-san.pem tests/testdata/cert.der @@ -63,6 +64,9 @@ tests/testdata/csr-100sans.pem tests/testdata/csr-6sans.pem tests/testdata/csr-idnsans.pem +tests/testdata/csr-ipsans.pem +tests/testdata/csr-ipv6sans.pem +tests/testdata/csr-mixed.pem tests/testdata/csr-nosans.pem tests/testdata/csr-san.pem tests/testdata/csr.der diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/acme.egg-info/requires.txt new/acme-1.20.0/acme.egg-info/requires.txt --- old/acme-1.18.0/acme.egg-info/requires.txt 2021-08-03 22:13:30.000000000 +0200 +++ new/acme-1.20.0/acme.egg-info/requires.txt 2021-10-05 15:53:03.000000000 +0200 @@ -1,6 +1,5 @@ -chardet cryptography>=2.1.4 -josepy>=1.1.0 +josepy>=1.9.0 PyOpenSSL>=17.3.0 pyrfc3339 pytz diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/examples/standalone/README new/acme-1.20.0/examples/standalone/README --- old/acme-1.18.0/examples/standalone/README 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/examples/standalone/README 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -python -m acme.standalone -p 1234 -curl -k https://localhost:1234 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/setup.py new/acme-1.20.0/setup.py --- old/acme-1.18.0/setup.py 2021-08-03 22:12:59.000000000 +0200 +++ new/acme-1.20.0/setup.py 2021-10-05 15:52:56.000000000 +0200 @@ -3,18 +3,11 @@ from setuptools import find_packages from setuptools import setup -version = '1.18.0' +version = '1.20.0' install_requires = [ - # This dependency just exists to ensure that chardet is installed along - # with requests so it will use it instead of charset_normalizer. See - # https://github.com/certbot/certbot/issues/8964 for more info. - 'chardet', 'cryptography>=2.1.4', - # formerly known as acme.jose: - # 1.1.0+ is required to avoid the warnings described at - # https://github.com/certbot/josepy/issues/13. - 'josepy>=1.1.0', + 'josepy>=1.9.0', 'PyOpenSSL>=17.3.0', 'pyrfc3339', 'pytz', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/tests/client_test.py new/acme-1.20.0/tests/client_test.py --- old/acme-1.18.0/tests/client_test.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/tests/client_test.py 2021-10-05 15:52:55.000000000 +0200 @@ -3,6 +3,7 @@ import copy import datetime import http.client as http_client +import ipaddress import json import unittest from typing import Dict @@ -23,6 +24,7 @@ CERT_DER = test_util.load_vector('cert.der') CERT_SAN_PEM = test_util.load_vector('cert-san.pem') CSR_SAN_PEM = test_util.load_vector('csr-san.pem') +CSR_MIXED_PEM = test_util.load_vector('csr-mixed.pem') KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) KEY2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) @@ -740,7 +742,7 @@ self.orderr = messages.OrderResource( body=self.order, uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1', - authorizations=[self.authzr, self.authzr2], csr_pem=CSR_SAN_PEM) + authorizations=[self.authzr, self.authzr2], csr_pem=CSR_MIXED_PEM) def test_new_account(self): self.response.status_code = http_client.CREATED @@ -770,7 +772,7 @@ with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get: mock_post_as_get.side_effect = (authz_response, authz_response2) - self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr) + self.assertEqual(self.client.new_order(CSR_MIXED_PEM), self.orderr) @mock.patch('acme.client.datetime') def test_poll_and_finalize(self, mock_datetime): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/tests/crypto_util_test.py new/acme-1.20.0/tests/crypto_util_test.py --- old/acme-1.18.0/tests/crypto_util_test.py 2021-08-03 22:12:58.000000000 +0200 +++ new/acme-1.20.0/tests/crypto_util_test.py 2021-10-05 15:52:55.000000000 +0200 @@ -1,5 +1,6 @@ """Tests for acme.crypto_util.""" import itertools +import ipaddress import socket import socketserver import threading @@ -108,7 +109,6 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase): """Test for acme.crypto_util._pyopenssl_cert_or_req_san.""" - @classmethod def _call(cls, loader, name): # pylint: disable=protected-access @@ -174,9 +174,50 @@ ['chicago-cubs.venafi.example', 'cubs.venafi.example']) +class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase): + """Test for acme.crypto_util._pyopenssl_cert_or_req_san_ip.""" + + @classmethod + def _call(cls, loader, name): + # pylint: disable=protected-access + from acme.crypto_util import _pyopenssl_cert_or_req_san_ip + return _pyopenssl_cert_or_req_san_ip(loader(name)) + + def _call_cert(self, name): + return self._call(test_util.load_cert, name) + + def _call_csr(self, name): + return self._call(test_util.load_csr, name) + + def test_cert_no_sans(self): + self.assertEqual(self._call_cert('cert.pem'), []) + + def test_csr_no_sans(self): + self.assertEqual(self._call_csr('csr-nosans.pem'), []) + + def test_cert_domain_sans(self): + self.assertEqual(self._call_cert('cert-san.pem'), []) + + def test_csr_domain_sans(self): + self.assertEqual(self._call_csr('csr-san.pem'), []) + + def test_cert_ip_two_sans(self): + self.assertEqual(self._call_cert('cert-ipsans.pem'), ['192.0.2.145', '203.0.113.1']) -class RandomSnTest(unittest.TestCase): - """Test for random certificate serial numbers.""" + def test_csr_ip_two_sans(self): + self.assertEqual(self._call_csr('csr-ipsans.pem'), ['192.0.2.145', '203.0.113.1']) + + def test_csr_ipv6_sans(self): + self.assertEqual(self._call_csr('csr-ipv6sans.pem'), + ['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']) + + def test_cert_ipv6_sans(self): + self.assertEqual(self._call_cert('cert-ipv6sans.pem'), + ['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']) + + +class GenSsCertTest(unittest.TestCase): + """Test for gen_ss_cert (generation of self-signed cert).""" def setUp(self): @@ -187,11 +228,19 @@ 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) + 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()) - self.assertGreater(len(set(self.serial_num)), 1) + self.assertGreaterEqual(len(set(self.serial_num)), self.cert_count) + + + def test_no_name(self): + from acme.crypto_util import gen_ss_cert + with self.assertRaises(AssertionError): + gen_ss_cert(self.key, ips=[ipaddress.ip_address("1.1.1.1")]) + gen_ss_cert(self.key) + class MakeCSRTest(unittest.TestCase): """Test for standalone functions.""" @@ -223,6 +272,27 @@ ).get_data(), ) + 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')]) + self.assertIn(b'--BEGIN CERTIFICATE REQUEST--' , csr_pem) + self.assertIn(b'--END CERTIFICATE REQUEST--' , 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'): + self.assertEqual(len(csr.get_extensions()), 1) + self.assertEqual(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 + def test_make_csr_must_staple(self): csr_pem = self._call_with_key(["a.example"], must_staple=True) csr = OpenSSL.crypto.load_certificate_request( @@ -241,6 +311,9 @@ self.assertEqual(len(must_staple_exts), 1, "Expected exactly one Must Staple extension") + def test_make_csr_without_hostname(self): + self.assertRaises(ValueError, self._call_with_key) + class DumpPyopensslChainTest(unittest.TestCase): """Test for dump_pyopenssl_chain.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/tests/testdata/cert-ipsans.pem new/acme-1.20.0/tests/testdata/cert-ipsans.pem --- old/acme-1.18.0/tests/testdata/cert-ipsans.pem 1970-01-01 01:00:00.000000000 +0100 +++ new/acme-1.20.0/tests/testdata/cert-ipsans.pem 2021-10-05 15:52:55.000000000 +0200 @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDizCCAnOgAwIBAgIIPNBLQXwhoUkwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE +AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxNzNiMjYwHhcNMjAwNTI5MTkxODA5 +WhcNMjUwNTI5MTkxODA5WjAWMRQwEgYDVQQDEwsxOTIuMC4yLjE0NTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALyChb+NDA26GF1AfC0nzEdfOTchKw0h +q41xEjonvg5UXgZf/aH/ntvugIkYP0MaFifNAjebOVVsemEVEtyWcUKTfBHKZGbZ +ukTDwFIjfTccCfo6U/B2H7ZLzJIywl8DcUw9DypadeQBm8PS0VVR2ncy73dvaqym +crhAwlASyXU0mhLqRDMMxfg5Bn/FWpcsIcDpLmPn8Q/FvdRc2t5ryBNw/aWOlwqT +Oy16nbfLj2T0zG1A3aPuD+eT/JFUe/o3K7R+FAx7wt+RziQO46wLVVF1SueZUrIU +zqN04Gl8Kt1WM2SniZ0gq/rORUNcPtT0NAEsEslTQfA+Trq6j2peqyMCAwEAAaOB +yjCBxzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF +BwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHj1mwZzP//nMIH2i58NRUl/arHn +MB8GA1UdIwQYMBaAFF5DVAKabvIUvKFHGouscA2Qdpe6MDEGCCsGAQUFBwEBBCUw +IzAhBggrBgEFBQcwAYYVaHR0cDovLzEyNy4wLjAuMTo0MDAyMBUGA1UdEQQOMAyH +BMAAApGHBMsAcQEwDQYJKoZIhvcNAQELBQADggEBAHjSgDg76/UCIMSYddyhj18r +LdNKjA7p8ovnErSkebFT4lIZ9f3Sma9moNr0w64M33NamuFyHe/KTdk90mvoW8Uu +26aDekiRIeeMakzbAtDKn67tt2tbedKIYRATcSYVwsV46uZKbM621dZKIjjxOWpo +IY6rZYrku8LYhoXJXOqRduV3cTRVuTm5bBa9FfVNtt6N1T5JOtKKDEhuSaF4RSug +PDy3hQIiHrVvhPfVrXU3j6owz/8UCS5549inES9ONTFrvM9o0H1R/MsmGNXR5hF5 +iJqHKC7n8LZujhVnoFIpHu2Dsiefbfr+yRYJS4I+ezy6Nq/Ok8rc8zp0eoX+uyY= +-----END CERTIFICATE----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/tests/testdata/cert-ipv6sans.pem new/acme-1.20.0/tests/testdata/cert-ipv6sans.pem --- old/acme-1.18.0/tests/testdata/cert-ipv6sans.pem 1970-01-01 01:00:00.000000000 +0100 +++ new/acme-1.20.0/tests/testdata/cert-ipv6sans.pem 2021-10-05 15:52:55.000000000 +0200 @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDmzCCAoOgAwIBAgIIFdxeZP+v2rgwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE +AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA0M2M5NTcwHhcNMjAwNTMwMDQwNzMw +WhcNMjUwNTMwMDQwNzMwWjAOMQwwCgYDVQQDEwM6OjEwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC7VidVduJvqKtrSH0fw6PjE0cqL4Kfzo7klexWUkHG +KVAa0fRVZFZ462jxKOt417V2U4WJQ6WHHO9PJ+3gW62d/MhCw8FRtUQS4nYFjqB6 +32+RFU21VRN7cWoQEqSwnEPbh/v/zv/KS5JhQ+swWUo79AOLm1kjnZWCKtcqh1Lc +Ug5Tkpot6luoxTKp52MkchvXDpj0q2B/XpLJ8/pw5cqjv7mH12EDOK2HXllA+WwX +ZpstcEhaA4FqtaHOW/OHnwTX5MUbINXE5YYHVEDR6moVM31/W/3pe9NDUMTDE7Si +lVQnZbXM9NYbzZqlh+WhemDWwnIfGI6rtsfNEiirVEOlAgMBAAGjgeIwgd8wDgYD +VR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNV +HRMBAf8EAjAAMB0GA1UdDgQWBBS8DL+MZfDIy6AKky69Tgry2Vxq5DAfBgNVHSME +GDAWgBRAsFqVenRRKgB1YPzWKzb9bzZ/ozAxBggrBgEFBQcBAQQlMCMwIQYIKwYB +BQUHMAGGFWh0dHA6Ly8xMjcuMC4wLjE6NDAwMjAtBgNVHREEJjAkhxAAAAAAAAAA +AAAAAAAAAAABhxCjvjLzIG7HXQlWDO6YWF7FMA0GCSqGSIb3DQEBCwUAA4IBAQBY +M9UTZ3uaKMQ+He9kWR3p9jh6hTSD0FNi79ZdfkG0lgSzhhduhN7OhzQH2ihUUfa6 +rtKTw74fGbszhizCd9UB8YPKlm3si1Xbg6ZUQlA1RtoQo7RUGEa6ZbR68PKGm9Go +hTTFIl/JS8jzxBR8jywZdyqtprUx+nnNUDiNk0hJtFLhw7OJH0AHlAUNqHsfD08m +HXRdaV6q14HXU5g31slBat9H4D6tCU/2uqBURwW0wVdnqh4QeRfAeqiatJS9EmSF +ctbc7n894Idy2Xce7NFoIy5cht3m6Rd42o/LmBsJopBmQcDPZT70/XzRtc2qE0cS +CzBIGQHUJ6BfmBjrCQnp +-----END CERTIFICATE----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/tests/testdata/csr-ipsans.pem new/acme-1.20.0/tests/testdata/csr-ipsans.pem --- old/acme-1.18.0/tests/testdata/csr-ipsans.pem 1970-01-01 01:00:00.000000000 +0100 +++ new/acme-1.20.0/tests/testdata/csr-ipsans.pem 2021-10-05 15:52:55.000000000 +0200 @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICbTCCAVUCAQIwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKT/ +CE7Y5EYBvI4p7Frt763upIKHDHO/R5/TWMjG8Jm9qTMui8sbMgyh2Yh+lR/j/5Xd +tQrhgC6wx10MrW2+3JtYS88HP1p6si8zU1dbK34n3NyyklR2RivW0R7dXgnYNy7t +5YcDYLCrbRMIPINV/uHrmzIHWYUDNcZVdAfIM2AHfKYuV6Mepcn///5GR+l4GcAh +Nkf9CW8OdAIuKdbyLCxVr0mUW/vJz1b12uxPsgUdax9sjXgZdT4pfMXADsFd1NeF +atpsXU073inqtHru+2F9ijHTQ75TC+u/rr6eYl3BnBntac0gp/ADtDBii7/Q1JOO +Bhq7xJNqqxIEdiyM7zcCAwEAAaAoMCYGCSqGSIb3DQEJDjEZMBcwFQYDVR0RBA4w +DIcEwAACkYcEywBxATANBgkqhkiG9w0BAQsFAAOCAQEADG5g3zdbSCaXpZhWHkzE +Mek3f442TUE1pB+ITRpthmM4N3zZWETYmbLCIAO624uMrRnbCCMvAoLs/L/9ETg/ +XMMFtonQC8u9i9tV8B1ceBh8lpIfa+8b9TMWH3bqnrbWQ+YIl+Yd0gXiCZWJ9vK4 +eM1Gddu/2bR6s/k4h/XAWRgEexqk57EHr1z0N+T9OoX939n3mVcNI+u9kfd5VJ0z +VyA3R8WR6T6KlEl5P5pcWe5Kuyhi7xMmLVImXqBtvKq4O1AMfM+gQr/yn9aE8IRq +khP7JrMBLUIub1c/qu2TfvnynNPSM/ZcOX+6PHdHmRkR3nI0Ndpv7Ntv31FTplAm +Dw== +-----END CERTIFICATE REQUEST----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/tests/testdata/csr-ipv6sans.pem new/acme-1.20.0/tests/testdata/csr-ipv6sans.pem --- old/acme-1.18.0/tests/testdata/csr-ipv6sans.pem 1970-01-01 01:00:00.000000000 +0100 +++ new/acme-1.20.0/tests/testdata/csr-ipv6sans.pem 2021-10-05 15:52:55.000000000 +0200 @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIChTCCAW0CAQIwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOIc +UAppcqJfTkSqqOFqGt1v7lIJZPOcF4bcKI3d5cHAGbOuVxbC7uMaDuObwYLzoiED +qnvs1NaEq2phO6KsgGESB7IE2LUjJivO7OnSZjNRpL5si/9egvBiNCn/50lULaWG +gLEuyMfk3awZy2mVAymy7Grhbx069A4TH8TqsHuq2RpKyuDL27e/jUt6yYecb3pu +hWMiWy3segif4tI46pkOW0/I6DpxyYD2OqOvzxm/voS9RMqE2+7YJA327H7bEi3N +lJZEZ1zy7clZ9ga5fBQaetzbg2RyxTrZ7F919NQXSFoXgxb10Eg64wIpz0L3ooCm +GEHehsZZexa3J5ccIvMCAwEAAaBAMD4GCSqGSIb3DQEJDjExMC8wLQYDVR0RBCYw +JIcQAAAAAAAAAAAAAAAAAAAAAYcQo74y8yBux10JVgzumFhexTANBgkqhkiG9w0B +AQsFAAOCAQEALvwVn0A/JPTCiNzcozHFnp5M23C9PXCplWc5u4k34d4XXzpSeFDz +fL4gy7NpYIueme2K2ppw2j3PNQUdR6vQ5a75sriegWYrosL+7Q6Joh51ZyEUZQoD +mNl4M4S4oX85EaChR6NFGBywTfjFarYi32XBTbFE7rK8N8KM+DQkNdwL1MXqaHWz +F1obQKpNXlLedbCBOteV5Eg4zG3565zu/Gw/NhwzzV3mQmgxUcd1sMJxAfHQz4Vl +ImLL+xMcR03nDsH2bgtDbK2tJm7WszSxA9tC+Xp2lRewxrnQloRWPYDz177WGQ5Q +SoGDzTTtA6uWZxG8h7CkNLOGvA8LtU2rNA== +-----END CERTIFICATE REQUEST----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-1.18.0/tests/testdata/csr-mixed.pem new/acme-1.20.0/tests/testdata/csr-mixed.pem --- old/acme-1.18.0/tests/testdata/csr-mixed.pem 1970-01-01 01:00:00.000000000 +0100 +++ new/acme-1.20.0/tests/testdata/csr-mixed.pem 2021-10-05 15:52:55.000000000 +0200 @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICdjCCAV4CAQIwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMXq +v1y8EIcCbaUIzCtOcLkLS0MJ35oS+6DmV5WB1A0cIk6YrjsHIsY2lwMm13BWIvmw +tY+Y6n0rr7eViNx5ZRGHpHEI/TL3Neb+VefTydL5CgvK3dd4ex2kSbTaed3fmpOx +qMajEduwNcZPCcmoEXPkfrCP8w2vKQUkQ+JRPcdX1nTuzticeRP5B7YCmJsmxkEh +Y0tzzZ+NIRDARoYNofefY86h3e5q66gtJxccNchmIM3YQahhg5n3Xoo8hGfM/TIc +R7ncCBCLO6vtqo0QFva/NQODrgOmOsmgvqPkUWQFdZfWM8yIaU826dktx0CPB78t +TudnJ1rBRvGsjHMsZikCAwEAAaAxMC8GCSqGSIb3DQEJDjEiMCAwHgYDVR0RBBcw +FYINYS5leGVtcGxlLmNvbYcEwAACbzANBgkqhkiG9w0BAQsFAAOCAQEAdGMcRCxq +1X09gn1TNdMt64XUv+wdJCKDaJ+AgyIJj7QvVw8H5k7dOnxS4I+a/yo4jE+LDl2/ +AuHcBLFEI4ddewdJSMrTNZjuRYuOdr3KP7fL7MffICSBi45vw5EOXg0tnjJCEiKu +6gcJgbLSP5JMMd7Haf33Q/VWsmHofR3VwOMdrnakwAU3Ff5WTuXTNVhL1kT/uLFX +yW1ru6BF4unwNqSR2UeulljpNfRBsiN4zJK11W6n9KT0NkBr9zY5WCM4sW7i8k9V +TeypWGo3jBKzYAGeuxZsB97U77jZ2lrGdBLZKfbcjnTeRVqCvCRrui4El7UGYFmj +7s6OJyWx5DSV8w== +-----END CERTIFICATE REQUEST-----