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 2022-04-08 22:46:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-acme (Old)
 and      /work/SRC/openSUSE:Factory/.python-acme.new.1900 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-acme"

Fri Apr  8 22:46:00 2022 rev:57 rq:967759 version:1.26.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-acme/python-acme.changes  2021-12-21 
20:16:45.257152683 +0100
+++ /work/SRC/openSUSE:Factory/.python-acme.new.1900/python-acme.changes        
2022-04-08 22:46:16.478768940 +0200
@@ -1,0 +2,19 @@
+Thu Apr  7 15:33:21 UTC 2022 - Mark??ta Machov?? <mmach...@suse.com>
+
+- Update to version 1.26.0
+  * Added show_account subcommand, which will fetch the account information 
from 
+    the ACME server and show the account details (account URL and, if 
applicable, 
+    email address or addresses)
+  * The acme library now requires requests>=2.20.0.
+  * Certbot and its acme library now require pytz>=2019.3.
+  * Certbot and its acme module now depend on josepy>=1.13.0 due to better 
type annotation support.
+  * Previously, when Certbot was in the process of registering a new ACME 
account 
+    and the ACME server did not present any Terms of Service, the user was 
asked 
+    to agree with a non-existent Terms of Service ("None"). This bug is now 
fixed, 
+    so that if an ACME server does not provide any Terms of Service to agree 
with, 
+    the user is not asked to agree to a non-existent Terms of Service any 
longer.
+  * If account registration fails, Certbot did not relay the error from the 
ACME 
+    server back to the user. This is now fixed: the error message from the 
ACME 
+    server is now presented to the user when account registration fails.
+
+-------------------------------------------------------------------

Old:
----
  acme-1.22.0.tar.gz
  acme-1.22.0.tar.gz.asc

New:
----
  acme-1.26.0.tar.gz
  acme-1.26.0.tar.gz.asc

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-acme.spec ++++++
--- /var/tmp/diff_new_pack.JRAhEi/_old  2022-04-08 22:46:17.050762592 +0200
+++ /var/tmp/diff_new_pack.JRAhEi/_new  2022-04-08 22:46:17.054762547 +0200
@@ -1,7 +1,7 @@
 #
 # spec file
 #
-# Copyright (c) 2021 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -20,7 +20,7 @@
 %define skip_python2 1
 %define libname acme
 Name:           python-%{libname}
-Version:        1.22.0
+Version:        1.26.0
 Release:        0
 Summary:        Python library for the ACME protocol
 License:        Apache-2.0
@@ -29,22 +29,22 @@
 Source1:        
https://files.pythonhosted.org/packages/source/a/%{libname}/%{libname}-%{version}.tar.gz.asc
 Source2:        %{name}.keyring
 BuildRequires:  %{python_module cryptography >= 2.5.0}
-BuildRequires:  %{python_module josepy >= 1.9.0}
+BuildRequires:  %{python_module josepy >= 1.13.0}
 BuildRequires:  %{python_module pyOpenSSL >= 17.3.0}
 BuildRequires:  %{python_module pyRFC3339}
 BuildRequires:  %{python_module pytest}
-BuildRequires:  %{python_module pytz}
-BuildRequires:  %{python_module requests >= 2.14.2}
+BuildRequires:  %{python_module pytz >= 2019.3}
+BuildRequires:  %{python_module requests >= 2.20.0}
 BuildRequires:  %{python_module requests-toolbelt >= 0.3.0}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-cryptography >= 2.5.0
-Requires:       python-josepy >= 1.9.0
+Requires:       python-josepy >= 1.13.0
 Requires:       python-pyOpenSSL >= 17.3.0
 Requires:       python-pyRFC3339
-Requires:       python-pytz
-Requires:       python-requests >= 2.14.2
+Requires:       python-pytz >= 2019.3
+Requires:       python-requests >= 2.20.0
 Requires:       python-requests-toolbelt >= 0.3.0
 BuildArch:      noarch
 %if %{?suse_version} < 1500

++++++ acme-1.22.0.tar.gz -> acme-1.26.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/MANIFEST.in new/acme-1.26.0/MANIFEST.in
--- old/acme-1.22.0/MANIFEST.in 2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/MANIFEST.in 2022-04-05 19:41:26.000000000 +0200
@@ -4,5 +4,6 @@
 recursive-include docs *
 recursive-include examples *
 recursive-include tests *
+include acme/py.typed
 global-exclude __pycache__
 global-exclude *.py[cod]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/PKG-INFO new/acme-1.26.0/PKG-INFO
--- old/acme-1.22.0/PKG-INFO    2021-12-07 23:02:52.134568200 +0100
+++ new/acme-1.26.0/PKG-INFO    2022-04-05 19:41:33.131234600 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: acme
-Version: 1.22.0
+Version: 1.26.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
@@ -12,14 +12,13 @@
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Topic :: Internet :: WWW/HTTP
 Classifier: Topic :: Security
-Requires-Python: >=3.6
+Requires-Python: >=3.7
 Provides-Extra: docs
 Provides-Extra: test
 License-File: LICENSE.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/__init__.py 
new/acme-1.26.0/acme/__init__.py
--- old/acme-1.22.0/acme/__init__.py    2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/__init__.py    2022-04-05 19:41:26.000000000 +0200
@@ -2,7 +2,7 @@
 
 This module is an implementation of the `ACME protocol`_.
 
-.. _`ACME protocol`: https://ietf-wg-acme.github.io/acme
+.. _`ACME protocol`: https://datatracker.ietf.org/doc/html/rfc8555
 
 """
 import sys
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/challenges.py 
new/acme-1.26.0/acme/challenges.py
--- old/acme-1.22.0/acme/challenges.py  2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/challenges.py  2022-04-05 19:41:26.000000000 +0200
@@ -12,6 +12,8 @@
 from typing import Optional
 from typing import Tuple
 from typing import Type
+from typing import TypeVar
+from typing import Union
 
 from cryptography.hazmat.primitives import hashes
 import josepy as jose
@@ -27,6 +29,8 @@
 
 logger = logging.getLogger(__name__)
 
+GenericChallenge = TypeVar('GenericChallenge', bound='Challenge')
+
 
 class Challenge(jose.TypedJSONObjectWithFields):
     # _fields_to_partial_json
@@ -34,9 +38,10 @@
     TYPES: Dict[str, Type['Challenge']] = {}
 
     @classmethod
-    def from_json(cls, jobj: Mapping[str, Any]) -> 'Challenge':
+    def from_json(cls: Type[GenericChallenge],
+                  jobj: Mapping[str, Any]) -> Union[GenericChallenge, 
'UnrecognizedChallenge']:
         try:
-            return super().from_json(jobj)
+            return cast(GenericChallenge, super().from_json(jobj))
         except jose.UnrecognizedTypeError as error:
             logger.debug(error)
             return UnrecognizedChallenge.from_json(jobj)
@@ -47,7 +52,7 @@
     """ACME challenge response."""
     TYPES: Dict[str, Type['ChallengeResponse']] = {}
     resource_type = 'challenge'
-    resource = fields.Resource(resource_type)
+    resource: str = fields.resource(resource_type)
 
 
 class UnrecognizedChallenge(Challenge):
@@ -62,6 +67,7 @@
     :ivar jobj: Original JSON decoded object.
 
     """
+    jobj: Dict[str, Any]
 
     def __init__(self, jobj: Mapping[str, Any]) -> None:
         super().__init__()
@@ -85,7 +91,7 @@
     """Minimum size of the :attr:`token` in bytes."""
 
     # TODO: acme-spec doesn't specify token as base64-encoded value
-    token: bytes = jose.Field(
+    token: bytes = jose.field(
         "token", encoder=jose.encode_b64jose, decoder=functools.partial(
             jose.decode_b64jose, size=TOKEN_SIZE, minimum=True))
 
@@ -108,10 +114,10 @@
 class KeyAuthorizationChallengeResponse(ChallengeResponse):
     """Response to Challenges based on Key Authorization.
 
-    :param unicode key_authorization:
+    :param str key_authorization:
 
     """
-    key_authorization = jose.Field("keyAuthorization")
+    key_authorization: str = jose.field("keyAuthorization")
     thumbprint_hash_function = hashes.SHA256
 
     def verify(self, chall: 'KeyAuthorizationChallenge', account_public_key: 
jose.JWK) -> bool:
@@ -126,7 +132,7 @@
         :rtype: bool
 
         """
-        parts = self.key_authorization.split('.')
+        parts = self.key_authorization.split('.')  # pylint: disable=no-member
         if len(parts) != 2:
             logger.debug("Key authorization (%r) is not well formed",
                          self.key_authorization)
@@ -152,6 +158,9 @@
         return jobj
 
 
+# TODO: Make this method a generic of K (bound=KeyAuthorizationChallenge), 
response_cls of type
+#  Type[K] and use it in response/response_and_validation return types once 
Python 3.6 support is
+#  dropped (do not support generic ABC classes, see 
https://github.com/python/typing/issues/449).
 class KeyAuthorizationChallenge(_TokenChallenge, metaclass=abc.ABCMeta):
     """Challenge based on Key Authorization.
 
@@ -168,7 +177,7 @@
         """Generate Key Authorization.
 
         :param JWK account_key:
-        :rtype unicode:
+        :rtype str:
 
         """
         return self.encode("token") + "." + jose.b64encode(
@@ -229,7 +238,7 @@
         around `KeyAuthorizationChallengeResponse.verify`.
 
         :param challenges.DNS01 chall: Corresponding challenge.
-        :param unicode domain: Domain name being verified.
+        :param str domain: Domain name being verified.
         :param JWK account_public_key: Public key for the key pair
             being authorized.
 
@@ -257,7 +266,7 @@
         """Generate validation.
 
         :param JWK account_key:
-        :rtype: unicode
+        :rtype: str
 
         """
         return jose.b64encode(hashlib.sha256(self.key_authorization(
@@ -266,10 +275,11 @@
     def validation_domain_name(self, name: str) -> str:
         """Domain name for TXT validation record.
 
-        :param unicode name: Domain name being validated.
+        :param str name: Domain name being validated.
+        :rtype: str
 
         """
-        return "{0}.{1}".format(self.LABEL, name)
+        return f"{self.LABEL}.{name}"
 
 
 @ChallengeResponse.register
@@ -293,7 +303,7 @@
         """Simple verify.
 
         :param challenges.SimpleHTTP chall: Corresponding challenge.
-        :param unicode domain: Domain name being verified.
+        :param str domain: Domain name being verified.
         :param JWK account_public_key: Public key for the key pair
             being authorized.
         :param int port: Port used in the validation.
@@ -357,7 +367,7 @@
     def path(self) -> str:
         """Path (starting with '/') for provisioned resource.
 
-        :rtype: string
+        :rtype: str
 
         """
         return '/' + self.URI_ROOT_PATH + '/' + self.encode('token')
@@ -368,8 +378,8 @@
         Forms an URI to the HTTPS server provisioned resource
         (containing :attr:`~SimpleHTTP.token`).
 
-        :param unicode domain: Domain name being verified.
-        :rtype: string
+        :param str domain: Domain name being verified.
+        :rtype: str
 
         """
         return "http://"; + domain + self.path
@@ -378,7 +388,7 @@
         """Generate validation.
 
         :param JWK account_key:
-        :rtype: unicode
+        :rtype: str
 
         """
         return self.key_authorization(account_key)
@@ -409,7 +419,7 @@
                  ) -> Tuple[crypto.X509, crypto.PKey]:
         """Generate tls-alpn-01 certificate.
 
-        :param unicode domain: Domain verified by the challenge.
+        :param str domain: Domain verified by the challenge.
         :param OpenSSL.crypto.PKey key: Optional private key used in
             certificate generation. If not provided (``None``), then
             fresh key will be generated.
@@ -433,8 +443,8 @@
                    port: Optional[int] = None) -> crypto.X509:
         """Probe tls-alpn-01 challenge certificate.
 
-        :param unicode domain: domain being validated, required.
-        :param string host: IP address used to probe the certificate.
+        :param str domain: domain being validated, required.
+        :param str host: IP address used to probe the certificate.
         :param int port: Port used to probe the certificate.
 
         """
@@ -450,7 +460,7 @@
     def verify_cert(self, domain: str, cert: crypto.X509) -> bool:
         """Verify tls-alpn-01 challenge certificate.
 
-        :param unicode domain: Domain name being validated.
+        :param str domain: Domain name being validated.
         :param OpensSSL.crypto.X509 cert: Challenge certificate.
 
         :returns: Whether the certificate was successfully verified.
@@ -462,7 +472,7 @@
         # Type ignore needed due to
         # https://github.com/pyca/pyopenssl/issues/730.
         logger.debug('Certificate %s. SANs: %s',
-                     cert.digest('sha256'), names)  # type: ignore[arg-type]
+                     cert.digest('sha256'), names)
         if len(names) != 1 or names[0].lower() != domain.lower():
             return False
 
@@ -523,7 +533,7 @@
         """Generate validation.
 
         :param JWK account_key:
-        :param unicode domain: Domain verified by the challenge.
+        :param str domain: Domain verified by the challenge.
         :param OpenSSL.crypto.PKey cert_key: Optional private key used
             in certificate generation. If not provided (``None``), then
             fresh key will be generated.
@@ -531,9 +541,10 @@
         :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
 
         """
-        return self.response(account_key).gen_cert(
+        # TODO: Remove cast when response() is generic.
+        return cast(TLSALPN01Response, self.response(account_key)).gen_cert(
             key=kwargs.get('cert_key'),
-            domain=kwargs.get('domain'))
+            domain=cast(str, kwargs.get('domain')))
 
     @staticmethod
     def is_supported() -> bool:
@@ -599,13 +610,12 @@
         :rtype: DNSResponse
 
         """
-        return DNSResponse(validation=self.gen_validation(
-            account_key, **kwargs))
+        return DNSResponse(validation=self.gen_validation(account_key, 
**kwargs))
 
     def validation_domain_name(self, name: str) -> str:
         """Domain name for TXT validation record.
 
-        :param unicode name: Domain name being validated.
+        :param str name: Domain name being validated.
 
         """
         return "{0}.{1}".format(self.LABEL, name)
@@ -620,7 +630,7 @@
     """
     typ = "dns"
 
-    validation = jose.Field("validation", decoder=jose.JWS.from_json)
+    validation: jose.JWS = jose.field("validation", decoder=jose.JWS.from_json)
 
     def check_validation(self, chall: 'DNS', account_public_key: jose.JWK) -> 
bool:
         """Check validation.
@@ -631,4 +641,4 @@
         :rtype: bool
 
         """
-        return chall.check_validation(cast(jose.JWS, self.validation), 
account_public_key)
+        return chall.check_validation(self.validation, account_public_key)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/client.py 
new/acme-1.26.0/acme/client.py
--- old/acme-1.22.0/acme/client.py      2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/client.py      2022-04-05 19:41:26.000000000 +0200
@@ -19,6 +19,7 @@
 from typing import Dict
 from typing import Iterable
 from typing import List
+from typing import Mapping
 from typing import Optional
 from typing import Set
 from typing import Text
@@ -33,6 +34,7 @@
 from requests.utils import parse_header_links
 from requests_toolbelt.adapters.source import SourceAddressAdapter
 
+from acme import challenges
 from acme import crypto_util
 from acme import errors
 from acme import jws
@@ -156,12 +158,12 @@
         authzr = messages.AuthorizationResource(
             body=messages.Authorization.from_json(response.json()),
             uri=response.headers.get('Location', uri))
-        if identifier is not None and authzr.body.identifier != identifier:
+        if identifier is not None and authzr.body.identifier != identifier:  # 
pylint: disable=no-member
             raise errors.UnexpectedUpdate(authzr)
         return authzr
 
-    def answer_challenge(self, challb: messages.ChallengeBody, response: 
requests.Response
-                         ) -> messages.ChallengeResource:
+    def answer_challenge(self, challb: messages.ChallengeBody,
+                         response: challenges.ChallengeResponse) -> 
messages.ChallengeResource:
         """Answer challenge.
 
         :param challb: Challenge Resource body.
@@ -176,15 +178,15 @@
         :raises .UnexpectedUpdate:
 
         """
-        response = self._post(challb.uri, response)
+        resp = self._post(challb.uri, response)
         try:
-            authzr_uri = response.links['up']['url']
+            authzr_uri = resp.links['up']['url']
         except KeyError:
             raise errors.ClientError('"up" Link header missing')
         challr = messages.ChallengeResource(
             authzr_uri=authzr_uri,
-            body=messages.ChallengeBody.from_json(response.json()))
-        # TODO: check that challr.uri == response.headers['Location']?
+            body=messages.ChallengeBody.from_json(resp.json()))
+        # TODO: check that challr.uri == resp.headers['Location']?
         if challr.uri != challb.uri:
             raise errors.UnexpectedUpdate(challr.uri)
         return challr
@@ -492,7 +494,7 @@
             updated[authzr] = updated_authzr
 
             attempts[authzr] += 1
-            if updated_authzr.body.status not in (
+            if updated_authzr.body.status not in (  # pylint: disable=no-member
                     messages.STATUS_VALID, messages.STATUS_INVALID):
                 if attempts[authzr] < max_attempts:
                     # push back to the priority queue, with updated retry_after
@@ -599,7 +601,7 @@
         :raises .ClientError: If revocation is unsuccessful.
 
         """
-        self._revoke(cert, rsn, self.directory[cast(str, messages.Revocation)])
+        self._revoke(cert, rsn, self.directory[messages.Revocation])
 
 
 class ClientV2(ClientBase):
@@ -756,7 +758,7 @@
         for url in orderr.body.authorizations:
             while datetime.datetime.now() < deadline:
                 authzr = self._authzr_from_response(self._post_as_get(url), 
uri=url)
-                if authzr.body.status != messages.STATUS_PENDING:
+                if authzr.body.status != messages.STATUS_PENDING:  # pylint: 
disable=no-member
                     responses.append(authzr)
                     break
                 time.sleep(1)
@@ -897,14 +899,15 @@
                 check_tos_cb(tos)
         if self.acme_version == 1:
             client_v1 = cast(Client, self.client)
-            regr = client_v1.register(regr)
-            if regr.terms_of_service is not None:
-                _assess_tos(regr.terms_of_service)
-                return client_v1.agree_to_tos(regr)
-            return regr
+            regr_res = client_v1.register(regr)
+            if regr_res.terms_of_service is not None:
+                _assess_tos(regr_res.terms_of_service)
+                return client_v1.agree_to_tos(regr_res)
+            return regr_res
         else:
             client_v2 = cast(ClientV2, self.client)
-            if "terms_of_service" in client_v2.directory.meta:
+            if ("terms_of_service" in client_v2.directory.meta and
+                client_v2.directory.meta.terms_of_service is not None):
                 _assess_tos(client_v2.directory.meta.terms_of_service)
                 regr = regr.update(terms_of_service_agreed=True)
             return client_v2.new_account(regr)
@@ -970,7 +973,8 @@
                     'certificate, please rerun the command for a new one.')
 
             cert = OpenSSL.crypto.dump_certificate(
-                    OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode()
+                OpenSSL.crypto.FILETYPE_PEM,
+                cast(OpenSSL.crypto.X509, cast(jose.ComparableX509, 
certr.body).wrapped)).decode()
             chain_str = crypto_util.dump_pyopenssl_chain(chain).decode()
 
             return orderr.update(fullchain_pem=(cert + chain_str))
@@ -1056,7 +1060,7 @@
             pass
 
     def _wrap_in_jws(self, obj: jose.JSONDeSerializable, nonce: str, url: str,
-                     acme_version: int) -> jose.JWS:
+                     acme_version: int) -> str:
         """Wrap `JSONDeSerializable` object in JWS.
 
         .. todo:: Implement ``acmePath``.
@@ -1064,7 +1068,7 @@
         :param josepy.JSONDeSerializable obj:
         :param str url: The URL to which this object will be POSTed
         :param str nonce:
-        :rtype: `josepy.JWS`
+        :rtype: str
 
         """
         if isinstance(obj, VersionedLEACMEMixin):
@@ -1073,7 +1077,7 @@
         logger.debug('JWS payload:\n%s', jobj)
         kwargs = {
             "alg": self.alg,
-            "nonce": nonce
+            "nonce": nonce,
         }
         if acme_version == 2:
             kwargs["url"] = url
@@ -1082,7 +1086,7 @@
             if self.account is not None:
                 kwargs["kid"] = self.account["uri"]
         kwargs["key"] = self.key
-        return jws.JWS.sign(jobj, **kwargs).json_dumps(indent=2)
+        return jws.JWS.sign(jobj, **cast(Mapping[str, Any], 
kwargs)).json_dumps(indent=2)
 
     @classmethod
     def _check_response(cls, response: requests.Response,
@@ -1100,7 +1104,7 @@
             is ignored, but logged.
 
         :raises .messages.Error: If server response body
-            carries HTTP Problem (draft-ietf-appsawg-http-problem-00).
+            carries HTTP Problem 
(https://datatracker.ietf.org/doc/html/rfc7807).
         :raises .ClientError: In case of other networking errors.
 
         """
@@ -1139,8 +1143,7 @@
                     'response', response_ct)
 
             if content_type == cls.JSON_CONTENT_TYPE and jobj is None:
-                raise errors.ClientError(
-                    'Unexpected response Content-Type: 
{0}'.format(response_ct))
+                raise errors.ClientError(f'Unexpected response Content-Type: 
{response_ct}')
 
         return response
 
@@ -1193,7 +1196,7 @@
             if m is None:
                 raise  # pragma: no cover
             host, path, _err_no, err_msg = m.groups()
-            raise ValueError("Requesting {0}{1}:{2}".format(host, path, 
err_msg))
+            raise ValueError(f"Requesting {host}{path}:{err_msg}")
 
         # 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/crypto_util.py 
new/acme-1.26.0/acme/crypto_util.py
--- old/acme-1.22.0/acme/crypto_util.py 2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/crypto_util.py 2022-04-05 19:41:26.000000000 +0200
@@ -278,7 +278,7 @@
     :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
 
     :returns: A list of Subject Alternative Names that is DNS.
-    :rtype: `list` of `unicode`
+    :rtype: `list` of `str`
 
     """
     # This function finds SANs with dns name
@@ -300,7 +300,7 @@
     :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
+    :rtype: `list` of `str`. note that this returns as string, not IPaddress 
object
 
     """
 
@@ -320,7 +320,7 @@
     :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
 
     :returns: raw san strings, parsed byte as utf-8
-    :rtype: `list` of `unicode`
+    :rtype: `list` of `str`
 
     """
     # This function finds SANs by dumping the certificate/CSR to text and
@@ -352,7 +352,7 @@
                 ) -> crypto.X509:
     """Generate new self-signed certificate.
 
-    :type domains: `list` of `unicode`
+    :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.
@@ -410,7 +410,8 @@
     return cert
 
 
-def dump_pyopenssl_chain(chain: List[crypto.X509], filetype: int = 
crypto.FILETYPE_PEM) -> bytes:
+def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], 
List[crypto.X509]],
+                         filetype: int = crypto.FILETYPE_PEM) -> bytes:
     """Dump certificate chain into a bundle.
 
     :param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
@@ -425,6 +426,8 @@
 
     def _dump_cert(cert: Union[jose.ComparableX509, crypto.X509]) -> bytes:
         if isinstance(cert, jose.ComparableX509):
+            if isinstance(cert.wrapped, crypto.X509Req):
+                raise errors.Error("Unexpected CSR provided.")  # pragma: no 
cover
             cert = cert.wrapped
         return crypto.dump_certificate(filetype, cert)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/fields.py 
new/acme-1.26.0/acme/fields.py
--- old/acme-1.22.0/acme/fields.py      2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/fields.py      2022-04-05 19:41:26.000000000 +0200
@@ -56,8 +56,8 @@
 
     def __init__(self, resource_type: str, *args: Any, **kwargs: Any) -> None:
         self.resource_type = resource_type
-        super().__init__(
-            'resource', default=resource_type, *args, **kwargs)
+        kwargs['default'] = resource_type
+        super().__init__('resource', *args, **kwargs)
 
     def decode(self, value: Any) -> Any:
         if value != self.resource_type:
@@ -65,3 +65,18 @@
                 'Wrong resource type: {0} instead of {1}'.format(
                     value, self.resource_type))
         return value
+
+
+def fixed(json_name: str, value: Any) -> Any:
+    """Generates a type-friendly Fixed field."""
+    return Fixed(json_name, value)
+
+
+def rfc3339(json_name: str, omitempty: bool = False) -> Any:
+    """Generates a type-friendly RFC3339 field."""
+    return RFC3339Field(json_name, omitempty=omitempty)
+
+
+def resource(resource_type: str) -> Any:
+    """Generates a type-friendly Resource field."""
+    return Resource(resource_type)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/jws.py new/acme-1.26.0/acme/jws.py
--- old/acme-1.22.0/acme/jws.py 2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/jws.py 2022-04-05 19:41:26.000000000 +0200
@@ -12,14 +12,14 @@
 class Header(jose.Header):
     """ACME-specific JOSE Header. Implements nonce, kid, and url.
     """
-    nonce = jose.Field('nonce', omitempty=True, encoder=jose.encode_b64jose)
-    kid = jose.Field('kid', omitempty=True)
-    url = jose.Field('url', omitempty=True)
+    nonce: Optional[bytes] = jose.field('nonce', omitempty=True, 
encoder=jose.encode_b64jose)
+    kid: Optional[str] = jose.field('kid', omitempty=True)
+    url: Optional[str] = jose.field('url', omitempty=True)
 
     # Mypy does not understand the josepy magic happening here, and falsely 
claims
     # that nonce is redefined. Let's ignore the type check here.
-    @nonce.decoder  # type: ignore
-    def nonce(value: str) -> bytes:  # pylint: 
disable=no-self-argument,missing-function-docstring
+    @nonce.decoder  # type: ignore[no-redef,union-attr]
+    def nonce(value: str) -> bytes:  # type: ignore[misc]  # pylint: 
disable=no-self-argument,missing-function-docstring
         try:
             return jose.decode_b64jose(value)
         except jose.DeserializationError as error:
@@ -29,12 +29,12 @@
 
 class Signature(jose.Signature):
     """ACME-specific Signature. Uses ACME-specific Header for customer 
fields."""
-    __slots__ = jose.Signature._orig_slots  # pylint: disable=no-member
+    __slots__ = jose.Signature._orig_slots  # type: ignore[attr-defined]  # 
pylint: disable=protected-access,no-member
 
     # TODO: decoder/encoder should accept cls? Otherwise, subclassing
     # JSONObjectWithFields is tricky...
     header_cls = Header
-    header = jose.Field(
+    header: Header = jose.field(
         'header', omitempty=True, default=header_cls(),
         decoder=header_cls.from_json)
 
@@ -44,10 +44,10 @@
 class JWS(jose.JWS):
     """ACME-specific JWS. Includes none, url, and kid in protected header."""
     signature_cls = Signature
-    __slots__ = jose.JWS._orig_slots
+    __slots__ = jose.JWS._orig_slots  # type: ignore[attr-defined]  # pylint: 
disable=protected-access
 
     @classmethod
-    # pylint: disable=arguments-differ
+    # type: ignore[override]  # pylint: disable=arguments-differ
     def sign(cls, payload: bytes, key: jose.JWK, alg: jose.JWASignature, 
nonce: Optional[bytes],
              url: Optional[str] = None, kid: Optional[str] = None) -> jose.JWS:
         # Per ACME spec, jwk and kid are mutually exclusive, so only include a
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/messages.py 
new/acme-1.26.0/acme/messages.py
--- old/acme-1.22.0/acme/messages.py    2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/messages.py    2022-04-05 19:41:26.000000000 +0200
@@ -1,4 +1,5 @@
 """ACME protocol messages."""
+import datetime
 from collections.abc import Hashable
 import json
 from typing import Any
@@ -7,9 +8,12 @@
 from typing import List
 from typing import Mapping
 from typing import MutableMapping
+from typing import Optional
 from typing import Tuple
 from typing import Type
-from typing import Optional
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
 
 import josepy as jose
 
@@ -20,6 +24,11 @@
 from acme import util
 from acme.mixins import ResourceMixin
 
+if TYPE_CHECKING:
+    from typing_extensions import Protocol  # pragma: no cover
+else:
+    Protocol = object
+
 OLD_ERROR_PREFIX = "urn:acme:error:"
 ERROR_PREFIX = "urn:ietf:params:acme:error:"
 
@@ -56,11 +65,11 @@
     'externalAccountRequired': 'The server requires external account binding',
 }
 
-ERROR_TYPE_DESCRIPTIONS = dict(
-    (ERROR_PREFIX + name, desc) for name, desc in ERROR_CODES.items())
-
-ERROR_TYPE_DESCRIPTIONS.update(dict(  # add errors with old prefix, deprecate 
me
-    (OLD_ERROR_PREFIX + name, desc) for name, desc in ERROR_CODES.items()))
+ERROR_TYPE_DESCRIPTIONS = {**{
+    ERROR_PREFIX + name: desc for name, desc in ERROR_CODES.items()
+}, **{  # add errors with old prefix, deprecate me
+    OLD_ERROR_PREFIX + name: desc for name, desc in ERROR_CODES.items()
+}}
 
 
 def is_acme_error(err: BaseException) -> bool:
@@ -73,22 +82,22 @@
 class Error(jose.JSONObjectWithFields, errors.Error):
     """ACME error.
 
-    https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
+    https://datatracker.ietf.org/doc/html/rfc7807
 
-    :ivar unicode typ:
-    :ivar unicode title:
-    :ivar unicode detail:
+    :ivar str typ:
+    :ivar str title:
+    :ivar str detail:
 
     """
-    typ = jose.Field('type', omitempty=True, default='about:blank')
-    title = jose.Field('title', omitempty=True)
-    detail = jose.Field('detail', omitempty=True)
+    typ: str = jose.field('type', omitempty=True, default='about:blank')
+    title: str = jose.field('title', omitempty=True)
+    detail: str = jose.field('detail', omitempty=True)
 
     @classmethod
     def with_code(cls, code: str, **kwargs: Any) -> 'Error':
         """Create an Error instance with an ACME Error code.
 
-        :unicode code: An ACME error code, like 'dnssec'.
+        :str code: An ACME error code, like 'dnssec'.
         :kwargs: kwargs to pass to Error.
 
         """
@@ -98,14 +107,14 @@
         typ = ERROR_PREFIX + code
         # Mypy will not understand that the Error constructor accepts a named 
argument
         # "typ" because of josepy magic. Let's ignore the type check here.
-        return cls(typ=typ, **kwargs)  # type: ignore
+        return cls(typ=typ, **kwargs)
 
     @property
     def description(self) -> Optional[str]:
         """Hardcoded error description based on its type.
 
         :returns: Description if standard ACME error or ``None``.
-        :rtype: unicode
+        :rtype: str
 
         """
         return ERROR_TYPE_DESCRIPTIONS.get(self.typ)
@@ -117,7 +126,7 @@
         Basically self.typ without the ERROR_PREFIX.
 
         :returns: error code if standard ACME code or ``None``.
-        :rtype: unicode
+        :rtype: str
 
         """
         code = str(self.typ).rsplit(':', maxsplit=1)[-1]
@@ -148,12 +157,11 @@
     @classmethod
     def from_json(cls, jobj: str) -> '_Constant':
         if jobj not in cls.POSSIBLE_NAMES:  # pylint: 
disable=unsupported-membership-test
-            raise jose.DeserializationError(
-                '{0} not recognized'.format(cls.__name__))
+            raise jose.DeserializationError(f'{cls.__name__} not recognized')
         return cls.POSSIBLE_NAMES[jobj]
 
     def __repr__(self) -> str:
-        return '{0}({1})'.format(self.__class__.__name__, self.name)
+        return f'{self.__class__.__name__}({self.name})'
 
     def __eq__(self, other: Any) -> bool:
         return isinstance(other, type(self)) and other.name == self.name
@@ -164,7 +172,7 @@
 
 class Status(_Constant):
     """ACME "status" field."""
-    POSSIBLE_NAMES: Dict[str, 'Status'] = {}
+    POSSIBLE_NAMES: Dict[str, _Constant] = {}
 
 
 STATUS_UNKNOWN = Status('unknown')
@@ -179,7 +187,7 @@
 
 class IdentifierType(_Constant):
     """ACME identifier type."""
-    POSSIBLE_NAMES: Dict[str, 'IdentifierType'] = {}
+    POSSIBLE_NAMES: Dict[str, _Constant] = {}
 
 
 IDENTIFIER_FQDN = IdentifierType('dns')  # IdentifierDNS in Boulder
@@ -190,25 +198,35 @@
     """ACME identifier.
 
     :ivar IdentifierType typ:
-    :ivar unicode value:
+    :ivar str value:
 
     """
-    typ = jose.Field('type', decoder=IdentifierType.from_json)
-    value = jose.Field('value')
+    typ: IdentifierType = jose.field('type', decoder=IdentifierType.from_json)
+    value: str = jose.field('value')
+
+
+class HasResourceType(Protocol):
+    """
+    Represents a class with a resource_type class parameter of type string.
+    """
+    resource_type: str = NotImplemented
+
+
+GenericHasResourceType = TypeVar("GenericHasResourceType", 
bound=HasResourceType)
 
 
 class Directory(jose.JSONDeSerializable):
     """Directory."""
 
-    _REGISTERED_TYPES: Dict[str, Type['Directory']] = {}
+    _REGISTERED_TYPES: Dict[str, Type[HasResourceType]] = {}
 
     class Meta(jose.JSONObjectWithFields):
         """Directory Meta."""
-        _terms_of_service = jose.Field('terms-of-service', omitempty=True)
-        _terms_of_service_v2 = jose.Field('termsOfService', omitempty=True)
-        website = jose.Field('website', omitempty=True)
-        caa_identities = jose.Field('caaIdentities', omitempty=True)
-        external_account_required = jose.Field('externalAccountRequired', 
omitempty=True)
+        _terms_of_service: str = jose.field('terms-of-service', omitempty=True)
+        _terms_of_service_v2: str = jose.field('termsOfService', 
omitempty=True)
+        website: str = jose.field('website', omitempty=True)
+        caa_identities: List[str] = jose.field('caaIdentities', omitempty=True)
+        external_account_required: bool = 
jose.field('externalAccountRequired', omitempty=True)
 
         def __init__(self, **kwargs: Any) -> None:
             kwargs = {self._internal_name(k): v for k, v in kwargs.items()}
@@ -229,11 +247,14 @@
             return '_' + name if name == 'terms_of_service' else name
 
     @classmethod
-    def _canon_key(cls, key: str) -> str:
-        return getattr(key, 'resource_type', key)
+    def _canon_key(cls, key: Union[str, HasResourceType, 
Type[HasResourceType]]) -> str:
+        if isinstance(key, str):
+            return key
+        return key.resource_type
 
     @classmethod
-    def register(cls, resource_body_cls: Type['Directory']) -> 
Type['Directory']:
+    def register(cls,
+                 resource_body_cls: Type[GenericHasResourceType]) -> 
Type[GenericHasResourceType]:
         """Register resource."""
         resource_type = resource_body_cls.resource_type
         assert resource_type not in cls._REGISTERED_TYPES
@@ -252,7 +273,7 @@
         except KeyError as error:
             raise AttributeError(str(error))
 
-    def __getitem__(self, name: str) -> Any:
+    def __getitem__(self, name: Union[str, HasResourceType, 
Type[HasResourceType]]) -> Any:
         try:
             return self._jobj[self._canon_key(name)]
         except KeyError:
@@ -273,16 +294,16 @@
     :ivar acme.messages.ResourceBody body: Resource body.
 
     """
-    body = jose.Field('body')
+    body: "ResourceBody" = jose.field('body')
 
 
 class ResourceWithURI(Resource):
     """ACME Resource with URI.
 
-    :ivar unicode ~.uri: Location of the resource.
+    :ivar str uri: Location of the resource.
 
     """
-    uri = jose.Field('uri')  # no ChallengeResource.uri
+    uri: str = jose.field('uri')  # no ChallengeResource.uri
 
 
 class ResourceBody(jose.JSONObjectWithFields):
@@ -308,35 +329,40 @@
         return eab.to_partial_json()
 
 
+GenericRegistration = TypeVar('GenericRegistration', bound='Registration')
+
+
 class Registration(ResourceBody):
     """Registration Resource Body.
 
-    :ivar josepy.jwk.JWK key: Public key.
+    :ivar jose.JWK key: Public key.
     :ivar tuple contact: Contact information following ACME spec,
-        `tuple` of `unicode`.
-    :ivar unicode agreement:
+        `tuple` of `str`.
+    :ivar str agreement:
 
     """
     # on new-reg key server ignores 'key' and populates it based on
     # JWS.signature.combined.jwk
-    key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)
+    key: jose.JWK = jose.field('key', omitempty=True, 
decoder=jose.JWK.from_json)
     # Contact field implements special behavior to allow messages that clear 
existing
     # contacts while not expecting the `contact` field when loading from json.
     # This is implemented in the constructor and *_json methods.
-    contact = jose.Field('contact', omitempty=True, default=())
-    agreement = jose.Field('agreement', omitempty=True)
-    status = jose.Field('status', omitempty=True)
-    terms_of_service_agreed = jose.Field('termsOfServiceAgreed', 
omitempty=True)
-    only_return_existing = jose.Field('onlyReturnExisting', omitempty=True)
-    external_account_binding = jose.Field('externalAccountBinding', 
omitempty=True)
+    contact: Tuple[str, ...] = jose.field('contact', omitempty=True, 
default=())
+    agreement: str = jose.field('agreement', omitempty=True)
+    status: Status = jose.field('status', omitempty=True)
+    terms_of_service_agreed: bool = jose.field('termsOfServiceAgreed', 
omitempty=True)
+    only_return_existing: bool = jose.field('onlyReturnExisting', 
omitempty=True)
+    external_account_binding: Dict[str, Any] = 
jose.field('externalAccountBinding',
+                                                          omitempty=True)
 
     phone_prefix = 'tel:'
     email_prefix = 'mailto:'
 
     @classmethod
-    def from_data(cls, phone: Optional[str] = None, email: Optional[str] = 
None,
+    def from_data(cls: Type[GenericRegistration], phone: Optional[str] = None,
+                  email: Optional[str] = None,
                   external_account_binding: Optional[Dict[str, Any]] = None,
-                  **kwargs: Any) -> 'Registration':
+                  **kwargs: Any) -> GenericRegistration:
         """
         Create registration resource from contact details.
 
@@ -419,26 +445,26 @@
 class NewRegistration(ResourceMixin, Registration):
     """New registration."""
     resource_type = 'new-reg'
-    resource = fields.Resource(resource_type)
+    resource: str = fields.resource(resource_type)
 
 
 class UpdateRegistration(ResourceMixin, Registration):
     """Update registration."""
     resource_type = 'reg'
-    resource = fields.Resource(resource_type)
+    resource: str = fields.resource(resource_type)
 
 
 class RegistrationResource(ResourceWithURI):
     """Registration Resource.
 
     :ivar acme.messages.Registration body:
-    :ivar unicode new_authzr_uri: Deprecated. Do not use.
-    :ivar unicode terms_of_service: URL for the CA TOS.
+    :ivar str new_authzr_uri: Deprecated. Do not use.
+    :ivar str terms_of_service: URL for the CA TOS.
 
     """
-    body = jose.Field('body', decoder=Registration.from_json)
-    new_authzr_uri = jose.Field('new_authzr_uri', omitempty=True)
-    terms_of_service = jose.Field('terms_of_service', omitempty=True)
+    body: Registration = jose.field('body', decoder=Registration.from_json)
+    new_authzr_uri: str = jose.field('new_authzr_uri', omitempty=True)
+    terms_of_service: str = jose.field('terms_of_service', omitempty=True)
 
 
 class ChallengeBody(ResourceBody):
@@ -463,12 +489,12 @@
     # challenge object supports either one, but should be accessed through the
     # name "uri". In Client.answer_challenge, whichever one is set will be
     # used.
-    _uri = jose.Field('uri', omitempty=True, default=None)
-    _url = jose.Field('url', omitempty=True, default=None)
-    status = jose.Field('status', decoder=Status.from_json,
+    _uri: str = jose.field('uri', omitempty=True, default=None)
+    _url: str = jose.field('url', omitempty=True, default=None)
+    status: Status = jose.field('status', decoder=Status.from_json,
                         omitempty=True, default=STATUS_PENDING)
-    validated = fields.RFC3339Field('validated', omitempty=True)
-    error = jose.Field('error', decoder=Error.from_json,
+    validated: datetime.datetime = fields.rfc3339('validated', omitempty=True)
+    error: Error = jose.field('error', decoder=Error.from_json,
                        omitempty=True, default=None)
 
     def __init__(self, **kwargs: Any) -> None:
@@ -511,16 +537,16 @@
     """Challenge Resource.
 
     :ivar acme.messages.ChallengeBody body:
-    :ivar unicode authzr_uri: URI found in the 'up' ``Link`` header.
+    :ivar str authzr_uri: URI found in the 'up' ``Link`` header.
 
     """
-    body = jose.Field('body', decoder=ChallengeBody.from_json)
-    authzr_uri = jose.Field('authzr_uri')
+    body: ChallengeBody = jose.field('body', decoder=ChallengeBody.from_json)
+    authzr_uri: str = jose.field('authzr_uri')
 
     @property
     def uri(self) -> str:
         """The URL of the challenge body."""
-        return self.body.uri
+        return self.body.uri  # pylint: disable=no-member
 
 
 class Authorization(ResourceBody):
@@ -534,26 +560,26 @@
     :ivar datetime.datetime expires:
 
     """
-    identifier = jose.Field('identifier', decoder=Identifier.from_json, 
omitempty=True)
-    challenges = jose.Field('challenges', omitempty=True)
-    combinations = jose.Field('combinations', omitempty=True)
+    identifier: Identifier = jose.field('identifier', 
decoder=Identifier.from_json, omitempty=True)
+    challenges: List[ChallengeBody] = jose.field('challenges', omitempty=True)
+    combinations: Tuple[Tuple[int, ...], ...] = jose.field('combinations', 
omitempty=True)
 
-    status = jose.Field('status', omitempty=True, decoder=Status.from_json)
+    status: Status = jose.field('status', omitempty=True, 
decoder=Status.from_json)
     # TODO: 'expires' is allowed for Authorization Resources in
     # general, but for Key Authorization '[t]he "expires" field MUST
     # be absent'... then acme-spec gives example with 'expires'
     # present... That's confusing!
-    expires = fields.RFC3339Field('expires', omitempty=True)
-    wildcard = jose.Field('wildcard', omitempty=True)
+    expires: datetime.datetime = fields.rfc3339('expires', omitempty=True)
+    wildcard: bool = jose.field('wildcard', omitempty=True)
 
     # Mypy does not understand the josepy magic happening here, and falsely 
claims
     # that challenge is redefined. Let's ignore the type check here.
     @challenges.decoder  # type: ignore
-    def challenges(value: List[Mapping[str, Any]]) -> Tuple[ChallengeBody, 
...]:  # pylint: disable=no-self-argument,missing-function-docstring
+    def challenges(value: List[Dict[str, Any]]) -> Tuple[ChallengeBody, ...]:  
# type: ignore[misc]  # pylint: 
disable=no-self-argument,missing-function-docstring
         return tuple(ChallengeBody.from_json(chall) for chall in value)
 
     @property
-    def resolved_combinations(self) -> Tuple[Tuple[Dict[str, Any], ...], ...]:
+    def resolved_combinations(self) -> Tuple[Tuple[ChallengeBody, ...], ...]:
         """Combinations with challenges instead of indices."""
         return tuple(tuple(self.challenges[idx] for idx in combo)
                      for combo in self.combinations)  # pylint: 
disable=not-an-iterable
@@ -563,37 +589,37 @@
 class NewAuthorization(ResourceMixin, Authorization):
     """New authorization."""
     resource_type = 'new-authz'
-    resource = fields.Resource(resource_type)
+    resource: str = fields.resource(resource_type)
 
 
 class UpdateAuthorization(ResourceMixin, Authorization):
     """Update authorization."""
     resource_type = 'authz'
-    resource = fields.Resource(resource_type)
+    resource: str = fields.resource(resource_type)
 
 
 class AuthorizationResource(ResourceWithURI):
     """Authorization Resource.
 
     :ivar acme.messages.Authorization body:
-    :ivar unicode new_cert_uri: Deprecated. Do not use.
+    :ivar str new_cert_uri: Deprecated. Do not use.
 
     """
-    body = jose.Field('body', decoder=Authorization.from_json)
-    new_cert_uri = jose.Field('new_cert_uri', omitempty=True)
+    body: Authorization = jose.field('body', decoder=Authorization.from_json)
+    new_cert_uri: str = jose.field('new_cert_uri', omitempty=True)
 
 
 @Directory.register
 class CertificateRequest(ResourceMixin, jose.JSONObjectWithFields):
     """ACME new-cert request.
 
-    :ivar josepy.util.ComparableX509 csr:
+    :ivar jose.ComparableX509 csr:
         `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
 
     """
     resource_type = 'new-cert'
-    resource = fields.Resource(resource_type)
-    csr = jose.Field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
+    resource: str = fields.resource(resource_type)
+    csr: jose.ComparableX509 = jose.field('csr', decoder=jose.decode_csr, 
encoder=jose.encode_csr)
 
 
 class CertificateResource(ResourceWithURI):
@@ -601,27 +627,27 @@
 
     :ivar josepy.util.ComparableX509 body:
         `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
-    :ivar unicode cert_chain_uri: URI found in the 'up' ``Link`` header
+    :ivar str cert_chain_uri: URI found in the 'up' ``Link`` header
     :ivar tuple authzrs: `tuple` of `AuthorizationResource`.
 
     """
-    cert_chain_uri = jose.Field('cert_chain_uri')
-    authzrs = jose.Field('authzrs')
+    cert_chain_uri: str = jose.field('cert_chain_uri')
+    authzrs: Tuple[AuthorizationResource, ...] = jose.field('authzrs')
 
 
 @Directory.register
 class Revocation(ResourceMixin, jose.JSONObjectWithFields):
     """Revocation message.
 
-    :ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
-        `.ComparableX509`
+    :ivar jose.ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
+        `jose.ComparableX509`
 
     """
     resource_type = 'revoke-cert'
-    resource = fields.Resource(resource_type)
-    certificate = jose.Field(
+    resource: str = fields.resource(resource_type)
+    certificate: jose.ComparableX509 = jose.field(
         'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
-    reason = jose.Field('reason')
+    reason: int = jose.field('reason')
 
 
 class Order(ResourceBody):
@@ -638,19 +664,18 @@
     :ivar datetime.datetime expires: When the order expires.
     :ivar ~.Error error: Any error that occurred during finalization, if 
applicable.
     """
-    identifiers = jose.Field('identifiers', omitempty=True)
-    status = jose.Field('status', decoder=Status.from_json,
-                        omitempty=True)
-    authorizations = jose.Field('authorizations', omitempty=True)
-    certificate = jose.Field('certificate', omitempty=True)
-    finalize = jose.Field('finalize', omitempty=True)
-    expires = fields.RFC3339Field('expires', omitempty=True)
-    error = jose.Field('error', omitempty=True, decoder=Error.from_json)
+    identifiers: List[Identifier] = jose.field('identifiers', omitempty=True)
+    status: Status = jose.field('status', decoder=Status.from_json, 
omitempty=True)
+    authorizations: List[str] = jose.field('authorizations', omitempty=True)
+    certificate: str = jose.field('certificate', omitempty=True)
+    finalize: str = jose.field('finalize', omitempty=True)
+    expires: datetime.datetime = fields.rfc3339('expires', omitempty=True)
+    error: Error = jose.field('error', omitempty=True, decoder=Error.from_json)
 
     # Mypy does not understand the josepy magic happening here, and falsely 
claims
     # that identifiers is redefined. Let's ignore the type check here.
     @identifiers.decoder  # type: ignore
-    def identifiers(value: List[Mapping[str, Any]]) -> Tuple[Identifier, ...]: 
 # pylint: disable=no-self-argument,missing-function-docstring
+    def identifiers(value: List[Dict[str, Any]]) -> Tuple[Identifier, ...]:  # 
type: ignore[misc]  # pylint: 
disable=no-self-argument,missing-function-docstring
         return tuple(Identifier.from_json(identifier) for identifier in value)
 
 
@@ -658,7 +683,7 @@
     """Order Resource.
 
     :ivar acme.messages.Order body:
-    :ivar str csr_pem: The CSR this Order will be finalized with.
+    :ivar bytes csr_pem: The CSR this Order will be finalized with.
     :ivar authorizations: Fully-fetched AuthorizationResource objects.
     :vartype authorizations: `list` of `acme.messages.AuthorizationResource`
     :ivar str fullchain_pem: The fetched contents of the certificate URL
@@ -668,11 +693,13 @@
         finalization.
     :vartype alternative_fullchains_pem: `list` of `str`
     """
-    body = jose.Field('body', decoder=Order.from_json)
-    csr_pem = jose.Field('csr_pem', omitempty=True)
-    authorizations = jose.Field('authorizations')
-    fullchain_pem = jose.Field('fullchain_pem', omitempty=True)
-    alternative_fullchains_pem = jose.Field('alternative_fullchains_pem', 
omitempty=True)
+    body: Order = jose.field('body', decoder=Order.from_json)
+    csr_pem: bytes = jose.field('csr_pem', omitempty=True)
+    authorizations: List[AuthorizationResource] = jose.field('authorizations')
+    fullchain_pem: str = jose.field('fullchain_pem', omitempty=True)
+    alternative_fullchains_pem: List[str] = 
jose.field('alternative_fullchains_pem',
+                                                       omitempty=True)
+
 
 @Directory.register
 class NewOrder(Order):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/mixins.py 
new/acme-1.26.0/acme/mixins.py
--- old/acme-1.22.0/acme/mixins.py      2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/mixins.py      2022-04-05 19:41:26.000000000 +0200
@@ -65,4 +65,4 @@
             jobj.pop(uncompliant_field, None)
         return jobj
 
-    raise AttributeError('Method {0}() is not 
implemented.'.format(jobj_method))  # pragma: no cover
+    raise AttributeError(f'Method {jobj_method}() is not implemented.')  # 
pragma: no cover
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme/standalone.py 
new/acme-1.26.0/acme/standalone.py
--- old/acme-1.22.0/acme/standalone.py  2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/acme/standalone.py  2022-04-05 19:41:26.000000000 +0200
@@ -8,6 +8,7 @@
 import socketserver
 import threading
 from typing import Any
+from typing import cast
 from typing import List
 from typing import Mapping
 from typing import Optional
@@ -34,16 +35,15 @@
         else:
             self.address_family = socket.AF_INET
         self.certs = kwargs.pop("certs", {})
-        self.method = kwargs.pop(
-            "method", crypto_util._DEFAULT_SSL_METHOD)
+        self.method = kwargs.pop("method", crypto_util._DEFAULT_SSL_METHOD)
         self.allow_reuse_address = kwargs.pop("allow_reuse_address", True)
-        socketserver.TCPServer.__init__(self, *args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def _wrap_sock(self) -> None:
-        self.socket = crypto_util.SSLSocket(
+        self.socket = cast(socket.socket, crypto_util.SSLSocket(
             self.socket, cert_selection=self._cert_selection,
             alpn_selection=getattr(self, '_alpn_selection', None),
-            method=self.method)
+            method=self.method))
 
     def _cert_selection(self, connection: SSL.Connection
                         ) -> Tuple[crypto.PKey, crypto.X509]:  # pragma: no 
cover
@@ -190,7 +190,7 @@
             self.address_family = socket.AF_INET6
         else:
             self.address_family = socket.AF_INET
-        BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
+        super().__init__(*args, **kwargs)
 
 
 class HTTP01Server(HTTPServer, ACMEServerMixin):
@@ -198,8 +198,8 @@
 
     def __init__(self, server_address: Tuple[str, int], resources: 
Set[challenges.HTTP01],
                  ipv6: bool = False, timeout: int = 30) -> None:
-        HTTPServer.__init__(
-            self, server_address, HTTP01RequestHandler.partial_init(
+        super().__init__(
+            server_address, HTTP01RequestHandler.partial_init(
                 simple_http_resources=resources, timeout=timeout), ipv6=ipv6)
 
 
@@ -208,7 +208,7 @@
        affect the other."""
 
     def __init__(self, *args: Any, **kwargs: Any) -> None:
-        BaseDualNetworkedServers.__init__(self, HTTP01Server, *args, **kwargs)
+        super().__init__(HTTP01Server, *args, **kwargs)
 
 
 class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
@@ -226,7 +226,7 @@
     def __init__(self, *args: Any, **kwargs: Any) -> None:
         self.simple_http_resources = kwargs.pop("simple_http_resources", set())
         self._timeout = kwargs.pop('timeout', 30)
-        BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.server: HTTP01Server
 
     # In parent class BaseHTTPRequestHandler, 'timeout' is a class-level 
property but we
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme.egg-info/PKG-INFO 
new/acme-1.26.0/acme.egg-info/PKG-INFO
--- old/acme-1.22.0/acme.egg-info/PKG-INFO      2021-12-07 23:02:52.000000000 
+0100
+++ new/acme-1.26.0/acme.egg-info/PKG-INFO      2022-04-05 19:41:32.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: acme
-Version: 1.22.0
+Version: 1.26.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
@@ -12,14 +12,13 @@
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Topic :: Internet :: WWW/HTTP
 Classifier: Topic :: Security
-Requires-Python: >=3.6
+Requires-Python: >=3.7
 Provides-Extra: docs
 Provides-Extra: test
 License-File: LICENSE.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme.egg-info/SOURCES.txt 
new/acme-1.26.0/acme.egg-info/SOURCES.txt
--- old/acme-1.22.0/acme.egg-info/SOURCES.txt   2021-12-07 23:02:52.000000000 
+0100
+++ new/acme-1.26.0/acme.egg-info/SOURCES.txt   2022-04-05 19:41:33.000000000 
+0200
@@ -2,7 +2,6 @@
 MANIFEST.in
 README.rst
 pytest.ini
-setup.cfg
 setup.py
 acme/__init__.py
 acme/challenges.py
@@ -14,6 +13,7 @@
 acme/magic_typing.py
 acme/messages.py
 acme/mixins.py
+acme/py.typed
 acme/standalone.py
 acme/util.py
 acme.egg-info/PKG-INFO
@@ -72,6 +72,7 @@
 tests/testdata/csr.der
 tests/testdata/csr.pem
 tests/testdata/dsa512_key.pem
+tests/testdata/ec_secp384r1_key.pem
 tests/testdata/rsa1024_cert.pem
 tests/testdata/rsa1024_key.pem
 tests/testdata/rsa2048_cert.pem
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/acme.egg-info/requires.txt 
new/acme-1.26.0/acme.egg-info/requires.txt
--- old/acme-1.22.0/acme.egg-info/requires.txt  2021-12-07 23:02:52.000000000 
+0100
+++ new/acme-1.26.0/acme.egg-info/requires.txt  2022-04-05 19:41:33.000000000 
+0200
@@ -1,11 +1,11 @@
 cryptography>=2.5.0
-josepy>=1.9.0
+josepy>=1.13.0
 PyOpenSSL>=17.3.0
 pyrfc3339
-pytz
-requests>=2.14.2
+pytz>=2019.3
+requests>=2.20.0
 requests-toolbelt>=0.3.0
-setuptools>=39.0.1
+setuptools>=41.6.0
 
 [docs]
 Sphinx>=1.0
@@ -14,3 +14,4 @@
 [test]
 pytest
 pytest-xdist
+typing-extensions
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/setup.cfg new/acme-1.26.0/setup.cfg
--- old/acme-1.22.0/setup.cfg   2021-12-07 23:02:52.134568200 +0100
+++ new/acme-1.26.0/setup.cfg   2022-04-05 19:41:33.131385000 +0200
@@ -1,6 +1,3 @@
-[bdist_wheel]
-universal = 1
-
 [egg_info]
 tag_build = 
 tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/setup.py new/acme-1.26.0/setup.py
--- old/acme-1.22.0/setup.py    2021-12-07 23:02:46.000000000 +0100
+++ new/acme-1.26.0/setup.py    2022-04-05 19:41:27.000000000 +0200
@@ -3,17 +3,17 @@
 from setuptools import find_packages
 from setuptools import setup
 
-version = '1.22.0'
+version = '1.26.0'
 
 install_requires = [
     'cryptography>=2.5.0',
-    'josepy>=1.9.0',
+    'josepy>=1.13.0',
     'PyOpenSSL>=17.3.0',
     'pyrfc3339',
-    'pytz',
-    'requests>=2.14.2',
+    'pytz>=2019.3',
+    'requests>=2.20.0',
     'requests-toolbelt>=0.3.0',
-    'setuptools>=39.0.1',
+    'setuptools>=41.6.0',
 ]
 
 docs_extras = [
@@ -24,6 +24,7 @@
 test_extras = [
     'pytest',
     'pytest-xdist',
+    'typing-extensions',
 ]
 
 setup(
@@ -34,14 +35,13 @@
     author="Certbot Project",
     author_email='certbot-...@eff.org',
     license='Apache License 2.0',
-    python_requires='>=3.6',
+    python_requires='>=3.7',
     classifiers=[
         'Development Status :: 5 - Production/Stable',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: Apache Software License',
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/tests/challenges_test.py 
new/acme-1.26.0/tests/challenges_test.py
--- old/acme-1.22.0/tests/challenges_test.py    2021-12-07 23:02:45.000000000 
+0100
+++ new/acme-1.26.0/tests/challenges_test.py    2022-04-05 19:41:26.000000000 
+0200
@@ -6,6 +6,7 @@
 import josepy as jose
 import OpenSSL
 import requests
+from josepy.jwk import JWKEC
 
 from acme import errors
 
@@ -401,8 +402,11 @@
         hash(DNS.from_json(self.jmsg))
 
     def test_gen_check_validation(self):
-        self.assertTrue(self.msg.check_validation(
-            self.msg.gen_validation(KEY), KEY.public_key()))
+        ec_key_secp384r1 = 
JWKEC(key=test_util.load_ecdsa_private_key('ec_secp384r1_key.pem'))
+        for key, alg in [(KEY, jose.RS256), (ec_key_secp384r1, jose.ES384)]:
+            with self.subTest(key=key, alg=alg):
+                self.assertTrue(self.msg.check_validation(
+                    self.msg.gen_validation(key, alg=alg), key.public_key()))
 
     def test_gen_check_validation_wrong_key(self):
         key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem'))
@@ -423,8 +427,7 @@
             payload=self.msg.update(
                 token=b'x' * 20).json_dumps().encode('utf-8'),
             alg=jose.RS256, key=KEY)
-        self.assertFalse(self.msg.check_validation(
-            bad_validation, KEY.public_key()))
+        self.assertFalse(self.msg.check_validation(bad_validation, 
KEY.public_key()))
 
     def test_gen_response(self):
         with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen:
@@ -435,8 +438,14 @@
         self.assertEqual(response.validation, mock.sentinel.validation)
 
     def test_validation_domain_name(self):
-        self.assertEqual(
-            '_acme-challenge.le.wtf', 
self.msg.validation_domain_name('le.wtf'))
+        self.assertEqual('_acme-challenge.le.wtf', 
self.msg.validation_domain_name('le.wtf'))
+
+    def test_validation_domain_name_ecdsa(self):
+        ec_key_secp384r1 = 
JWKEC(key=test_util.load_ecdsa_private_key('ec_secp384r1_key.pem'))
+        self.assertIs(self.msg.check_validation(
+            self.msg.gen_validation(ec_key_secp384r1, alg=jose.ES384),
+            ec_key_secp384r1.public_key()), True
+        )
 
 
 class DNSResponseTest(unittest.TestCase):
@@ -474,8 +483,7 @@
         hash(DNSResponse.from_json(self.jmsg_from))
 
     def test_check_validation(self):
-        self.assertTrue(
-            self.msg.check_validation(self.chall, KEY.public_key()))
+        self.assertTrue(self.msg.check_validation(self.chall, 
KEY.public_key()))
 
 
 class JWSPayloadRFC8555Compliant(unittest.TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/tests/fields_test.py 
new/acme-1.26.0/tests/fields_test.py
--- old/acme-1.22.0/tests/fields_test.py        2021-12-07 23:02:45.000000000 
+0100
+++ new/acme-1.26.0/tests/fields_test.py        2022-04-05 19:41:26.000000000 
+0200
@@ -10,8 +10,8 @@
     """Tests for acme.fields.Fixed."""
 
     def setUp(self):
-        from acme.fields import Fixed
-        self.field = Fixed('name', 'x')
+        from acme.fields import fixed
+        self.field = fixed('name', 'x')
 
     def test_decode(self):
         self.assertEqual('x', self.field.decode('x'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/tests/standalone_test.py 
new/acme-1.26.0/tests/standalone_test.py
--- old/acme-1.22.0/tests/standalone_test.py    2021-12-07 23:02:45.000000000 
+0100
+++ new/acme-1.26.0/tests/standalone_test.py    2022-04-05 19:41:26.000000000 
+0200
@@ -165,7 +165,6 @@
 class BaseDualNetworkedServersTest(unittest.TestCase):
     """Test for acme.standalone.BaseDualNetworkedServers."""
 
-
     class SingleProtocolServer(socketserver.TCPServer):
         """Server that only serves on a single protocol. FreeBSD has this 
behavior for AF_INET6."""
         def __init__(self, *args, **kwargs):
@@ -175,7 +174,7 @@
                 kwargs["bind_and_activate"] = False
             else:
                 self.address_family = socket.AF_INET
-            socketserver.TCPServer.__init__(self, *args, **kwargs)
+            super().__init__(*args, **kwargs)
             if ipv6:
                 # NB: On Windows, socket.IPPROTO_IPV6 constant may be missing.
                 # We use the corresponding value (41) instead.
@@ -202,7 +201,6 @@
 
         self.assertEqual(em.exception.errno, EADDRINUSE)
 
-
     def test_ports_equal(self):
         from acme.standalone import BaseDualNetworkedServers
         servers = BaseDualNetworkedServers(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/tests/test_util.py 
new/acme-1.26.0/tests/test_util.py
--- old/acme-1.22.0/tests/test_util.py  2021-12-07 23:02:45.000000000 +0100
+++ new/acme-1.26.0/tests/test_util.py  2022-04-05 19:41:26.000000000 +0200
@@ -10,6 +10,7 @@
 import josepy as jose
 from OpenSSL import crypto
 import pkg_resources
+from josepy.util import ComparableECKey
 
 
 def load_vector(*names):
@@ -60,6 +61,14 @@
         load_vector(*names), password=None, backend=default_backend()))
 
 
+def load_ecdsa_private_key(*names):
+    """Load ECDSA private key."""
+    loader = _guess_loader(names[-1], serialization.load_pem_private_key,
+                           serialization.load_der_private_key)
+    return ComparableECKey(loader(
+        load_vector(*names), password=None, backend=default_backend()))
+
+
 def load_pyopenssl_private_key(*names):
     """Load pyOpenSSL private key."""
     loader = _guess_loader(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/tests/testdata/README 
new/acme-1.26.0/tests/testdata/README
--- old/acme-1.22.0/tests/testdata/README       2021-12-07 23:02:45.000000000 
+0100
+++ new/acme-1.26.0/tests/testdata/README       2022-04-05 19:41:26.000000000 
+0200
@@ -15,3 +15,7 @@
   openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -x509 -outform 
DER > cert.der
   openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -x509 > 
rsa2048_cert.pem
   openssl req -key rsa1024_key.pem -new -subj '/CN=example.com' -x509 > 
rsa1024_cert.pem
+
+and for the elliptic key curves:
+
+  openssl genpkey -algorithm EC -out ec_secp384r1.pem -pkeyopt 
ec_paramgen_curve:P-384 -pkeyopt ec_param_enc:named_curve
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-1.22.0/tests/testdata/ec_secp384r1_key.pem 
new/acme-1.26.0/tests/testdata/ec_secp384r1_key.pem
--- old/acme-1.22.0/tests/testdata/ec_secp384r1_key.pem 1970-01-01 
01:00:00.000000000 +0100
+++ new/acme-1.26.0/tests/testdata/ec_secp384r1_key.pem 2022-04-05 
19:41:26.000000000 +0200
@@ -0,0 +1,6 @@
+-----BEGIN PRIVATE KEY-----
+MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDArTn0pbFk3xHfKeXte
+xJgS4JVdJQ8mqvezhaNpULZPnwb+mcKLlrj6f5SRM52yREGhZANiAAQcrMoPMVqV
+rHnDGGz5HUKLNmXfChlNgsrwsruawXF+M283CA6eckAjTXNyiC/ounWmvtoKsZG0
+2UQOfQUNSCANId/r986yRGc03W6RJSkcRp86qBYjNsLgbZpber/3+M4=
+-----END PRIVATE KEY-----

Reply via email to