Repository: certbot/certbot (language: python)

Issue:
Unpin josepy
In https://github.com/certbot/certbot/pull/9112 I'm proposing pinning back `josepy`. We should undo this and make our mypy tests pass with the new version of `josepy`.

This will be fixed by https://github.com/certbot/certbot/pull/9073.

cc @adferrand 


Discussion / hints:

Fix patch:
diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py
index de842a261b5..94d121a2163 100644
--- a/acme/acme/challenges.py
+++ b/acme/acme/challenges.py
@@ -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 @@ class Challenge(jose.TypedJSONObjectWithFields):
     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 @@ class ChallengeResponse(ResourceMixin, TypeMixin, jose.TypedJSONObjectWithFields
     """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 @@ class UnrecognizedChallenge(Challenge):
     :ivar jobj: Original JSON decoded object.
 
     """
+    jobj: Dict[str, Any]
 
     def __init__(self, jobj: Mapping[str, Any]) -> None:
         super().__init__()
@@ -85,7 +91,7 @@ class _TokenChallenge(Challenge):
     """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 @@ def good_token(self) -> bool:  # XXX: @token.decoder
 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 @@ def verify(self, chall: 'KeyAuthorizationChallenge', account_public_key: jose.JW
         :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 @@ def to_partial_json(self) -> Dict[str, Any]:
         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 @@ def key_authorization(self, account_key: jose.JWK) -> str:
         """Generate Key Authorization.
 
         :param JWK account_key:
-        :rtype unicode:
+        :rtype str:
 
         """
         return self.encode("token") + "." + jose.b64encode(
@@ -229,7 +238,7 @@ def simple_verify(self, chall: 'DNS01', domain: str, account_public_key: jose.JW
         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 @@ def validation(self, account_key: jose.JWK, **unused_kwargs: Any) -> str:
         """Generate validation.
 
         :param JWK account_key:
-        :rtype: unicode
+        :rtype: str
 
         """
         return jose.b64encode(hashlib.sha256(self.key_authorization(
@@ -266,7 +275,8 @@ def validation(self, account_key: jose.JWK, **unused_kwargs: Any) -> str:
     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)
@@ -293,7 +303,7 @@ def simple_verify(self, chall: 'HTTP01', domain: str, account_public_key: jose.J
         """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 @@ class HTTP01(KeyAuthorizationChallenge):
     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 @@ def uri(self, domain: str) -> str:
         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 @@ def validation(self, account_key: jose.JWK, **unused_kwargs: Any) -> str:
         """Generate validation.
 
         :param JWK account_key:
-        :rtype: unicode
+        :rtype: str
 
         """
         return self.key_authorization(account_key)
@@ -409,7 +419,7 @@ def gen_cert(self, domain: str, key: Optional[crypto.PKey] = None, bits: int = 2
                  ) -> 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 @@ def probe_cert(self, domain: str, host: Optional[str] = None,
                    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 probe_cert(self, domain: str, host: Optional[str] = None,
     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.
@@ -523,7 +533,7 @@ def validation(self, account_key: jose.JWK, **kwargs: Any) -> Tuple[crypto.X509,
         """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 @@ def validation(self, account_key: jose.JWK, **kwargs: Any) -> Tuple[crypto.X509,
         :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 @@ def gen_response(self, account_key: jose.JWK, **kwargs: Any) -> 'DNSResponse':
         :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 @@ class DNSResponse(ChallengeResponse):
     """
     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 @@ def check_validation(self, chall: 'DNS', account_public_key: jose.JWK) -> bool:
         :rtype: bool
 
         """
-        return chall.check_validation(cast(jose.JWS, self.validation), account_public_key)
+        return chall.check_validation(self.validation, account_public_key)
diff --git a/acme/acme/client.py b/acme/acme/client.py
index ae2f261aa3e..7e87e74741b 100644
--- a/acme/acme/client.py
+++ b/acme/acme/client.py
@@ -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 @@ def _authzr_from_response(self, response: requests.Response,
         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 @@ def answer_challenge(self, challb: messages.ChallengeBody, response: requests.Re
         :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 @@ def poll_and_request_issuance(self, csr: jose.ComparableX509,
             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 @@ def revoke(self, cert: jose.ComparableX509, rsn: int) -> None:
         :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 @@ def poll_authorizations(self, orderr: messages.OrderResource, deadline: datetime
         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,11 +899,11 @@ def _assess_tos(tos: str) -> None:
                 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:
@@ -970,7 +972,8 @@ def finalize_order(self, orderr: messages.OrderResource, deadline: datetime.date
                     '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 +1059,7 @@ def __del__(self) -> None:
             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 +1067,7 @@ def _wrap_in_jws(self, obj: jose.JSONDeSerializable, nonce: str, url: str,
         :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):
@@ -1082,7 +1085,7 @@ def _wrap_in_jws(self, obj: jose.JSONDeSerializable, nonce: str, url: str,
             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,
diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py
index b337e7697d4..eb667292680 100644
--- a/acme/acme/crypto_util.py
+++ b/acme/acme/crypto_util.py
@@ -278,7 +278,7 @@ def _pyopenssl_cert_or_req_san(cert_or_req: Union[crypto.X509, crypto.X509Req])
     :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 @@ def _pyopenssl_cert_or_req_san_ip(cert_or_req: Union[crypto.X509, crypto.X509Req
     :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 @@ def _pyopenssl_extract_san_list_raw(cert_or_req: Union[crypto.X509, crypto.X509R
     :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 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
                 ) -> 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 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
     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_pyopenssl_chain(chain: List[crypto.X509], filetype: int = crypto.FILETY
 
     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 --git a/acme/acme/fields.py b/acme/acme/fields.py
index b1548902033..191231df2e2 100644
--- a/acme/acme/fields.py
+++ b/acme/acme/fields.py
@@ -56,8 +56,8 @@ class Resource(jose.Field):
 
     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 @@ def decode(self, value: Any) -> Any:
                 '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 --git a/acme/acme/jws.py b/acme/acme/jws.py
index bde1bb59027..2a9627c6d5c 100644
--- a/acme/acme/jws.py
+++ b/acme/acme/jws.py
@@ -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 @@ def nonce(value: str) -> bytes:  # pylint: disable=no-self-argument,missing-func
 
 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 Signature(jose.Signature):
 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 --git a/acme/acme/messages.py b/acme/acme/messages.py
index 80dacb67478..c02a600da12 100644
--- a/acme/acme/messages.py
+++ b/acme/acme/messages.py
@@ -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:"
 
@@ -75,20 +84,20 @@ class Error(jose.JSONObjectWithFields, errors.Error):
 
     https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
 
-    :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 @@ def with_code(cls, code: str, **kwargs: Any) -> 'Error':
         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 @@ def code(self) -> Optional[str]:
         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]
@@ -164,7 +173,7 @@ def __hash__(self) -> int:
 
 class Status(_Constant):
     """ACME "status" field."""
-    POSSIBLE_NAMES: Dict[str, 'Status'] = {}
+    POSSIBLE_NAMES: Dict[str, _Constant] = {}
 
 
 STATUS_UNKNOWN = Status('unknown')
@@ -179,7 +188,7 @@ class Status(_Constant):
 
 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 +199,35 @@ class Identifier(jose.JSONObjectWithFields):
     """ACME identifier.
 
     :ivar IdentifierType typ:
-    :ivar unicode value:
+    :ivar str value:
+
+    """
+    typ: IdentifierType = jose.field('type', decoder=IdentifierType.from_json)
+    value: str = jose.field('value')
+
 
+class HasResourceType(Protocol):
     """
-    typ = jose.Field('type', decoder=IdentifierType.from_json)
-    value = jose.Field('value')
+    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 +248,14 @@ def _internal_name(self, name: str) -> str:
             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 +274,7 @@ def __getattr__(self, name: str) -> Any:
         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 +295,16 @@ class Resource(jose.JSONObjectWithFields):
     :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 +330,40 @@ def from_data(cls, account_public_key: jose.JWK, kid: str, hmac_key: str,
         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 +446,26 @@ def emails(self) -> Tuple[str, ...]:
 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 +490,12 @@ class ChallengeBody(ResourceBody):
     # 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 +538,16 @@ class ChallengeResource(Resource):
     """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 +561,26 @@ class Authorization(ResourceBody):
     :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 +590,37 @@ def resolved_combinations(self) -> Tuple[Tuple[Dict[str, Any], ...], ...]:
 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 +628,27 @@ class CertificateResource(ResourceWithURI):
 
     :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 +665,18 @@ class Order(ResourceBody):
     :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 +684,7 @@ class OrderResource(ResourceWithURI):
     """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 +694,13 @@ class OrderResource(ResourceWithURI):
         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 --git a/acme/acme/standalone.py b/acme/acme/standalone.py
index 5dfb271367e..f2df276a49e 100644
--- a/acme/acme/standalone.py
+++ b/acme/acme/standalone.py
@@ -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
@@ -39,10 +40,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
         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
diff --git a/acme/setup.py b/acme/setup.py
index e5d82220282..fb97eb6ad0f 100644
--- a/acme/setup.py
+++ b/acme/setup.py
@@ -7,7 +7,7 @@
 
 install_requires = [
     'cryptography>=2.5.0',
-    'josepy>=1.9.0',
+    'josepy>=1.10.0',
     'PyOpenSSL>=17.3.0',
     'pyrfc3339',
     'pytz',
@@ -24,6 +24,7 @@
 test_extras = [
     'pytest',
     'pytest-xdist',
+    'typing-extensions',
 ]
 
 setup(
diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py
index 73b6a1e1b3a..9b814f60dec 100644
--- a/certbot-apache/certbot_apache/_internal/configurator.py
+++ b/certbot-apache/certbot_apache/_internal/configurator.py
@@ -22,11 +22,9 @@
 from typing import Union
 
 from acme import challenges
-from acme.challenges import Challenge
 from certbot import achallenges
 from certbot import errors
 from certbot import util
-from certbot.achallenges import KeyAuthorizationAnnotatedChallenge
 from certbot.compat import filesystem
 from certbot.compat import os
 from certbot.display import util as display_util
@@ -240,7 +238,7 @@ def __init__(self, *args: Any, **kwargs: Any):
         # Add name_server association dict
         self.assoc: Dict[str, obj.VirtualHost] = {}
         # Outstanding challenges
-        self._chall_out: Set[KeyAuthorizationAnnotatedChallenge] = set()
+        self._chall_out: Set[achallenges.AnnotatedChallenge] = set()
         # List of vhosts configured per wildcard domain on this run.
         # used by deploy_cert() and enhance()
         self._wildcard_vhosts: Dict[str, List[obj.VirtualHost]] = {}
@@ -2532,9 +2530,8 @@ def get_chall_pref(self, unused_domain: str) -> Sequence[Type[challenges.HTTP01]
         """Return list of challenge preferences."""
         return [challenges.HTTP01]
 
-    def perform(
-        self, achalls: List[KeyAuthorizationAnnotatedChallenge]
-    ) -> List[Challenge]:
+    def perform(self, achalls: List[achallenges.AnnotatedChallenge]
+                ) -> List[challenges.ChallengeResponse]:
         """Perform the configuration related challenge.
 
         This function currently assumes all challenges will be fulfilled.
@@ -2543,10 +2540,13 @@ def perform(
 
         """
         self._chall_out.update(achalls)
-        responses: List[Optional[Challenge]] = [None] * len(achalls)
+        responses: List[Optional[challenges.ChallengeResponse]] = [None] * len(achalls)
         http_doer = http_01.ApacheHttp01(self)
 
         for i, achall in enumerate(achalls):
+            if not isinstance(achall, achallenges.KeyAuthorizationAnnotatedChallenge):
+                raise errors.Error("Challenge should be an instance "  # pragma: no cover
+                                   "of KeyAuthorizationAnnotatedChallenge")
             # Currently also have chall_doer hold associated index of the
             # challenge. This helps to put all of the responses back together
             # when they are all complete.
@@ -2560,18 +2560,17 @@ def perform(
             self.restart()
 
             # TODO: Remove this dirty hack. We need to determine a reliable way
-            # of identifying when the new configuration is being used.
+            #       of identifying when the new configuration is being used.
             time.sleep(3)
 
             self._update_responses(responses, http_response, http_doer)
 
-        # We assume all challenges has been fulfilled as described in the function documentation.
-        return cast(List[Challenge], responses)
+        return [response for response in responses if response]
 
     def _update_responses(
         self,
-        responses: List[Optional[challenges.HTTP01Response]],
-        chall_response: List[Challenge],
+        responses: List[Optional[challenges.ChallengeResponse]],
+        chall_response: List[challenges.KeyAuthorizationChallengeResponse],
         chall_doer: http_01.ApacheHttp01
     ) -> None:
         # Go through all of the challenges and assign them to the proper
@@ -2580,7 +2579,7 @@ def _update_responses(
         for i, resp in enumerate(chall_response):
             responses[chall_doer.indices[i]] = resp
 
-    def cleanup(self, achalls: List[KeyAuthorizationAnnotatedChallenge]) -> None:
+    def cleanup(self, achalls: List[achallenges.AnnotatedChallenge]) -> None:
         """Revert all challenges."""
         self._chall_out.difference_update(achalls)
 
diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py
index 2fe98172048..eed849dbec6 100644
--- a/certbot-apache/certbot_apache/_internal/http_01.py
+++ b/certbot-apache/certbot_apache/_internal/http_01.py
@@ -1,12 +1,11 @@
 """A class that performs HTTP-01 challenges for Apache"""
 import errno
 import logging
-from typing import Any
 from typing import List
 from typing import Set
 from typing import TYPE_CHECKING
 
-from acme.challenges import HTTP01Response
+from acme.challenges import KeyAuthorizationChallengeResponse
 from certbot import errors
 from certbot.achallenges import KeyAuthorizationAnnotatedChallenge
 from certbot.compat import filesystem
@@ -67,7 +66,7 @@ def __init__(self, configurator: "ApacheConfigurator") -> None:
             "http_challenges")
         self.moded_vhosts: Set[VirtualHost] = set()
 
-    def perform(self) -> List[KeyAuthorizationAnnotatedChallenge]:
+    def perform(self) -> List[KeyAuthorizationChallengeResponse]:
         """Perform all HTTP-01 challenges."""
         if not self.achalls:
             return []
@@ -182,7 +181,7 @@ def _unnamed_vhosts(self) -> List[VirtualHost]:
         """Return all VirtualHost objects with no ServerName"""
         return [vh for vh in self.configurator.vhosts if vh.name is None]
 
-    def _set_up_challenges(self) -> List[HTTP01Response]:
+    def _set_up_challenges(self) -> List[KeyAuthorizationChallengeResponse]:
         if not os.path.isdir(self.challenge_dir):
             old_umask = filesystem.umask(0o022)
             try:
@@ -200,9 +199,8 @@ def _set_up_challenges(self) -> List[HTTP01Response]:
 
         return responses
 
-    def _set_up_challenge(self, achall: KeyAuthorizationAnnotatedChallenge) -> HTTP01Response:
-        response: HTTP01Response
-        validation: Any
+    def _set_up_challenge(self, achall: KeyAuthorizationAnnotatedChallenge
+                          ) -> KeyAuthorizationChallengeResponse:
         response, validation = achall.response_and_validation()
 
         name: str = os.path.join(self.challenge_dir, achall.chall.encode("token"))
diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py
index d3fdd5e8cc2..8cf6d99660d 100644
--- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py
+++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py
@@ -138,7 +138,7 @@ def add_txt_record(self, record_name: str, record_content: str, record_ttl: int)
         except Exception as e:
             raise errors.PluginError('Encountered error adding TXT record: {0}'
                                      .format(e))
-        rcode = response.rcode()
+        rcode = response.rcode()  # type: ignore[attr-defined]
 
         if rcode == dns.rcode.NOERROR:
             logger.debug('Successfully added TXT record %s', record_name)
@@ -173,7 +173,7 @@ def del_txt_record(self, record_name: str, record_content: str) -> None:
         except Exception as e:
             raise errors.PluginError('Encountered error deleting TXT record: {0}'
                                      .format(e))
-        rcode = response.rcode()
+        rcode = response.rcode()  # type: ignore[attr-defined]
 
         if rcode == dns.rcode.NOERROR:
             logger.debug('Successfully deleted TXT record %s', record_name)
@@ -223,11 +223,13 @@ def _query_soa(self, domain_name: str) -> bool:
             except (OSError, dns.exception.Timeout) as e:
                 logger.debug('TCP query failed, fallback to UDP: %s', e)
                 response = dns.query.udp(request, self.server, self._default_timeout, self.port)
-            rcode = response.rcode()
+            rcode = response.rcode()  # type: ignore[attr-defined]
 
             # Authoritative Answer bit should be set
-            if (rcode == dns.rcode.NOERROR and response.get_rrset(response.answer,
-                domain, dns.rdataclass.IN, dns.rdatatype.SOA) and response.flags & dns.flags.AA):
+            if (rcode == dns.rcode.NOERROR
+                    and response.get_rrset(response.answer,  # type: ignore[attr-defined]
+                                           domain, dns.rdataclass.IN, dns.rdatatype.SOA)
+                    and response.flags & dns.flags.AA):
                 logger.debug('Received authoritative SOA response for %s', domain_name)
                 return True
 
diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py
index fb819f1946c..39784110cf4 100644
--- a/certbot-nginx/certbot_nginx/_internal/configurator.py
+++ b/certbot-nginx/certbot_nginx/_internal/configurator.py
@@ -1180,7 +1180,7 @@ def get_chall_pref(self, unused_domain: str) -> List[Type[challenges.Challenge]]
 
     # Entry point in main.py for performing challenges
     def perform(self, achalls: List[achallenges.AnnotatedChallenge]
-                ) -> List[challenges.HTTP01Response]:
+                ) -> List[challenges.ChallengeResponse]:
         """Perform the configuration related challenge.
 
         This function currently assumes all challenges will be fulfilled.
@@ -1189,13 +1189,16 @@ def perform(self, achalls: List[achallenges.AnnotatedChallenge]
 
         """
         self._chall_out += len(achalls)
-        responses: List[Optional[challenges.HTTP01Response]] = [None] * len(achalls)
+        responses: List[Optional[challenges.ChallengeResponse]] = [None] * len(achalls)
         http_doer = http_01.NginxHttp01(self)
 
         for i, achall in enumerate(achalls):
             # Currently also have chall_doer hold associated index of the
             # challenge. This helps to put all of the responses back together
             # when they are all complete.
+            if not isinstance(achall, achallenges.KeyAuthorizationAnnotatedChallenge):
+                raise errors.Error("Challenge should be an instance "
+                                   "of KeyAuthorizationAnnotatedChallenge")
             http_doer.add_chall(achall, i)
 
         http_response = http_doer.perform()
diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py
index 6f61bfb6f52..f9988007ede 100644
--- a/certbot-nginx/certbot_nginx/_internal/http_01.py
+++ b/certbot-nginx/certbot_nginx/_internal/http_01.py
@@ -11,7 +11,7 @@
 from certbot_nginx._internal.obj import Addr
 
 from acme import challenges
-from acme.challenges import HTTP01Response
+from acme.challenges import KeyAuthorizationChallengeResponse
 from certbot import errors
 from certbot.achallenges import KeyAuthorizationAnnotatedChallenge
 from certbot.compat import os
@@ -49,10 +49,10 @@ def __init__(self, configurator: "NginxConfigurator") -> None:
         self.challenge_conf = os.path.join(
             configurator.config.config_dir, "le_http_01_cert_challenge.conf")
 
-    def perform(self) -> List[HTTP01Response]:
+    def perform(self) -> List[KeyAuthorizationChallengeResponse]:
         """Perform a challenge on Nginx.
 
-        :returns: list of :class:`certbot.acme.challenges.HTTP01Response`
+        :returns: list of :class:`acme.challenges.KeyAuthorizationChallengeResponse`
         :rtype: list
 
         """
diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py
index 70a55be3a95..0fdc71dfa90 100644
--- a/certbot-nginx/certbot_nginx/_internal/nginxparser.py
+++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py
@@ -2,6 +2,7 @@
 # Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed)
 import copy
 import logging
+import operator
 import typing
 from typing import Any
 from typing import IO
@@ -167,13 +168,14 @@ def _coerce(self, inbound: Any) -> Tuple[Any, Any]:
                 inbound = UnspacedList(inbound)
             return inbound, inbound.spaced
 
-    def insert(self, i: int, x: Any) -> None:
+    def insert(self, i: "SupportsIndex", x: Any) -> None:
         """Insert object before index."""
+        idx = operator.index(i)
         item, spaced_item = self._coerce(x)
-        slicepos = self._spaced_position(i) if i < len(self) else len(self.spaced)
+        slicepos = self._spaced_position(idx) if idx < len(self) else len(self.spaced)
         self.spaced.insert(slicepos, spaced_item)
         if not spacey(item):
-            super().insert(i, item)
+            super().insert(idx, item)
         self.dirty = True
 
     def append(self, x: Any) -> None:
@@ -246,7 +248,7 @@ def is_dirty(self) -> bool:
 
     def _spaced_position(self, idx: "SupportsIndex") -> int:
         """Convert from indexes in the unspaced list to positions in the spaced one"""
-        int_idx = idx.__index__()
+        int_idx = operator.index(idx)
         pos = spaces = 0
         # Normalize indexes like list[-1] etc, and save the result
         if int_idx < 0:
diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py
index 041fe9cf58d..8c45efe21fa 100644
--- a/certbot/certbot/_internal/account.py
+++ b/certbot/certbot/_internal/account.py
@@ -54,9 +54,9 @@ class Meta(jose.JSONObjectWithFields):
             cross-machine migration scenarios.
 
         """
-        creation_dt = acme_fields.RFC3339Field("creation_dt")
-        creation_host = jose.Field("creation_host")
-        register_to_eff = jose.Field("register_to_eff", omitempty=True)
+        creation_dt: datetime.datetime = acme_fields.rfc3339("creation_dt")
+        creation_host: str = jose.field("creation_host")
+        register_to_eff: str = jose.field("register_to_eff", omitempty=True)
 
     def __init__(self, regr: messages.RegistrationResource, key: jose.JWK,
                  meta: Optional['Meta'] = None) -> None:
@@ -135,7 +135,7 @@ class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource):
        continue to write out this field for some time so older
        clients don't crash in that scenario.
     """
-    new_authzr_uri = jose.Field('new_authzr_uri')
+    new_authzr_uri: str = jose.field('new_authzr_uri')
 
 
 class AccountFileStorage(interfaces.AccountStorage):
diff --git a/certbot/certbot/_internal/auth_handler.py b/certbot/certbot/_internal/auth_handler.py
index 0fa1daf8e79..08dc8be35af 100644
--- a/certbot/certbot/_internal/auth_handler.py
+++ b/certbot/certbot/_internal/auth_handler.py
@@ -6,6 +6,7 @@
 from typing import Iterable
 from typing import List
 from typing import Optional
+from typing import Sequence
 from typing import Tuple
 from typing import Type
 
@@ -272,7 +273,7 @@ def _cleanup_challenges(self, achalls: List[achallenges.AnnotatedChallenge]) ->
         self.auth.cleanup(achalls)
 
     def _challenge_factory(self, authzr: messages.AuthorizationResource,
-                           path: List[int]) -> List[achallenges.AnnotatedChallenge]:
+                           path: Sequence[int]) -> List[achallenges.AnnotatedChallenge]:
         """Construct Namedtuple Challenges
 
         :param messages.AuthorizationResource authzr: authorization
@@ -350,7 +351,7 @@ def challb_to_achall(challb: messages.ChallengeBody, account_key: josepy.JWK,
 
 def gen_challenge_path(challbs: List[messages.ChallengeBody],
                        preferences: List[Type[challenges.Challenge]],
-                       combinations: Tuple[List[int], ...]) -> List[int]:
+                       combinations: Tuple[Tuple[int, ...], ...]) -> Tuple[int, ...]:
     """Generate a plan to get authority over the identity.
 
     .. todo:: This can be possibly be rewritten to use resolved_combinations.
@@ -383,8 +384,8 @@ def gen_challenge_path(challbs: List[messages.ChallengeBody],
 
 def _find_smart_path(challbs: List[messages.ChallengeBody],
                      preferences: List[Type[challenges.Challenge]],
-                     combinations: Tuple[List[int], ...]
-                     ) -> List[int]:
+                     combinations: Tuple[Tuple[int, ...], ...]
+                     ) -> Tuple[int, ...]:
     """Find challenge path with server hints.
 
     Can be called if combinations is included. Function uses a simple
@@ -399,7 +400,7 @@ def _find_smart_path(challbs: List[messages.ChallengeBody],
 
     # max_cost is now equal to sum(indices) + 1
 
-    best_combo: Optional[List[int]] = None
+    best_combo: Optional[Tuple[int, ...]] = None
     # Set above completing all of the available challenges
     best_combo_cost = max_cost
 
@@ -422,7 +423,7 @@ def _find_smart_path(challbs: List[messages.ChallengeBody],
 
 
 def _find_dumb_path(challbs: List[messages.ChallengeBody],
-                    preferences: List[Type[challenges.Challenge]]) -> List[int]:
+                    preferences: List[Type[challenges.Challenge]]) -> Tuple[int, ...]:
     """Find challenge path without server hints.
 
     Should be called if the combinations hint is not included by the
@@ -440,7 +441,7 @@ def _find_dumb_path(challbs: List[messages.ChallengeBody],
         else:
             raise _report_no_chall_path(challbs)
 
-    return path
+    return tuple(path)
 
 
 def _report_no_chall_path(challbs: List[messages.ChallengeBody]) -> errors.AuthorizationError:
diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py
index fec016ec77e..ad5d99b610e 100644
--- a/certbot/certbot/_internal/client.py
+++ b/certbot/certbot/_internal/client.py
@@ -251,7 +251,7 @@ def perform_registration(acme: acme_client.ClientV2, config: configuration.Names
             raise errors.Error("The ACME client must be an instance of "
                                "acme.client.BackwardsCompatibleClientV2")
     except messages.Error as e:
-        if e.code in ('invalidEmail', 'invalidContact'):
+        if e.code in ("invalidEmail", "invalidContact"):
             if config.noninteractive_mode:
                 msg = ("The ACME server believes %s is an invalid email address. "
                        "Please ensure it is a valid email and attempt "
diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py
index dc45ae27152..6c69b7e5beb 100644
--- a/certbot/certbot/_internal/plugins/manual.py
+++ b/certbot/certbot/_internal/plugins/manual.py
@@ -98,7 +98,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
         super().__init__(*args, **kwargs)
         self.reverter = reverter.Reverter(self.config)
         self.reverter.recovery_routine()
-        self.env: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] = {}
+        self.env: Dict[achallenges.AnnotatedChallenge, Dict[str, str]] = {}
         self.subsequent_dns_challenge = False
         self.subsequent_any_challenge = False
 
diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py
index f70d2ed9eb2..826acb2b178 100644
--- a/certbot/certbot/_internal/plugins/standalone.py
+++ b/certbot/certbot/_internal/plugins/standalone.py
@@ -30,7 +30,7 @@
 if TYPE_CHECKING:
     ServedType = DefaultDict[
         acme_standalone.BaseDualNetworkedServers,
-        Set[achallenges.KeyAuthorizationAnnotatedChallenge]
+        Set[achallenges.AnnotatedChallenge]
     ]
 
 
diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py
index bb61d8220b9..4a84197b085 100644
--- a/certbot/certbot/_internal/plugins/webroot.py
+++ b/certbot/certbot/_internal/plugins/webroot.py
@@ -20,7 +20,7 @@
 from certbot import errors
 from certbot import interfaces
 from certbot._internal import cli
-from certbot.achallenges import KeyAuthorizationAnnotatedChallenge as AnnotatedChallenge
+from certbot.achallenges import AnnotatedChallenge
 from certbot.compat import filesystem
 from certbot.compat import os
 from certbot.display import ops
@@ -85,7 +85,7 @@ def add_parser_arguments(cls, add: Callable[..., None]) -> None:
                  "file, it needs to be on a single line, like: webroot-map = "
                  '{"example.com":"/var/www"}.')
 
-    def auth_hint(self, failed_achalls: Iterable[AnnotatedChallenge]) -> str:  # pragma: no cover
+    def auth_hint(self, failed_achalls: List[AnnotatedChallenge]) -> str:  # pragma: no cover
         return ("The Certificate Authority failed to download the temporary challenge files "
                 "created by Certbot. Ensure that the listed domains serve their content from "
                 "the provided --webroot-path/-w and that files created there can be downloaded "
@@ -105,7 +105,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
     def prepare(self) -> None:  # pylint: disable=missing-function-docstring
         pass
 
-    def perform(self, achalls: Iterable[AnnotatedChallenge]) -> List[challenges.ChallengeResponse]:  # pylint: disable=missing-function-docstring
+    def perform(self, achalls: List[AnnotatedChallenge]) -> List[challenges.ChallengeResponse]:  # pylint: disable=missing-function-docstring
         self._set_webroots(achalls)
 
         self._create_challenge_dirs()
@@ -257,7 +257,7 @@ def _perform_single(self, achall: AnnotatedChallenge) -> challenges.ChallengeRes
         self.performed[root_path].add(achall)
         return response
 
-    def cleanup(self, achalls: Iterable[AnnotatedChallenge]) -> None:  # pylint: disable=missing-function-docstring
+    def cleanup(self, achalls: List[AnnotatedChallenge]) -> None:  # pylint: disable=missing-function-docstring
         for achall in achalls:
             root_path = self.full_roots.get(achall.domain, None)
             if root_path is not None:
diff --git a/certbot/certbot/_internal/reporter.py b/certbot/certbot/_internal/reporter.py
index 333fcca483a..fb09b1f2767 100644
--- a/certbot/certbot/_internal/reporter.py
+++ b/certbot/certbot/_internal/reporter.py
@@ -29,7 +29,7 @@ class Reporter:
     _msg_type = collections.namedtuple('_msg_type', 'priority text on_crash')
 
     def __init__(self, config: configuration.NamespaceConfig) -> None:
-        self.messages: queue.PriorityQueue[Reporter._msg_type] = queue.PriorityQueue()
+        self.messages: "queue.PriorityQueue[Reporter._msg_type]" = queue.PriorityQueue()
         self.config = config
 
     def add_message(self, msg: str, priority: int, on_crash: bool = True) -> None:
diff --git a/certbot/certbot/_internal/snap_config.py b/certbot/certbot/_internal/snap_config.py
index 3aad7991291..f006c8be1f2 100644
--- a/certbot/certbot/_internal/snap_config.py
+++ b/certbot/certbot/_internal/snap_config.py
@@ -18,8 +18,8 @@
     from urllib3.connectionpool import HTTPConnectionPool
 except ImportError:
     # Stub imports for oldest requirements, that will never be used in snaps.
-    HTTPConnection = object
-    HTTPConnectionPool = object
+    HTTPConnection = object  # type: ignore[misc,assignment]
+    HTTPConnectionPool = object  # type: ignore[misc,assignment]
 
 
 _ARCH_TRIPLET_MAP = {
diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py
index e3a62cc9ebe..ef86c5e4667 100644
--- a/certbot/certbot/crypto_util.py
+++ b/certbot/certbot/crypto_util.py
@@ -290,8 +290,11 @@ def make_key(bits: int = 1024, key_type: str = "rsa",
         try:
             name = elliptic_curve.upper()
             if name in ('SECP256R1', 'SECP384R1', 'SECP521R1'):
+                curve = getattr(ec, elliptic_curve.upper())
+                if not curve:
+                    raise errors.Error(f"Invalid curve type: {elliptic_curve}")
                 _key = ec.generate_private_key(
-                    curve=getattr(ec, elliptic_curve.upper(), None)(),
+                    curve=curve(),
                     backend=default_backend()
                 )
             else:
diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py
index 3af0ee6ece8..fbecc7372b3 100644
--- a/certbot/certbot/plugins/common.py
+++ b/certbot/certbot/plugins/common.py
@@ -18,6 +18,8 @@
 
 import pkg_resources
 
+from acme import challenges
+
 from certbot import achallenges
 from certbot import configuration
 from certbot import crypto_util
@@ -377,7 +379,7 @@ def add_chall(self, achall: achallenges.KeyAuthorizationAnnotatedChallenge,
         if idx is not None:
             self.indices.append(idx)
 
-    def perform(self) -> List[achallenges.KeyAuthorizationAnnotatedChallenge]:
+    def perform(self) -> List[challenges.KeyAuthorizationChallengeResponse]:
         """Perform all added challenges.
 
         :returns: challenge responses
diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt
index 819cbfda655..a3ffc16aba2 100644
--- a/tools/oldest_constraints.txt
+++ b/tools/oldest_constraints.txt
@@ -2,10 +2,9 @@
 # that script.
 apacheconfig==0.3.2
 asn1crypto==0.24.0
-astroid==2.8.6; python_version >= "3.6" and python_version < "4.0"
+astroid==2.9.0; python_version >= "3.6" and python_version < "4.0"
 atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0"
-attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
-backports.entry-points-selectable==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
+attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 bcrypt==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 boto3==1.4.7
 botocore==1.7.41
@@ -16,10 +15,10 @@ cloudflare==1.5.1
 colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or python_full_version >= "3.5.0" and python_version >= "3.6" and sys_platform == "win32" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32" and python_full_version >= "3.5.0"
 configargparse==0.10.0
 configobj==5.0.6
-coverage==6.1.2; python_version >= "3.6" or python_version >= "3.6"
+coverage==6.2; python_version >= "3.6" or python_version >= "3.6"
 cryptography==3.2.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
-cython==0.29.24; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
-distlib==0.3.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
+cython==0.29.26; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
+distlib==0.3.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 distro==1.0.1
 dns-lexicon==3.2.1
 dnspython==1.15.0
@@ -30,30 +29,30 @@ dockerpty==0.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or p
 docopt==0.6.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 docutils==0.18.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 execnet==1.9.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
-filelock==3.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6"
+filelock==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6"
 funcsigs==0.4
 future==0.18.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6"
 google-api-python-client==1.5.5
 httplib2==0.9.2
 idna==2.6
-importlib-metadata==4.8.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.8" or python_version < "3.8" and python_version >= "3.6"
+importlib-metadata==4.8.3; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.8" or python_version < "3.8" and python_version >= "3.6"
 importlib-resources==5.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.7"
 iniconfig==1.1.1; python_version >= "3.6"
 ipaddress==1.0.16
 isort==5.8.0; python_version >= "3.6" and python_version < "4.0"
 jmespath==0.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6"
-josepy==1.11.0; python_version >= "3.6"
+josepy==1.12.0; python_version >= "3.6"
 jsonschema==2.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
-lazy-object-proxy==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0"
+lazy-object-proxy==1.7.1; python_version >= "3.6" and python_version < "4.0"
 logger==1.4; python_version >= "3.6"
 mccabe==0.6.1; python_version >= "3.6" and python_version < "4.0"
 mock==1.0.1
 mypy-extensions==0.4.3; python_version >= "3.6"
-mypy==0.910; python_version >= "3.6"
+mypy==0.931; python_version >= "3.6"
 ndg-httpsclient==0.3.2
 oauth2client==4.0.0
 packaging==21.3; python_version >= "3.6"
-paramiko==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
+paramiko==2.9.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 parsedatetime==2.4
 pbr==1.8.0
 pip==21.3.1; python_version >= "3.6"
@@ -64,21 +63,21 @@ py==1.11.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_
 pyasn1-modules==0.0.10; python_version >= "3.6"
 pyasn1==0.1.9
 pycparser==2.14
-pylint==2.11.1; python_version >= "3.6" and python_version < "4.0"
-pynacl==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
+pylint==2.12.0; python_version >= "3.6" and python_version < "4.0"
+pynacl==1.5.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 pyopenssl==17.3.0
 pyparsing==2.2.0
 pypiwin32==223; sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6")
 pyrfc3339==1.0
 pytest-cov==3.0.0; python_version >= "3.6" or python_version >= "3.6"
-pytest-forked==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
-pytest-xdist==2.4.0; python_version >= "3.6" or python_version >= "3.6"
-pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
+pytest-forked==1.4.0; python_version >= "3.6"
+pytest-xdist==2.5.0; python_version >= "3.6" or python_version >= "3.6"
+pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6"
 python-augeas==0.5.0
 python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6"
 python-digitalocean==1.11
 pytz==2012c
-pywin32==302; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6")
+pywin32==303; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6")
 pyyaml==3.13; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6"
 requests-file==1.5.1; python_version >= "3.6"
 requests-toolbelt==0.9.1; python_version >= "3.6"
@@ -90,24 +89,25 @@ six==1.11.0
 texttable==0.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 tldextract==3.1.2; python_version >= "3.6"
 toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0"
-tomli==1.2.2; python_version >= "3.6"
+tomli==1.2.3; python_version >= "3.6"
 tox==1.9.2; python_version >= "3.6"
-typed-ast==1.4.3; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6"
-types-cryptography==3.3.9; python_version >= "3.6"
-types-enum34==1.1.1; python_version >= "3.6"
-types-ipaddress==1.0.1; python_version >= "3.6"
-types-mock==4.0.3; python_version >= "3.6"
-types-pyopenssl==21.0.1; python_version >= "3.6"
-types-pyrfc3339==1.1.0; python_version >= "3.6"
-types-python-dateutil==2.8.3; python_version >= "3.6"
-types-pytz==2021.3.1; python_version >= "3.6"
-types-requests==2.26.0; python_version >= "3.6"
-types-setuptools==57.4.3; python_version >= "3.6"
-types-six==1.16.2; python_version >= "3.6"
-typing-extensions==4.0.0; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10" or python_version < "3.8" and python_version >= "3.6"
+typed-ast==1.5.1; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6"
+types-cryptography==3.3.14; python_version >= "3.6"
+types-enum34==1.1.7; python_version >= "3.6"
+types-ipaddress==1.0.7; python_version >= "3.6"
+types-mock==4.0.8; python_version >= "3.6"
+types-pyopenssl==21.0.3; python_version >= "3.6"
+types-pyrfc3339==1.1.1; python_version >= "3.6"
+types-python-dateutil==2.8.7; python_version >= "3.6"
+types-pytz==2021.3.4; python_version >= "3.6"
+types-requests==2.27.7; python_version >= "3.6"
+types-setuptools==57.4.7; python_version >= "3.6"
+types-six==1.16.10; python_version >= "3.6"
+types-urllib3==1.26.7; python_version >= "3.6"
+typing-extensions==4.0.1; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10" or python_version < "3.8" and python_version >= "3.6"
 uritemplate==3.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 urllib3==1.10.2
-virtualenv==20.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
+virtualenv==20.13.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 websocket-client==0.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 wheel==0.33.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
 wrapt==1.13.3; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0"
diff --git a/tools/pinning/current/pyproject.toml b/tools/pinning/current/pyproject.toml
index c04180b06b9..ca04a0ada02 100644
--- a/tools/pinning/current/pyproject.toml
+++ b/tools/pinning/current/pyproject.toml
@@ -51,10 +51,6 @@ awscli = ">=1.19.62"
 # as a dependency here to ensure a version of cython is pinned for extra
 # stability.
 cython = "*"
-# Due to better type annotations in new versions, mypy tests currently fail
-# with new versions of josepy so we pin it back for now. Fixing this is being
-# tracked by https://github.com/certbot/certbot/issues/9113.
-josepy = "1.9.0"
 # We install mock in our "external-mock" tox environment to test that we didn't
 # break Certbot's test API which used to always use mock objects from the 3rd
 # party mock library. We list the mock dependency here so that is pinned, but
diff --git a/tools/requirements.txt b/tools/requirements.txt
index 38bf7c0cc9e..c91bf21174c 100644
--- a/tools/requirements.txt
+++ b/tools/requirements.txt
@@ -9,41 +9,41 @@ alabaster==0.7.12; python_version >= "3.6"
 apacheconfig==0.3.2; python_version >= "3.6"
 appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0"
 appnope==0.1.2; python_version == "3.6" and sys_platform == "darwin" or python_version >= "3.7" and sys_platform == "darwin"
-astroid==2.8.6; python_version >= "3.6" and python_version < "4.0"
+astroid==2.9.0; python_version >= "3.6" and python_version < "4.0"
 atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0"
-attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
-awscli==1.22.11; python_version >= "3.6"
+attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
+awscli==1.22.39; python_version >= "3.6"
 azure-devops==6.0.0b4; python_version >= "3.6"
 babel==2.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
 backcall==0.2.0; python_version == "3.6" or python_version >= "3.7"
 bcrypt==3.2.0; python_version >= "3.6"
 beautifulsoup4==4.10.0; python_full_version > "3.0.0" and python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0" and python_full_version > "3.0.0"
 bleach==4.1.0; python_version >= "3.6"
-boto3==1.20.11; python_version >= "3.6"
-botocore==1.23.11; python_version >= "3.6"
+boto3==1.20.39; python_version >= "3.6"
+botocore==1.23.39; python_version >= "3.6"
 cachecontrol==0.12.10; python_version >= "3.6" and python_version < "4.0"
 cached-property==1.5.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 cachetools==4.2.4; python_version >= "3.5" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6")
 cachy==0.3.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0"
 certifi==2021.10.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6"
-cffi==1.15.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" or python_version >= "3.6"
-charset-normalizer==2.0.7; python_full_version >= "3.6.0" and python_version >= "3.6"
+cffi==1.15.0; python_version >= "3.6" or python_version >= "3.6"
+charset-normalizer==2.0.10; python_full_version >= "3.6.0" and python_version >= "3.6"
 cleo==1.0.0a4; python_version >= "3.6" and python_version < "4.0"
 cloudflare==2.8.15; python_version >= "3.6"
 colorama==0.4.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or python_full_version >= "3.5.0" and python_version >= "3.6" and sys_platform == "win32" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and platform_system == "Windows" or python_version >= "3.6" and python_full_version >= "3.5.0" and platform_system == "Windows" or python_version == "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or python_version == "3.6" and sys_platform == "win32" and python_full_version >= "3.5.0" or python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or python_version >= "3.7" and sys_platform == "win32" and python_full_version >= "3.5.0"
 configargparse==1.5.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 configobj==5.0.6; python_version >= "3.6"
-coverage==6.1.2; python_version >= "3.6" or python_version >= "3.6"
+coverage==6.2; python_version >= "3.6" or python_version >= "3.6"
 crashtest==0.3.1; python_version >= "3.6" and python_version < "4.0"
-cryptography==36.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux"
-cython==0.29.24; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
+cryptography==36.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux"
+cython==0.29.26; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
 dataclasses==0.8; python_version >= "3.6" and python_version < "3.7"
-decorator==5.1.0; python_version == "3.6" or python_version > "3.6" or python_version >= "3.5" or python_version >= "3.7"
+decorator==5.1.1; python_version == "3.6" or python_version > "3.6" or python_version >= "3.5" or python_version >= "3.7"
 deprecated==1.2.13; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
-distlib==0.3.3; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6"
+distlib==0.3.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6"
 distro==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6"
-dns-lexicon==3.8.3; python_version >= "3.6" and python_version < "4.0"
-dnspython==2.1.0; python_version >= "3.6"
+dns-lexicon==3.8.5; python_version >= "3.6" and python_version < "4.0"
+dnspython==2.2.0; python_version >= "3.6" and python_version < "4.0"
 docker-compose==1.26.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 docker==4.2.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 dockerpty==0.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
@@ -52,12 +52,12 @@ docutils==0.15.2; python_version >= "3.6" and python_full_version < "3.0.0" or p
 entrypoints==0.3; python_version >= "3.6" and python_version < "4.0"
 execnet==1.9.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 fabric==2.6.0; python_version >= "3.6"
-filelock==3.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_version < "4.0"
-google-api-core==2.2.2; python_version >= "3.6"
-google-api-python-client==2.31.0; python_version >= "3.6"
+filelock==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_version < "4.0"
+google-api-core==2.4.0; python_version >= "3.6"
+google-api-python-client==2.36.0; python_version >= "3.6"
 google-auth-httplib2==0.1.0; python_version >= "3.6"
 google-auth==2.3.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
-googleapis-common-protos==1.53.0; python_version >= "3.6"
+googleapis-common-protos==1.54.0; python_version >= "3.6"
 html5lib==1.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0"
 httplib2==0.20.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 idna==3.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0"
@@ -68,58 +68,60 @@ iniconfig==1.1.1; python_version >= "3.6"
 invoke==1.6.0; python_version >= "3.6"
 ipdb==0.13.9; python_version >= "3.6"
 ipython-genutils==0.2.0
-ipython==7.16.1; python_version == "3.6"
-ipython==7.29.0; python_version >= "3.7"
-isodate==0.6.0; python_version >= "3.6"
+ipython==7.16.3; python_version == "3.6"
+ipython==7.31.1; python_version >= "3.7"
+isodate==0.6.1; python_version >= "3.6"
 isort==5.8.0; python_version >= "3.6" and python_version < "4.0"
-jedi==0.18.1; python_version == "3.6" or python_version >= "3.7"
+jedi==0.17.2; python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.5.0"
+jedi==0.18.1; python_version >= "3.7"
 jeepney==0.7.1; python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux"
 jinja2==3.0.3; python_version >= "3.6" or python_version >= "3.6"
 jmespath==0.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6"
-josepy==1.9.0; python_version >= "3.6"
-jsonlines==2.0.0; python_version >= "3.6"
-jsonpickle==2.0.0; python_version >= "3.6"
+josepy==1.12.0; python_version >= "3.6"
+jsonlines==3.0.0; python_version >= "3.6"
+jsonpickle==2.1.0; python_version >= "3.6"
 jsonschema==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 keyring==22.3.0; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6"
-lazy-object-proxy==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0"
+lazy-object-proxy==1.7.1; python_version >= "3.6" and python_version < "4.0"
 lockfile==0.12.2
 markupsafe==2.0.1; python_version >= "3.6"
 matplotlib-inline==0.1.3; python_version >= "3.7"
 mccabe==0.6.1; python_version >= "3.6" and python_version < "4.0"
 mock==4.0.3; python_version >= "3.6"
-msgpack==1.0.2; python_version >= "3.6" and python_version < "4.0"
+msgpack==1.0.3; python_version >= "3.6" and python_version < "4.0"
 msrest==0.6.21; python_version >= "3.6"
 mypy-extensions==0.4.3; python_version >= "3.6"
-mypy==0.910; python_version >= "3.6"
+mypy==0.931; python_version >= "3.6"
 oauth2client==4.1.3; python_version >= "3.6"
 oauthlib==3.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
 packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version >= "3.5.0"
-paramiko==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6"
+paramiko==2.9.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6"
 parsedatetime==2.6; python_version >= "3.6"
-parso==0.8.2; python_version == "3.6"
+parso==0.7.1; python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.5.0"
+parso==0.8.3; python_version >= "3.7"
 pathlib2==2.3.6; python_version >= "3.6"
 pexpect==4.8.0; python_version >= "3.6" and python_version < "4.0" or python_version == "3.6" and sys_platform != "win32" or python_version >= "3.7" and sys_platform != "win32"
 pickleshare==0.7.5; python_version == "3.6" or python_version >= "3.7"
 pip==21.3.1; python_version >= "3.6"
-pkginfo==1.8.1; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6"
+pkginfo==1.8.2; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6"
 platformdirs==2.4.0; python_version >= "3.6" and python_version < "4.0"
 pluggy==1.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6"
 ply==3.11; python_version >= "3.6"
 poetry-core==1.1.0a6; python_version >= "3.6" and python_version < "4.0"
 poetry==1.2.0a2; python_version >= "3.6" and python_version < "4.0"
-prompt-toolkit==3.0.22; python_version == "3.6" and python_full_version >= "3.6.2" or python_version >= "3.7" and python_full_version >= "3.6.2"
-protobuf==3.19.1; python_version >= "3.6"
+prompt-toolkit==3.0.24; python_version == "3.6" and python_full_version >= "3.6.2" or python_version >= "3.7" and python_full_version >= "3.6.2"
+protobuf==3.19.3; python_version >= "3.6"
 ptyprocess==0.7.0; python_version >= "3.6" and python_version < "4.0"
 py==1.11.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 pyasn1-modules==0.2.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6"
 pyasn1==0.4.8; python_version >= "3.6" and python_version < "4" or python_version >= "3.6"
 pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 pygithub==1.55; python_version >= "3.6"
-pygments==2.10.0; python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7"
+pygments==2.11.2; python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7"
 pyjwt==2.3.0; python_version >= "3.6"
 pylev==1.4.0; python_version >= "3.6" and python_version < "4.0"
-pylint==2.11.1; python_version >= "3.6" and python_version < "4.0"
-pynacl==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
+pylint==2.12.0; python_version >= "3.6" and python_version < "4.0"
+pynacl==1.5.0; python_version >= "3.6" or python_version >= "3.6"
 pynsist==2.7; python_version >= "3.6"
 pyopenssl==21.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
 pyparsing==3.0.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0"
@@ -127,36 +129,36 @@ pypiwin32==223; sys_platform == "win32" and python_version >= "3.6" and (python_
 pyrfc3339==1.1; python_version >= "3.6"
 pyrsistent==0.18.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 pytest-cov==3.0.0; python_version >= "3.6" or python_version >= "3.6"
-pytest-forked==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
-pytest-xdist==2.4.0; python_version >= "3.6" or python_version >= "3.6"
-pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
+pytest-forked==1.4.0; python_version >= "3.6"
+pytest-xdist==2.5.0; python_version >= "3.6" or python_version >= "3.6"
+pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6"
 python-augeas==1.1.0; python_version >= "3.6"
 python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6"
 python-digitalocean==1.17.0; python_version >= "3.6"
 python-dotenv==0.19.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 pytz==2021.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" or python_version >= "3.6"
 pywin32-ctypes==0.2.0; python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32"
-pywin32==302; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6")
+pywin32==303; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6")
 pyyaml==5.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0"
-readme-renderer==30.0; python_version >= "3.6"
+readme-renderer==32.0; python_version >= "3.6"
 requests-download==0.1.2; python_version >= "3.6"
 requests-file==1.5.1; python_version >= "3.6" and python_version < "4.0"
 requests-oauthlib==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
 requests-toolbelt==0.9.1; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" or python_version >= "3.6"
-requests==2.26.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0"
+requests==2.27.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0"
 rfc3986==1.5.0; python_version >= "3.6"
 rsa==4.7.2; python_version >= "3.6" and python_version < "4" or python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6")
 s3transfer==0.5.0; python_version >= "3.6"
 secretstorage==3.3.1; python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux"
 semantic-version==2.8.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 setuptools-rust==0.12.1; python_version >= "3.6"
-setuptools==59.2.0; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7" or python_version >= "3.6" and python_version < "4.0"
+setuptools==59.6.0; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7" or python_version >= "3.6" and python_version < "4.0"
 shellingham==1.4.0; python_version >= "3.6" and python_version < "4.0"
 six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" or python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.3.0"
 snowballstemmer==2.2.0; python_version >= "3.6"
 soupsieve==2.3.1; python_full_version > "3.0.0" and python_version >= "3.6"
 sphinx-rtd-theme==1.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
-sphinx==4.3.0; python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
+sphinx==4.3.2; python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
 sphinxcontrib-applehelp==1.0.2; python_version >= "3.6"
 sphinxcontrib-devhelp==1.0.2; python_version >= "3.6"
 sphinxcontrib-htmlhelp==2.0.0; python_version >= "3.6"
@@ -166,32 +168,33 @@ sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.6"
 texttable==1.6.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 tldextract==3.1.2; python_version >= "3.6" and python_version < "4.0"
 toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_version == "3.6" and python_full_version < "3.0.0" or python_version > "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.3.0" or python_version > "3.6" and python_full_version >= "3.3.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
-tomli==1.2.2; python_version >= "3.6"
-tomlkit==0.7.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0"
-tox==3.24.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
+tomli==1.2.3; python_version >= "3.6"
+tomlkit==0.8.0; python_version >= "3.6" and python_version < "4.0"
+tox==3.24.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 tqdm==4.62.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
 traitlets==4.3.3
 twine==3.3.0; python_version >= "3.6"
-typed-ast==1.4.3; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6"
-types-cryptography==3.3.8; python_version >= "3.6"
-types-enum34==1.1.1; python_version >= "3.6"
-types-ipaddress==1.0.1; python_version >= "3.6"
-types-mock==4.0.3; python_version >= "3.6"
-types-pyopenssl==21.0.0; python_version >= "3.6"
-types-pyrfc3339==1.1.0; python_version >= "3.6"
-types-python-dateutil==2.8.2; python_version >= "3.6"
-types-pytz==2021.3.0; python_version >= "3.6"
-types-requests==2.26.0; python_version >= "3.6"
-types-setuptools==57.4.2; python_version >= "3.6"
-types-six==1.16.2; python_version >= "3.6"
-typing-extensions==4.0.0; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10"
+typed-ast==1.5.1; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6"
+types-cryptography==3.3.14; python_version >= "3.6"
+types-enum34==1.1.7; python_version >= "3.6"
+types-ipaddress==1.0.7; python_version >= "3.6"
+types-mock==4.0.8; python_version >= "3.6"
+types-pyopenssl==21.0.3; python_version >= "3.6"
+types-pyrfc3339==1.1.1; python_version >= "3.6"
+types-python-dateutil==2.8.7; python_version >= "3.6"
+types-pytz==2021.3.4; python_version >= "3.6"
+types-requests==2.27.7; python_version >= "3.6"
+types-setuptools==57.4.7; python_version >= "3.6"
+types-six==1.16.10; python_version >= "3.6"
+types-urllib3==1.26.7; python_version >= "3.6"
+typing-extensions==4.0.1; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10" or python_version < "3.8" and python_version >= "3.6"
 uritemplate==4.1.1; python_version >= "3.6"
-urllib3==1.26.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6"
+urllib3==1.26.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6"
 virtualenv==20.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 wcwidth==0.2.5; python_version == "3.6" and python_full_version >= "3.6.2"
 webencodings==0.5.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" or python_version >= "3.6"
 websocket-client==0.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version >= "3.6"
-wheel==0.37.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
+wheel==0.37.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 wrapt==1.13.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0"
 yarg==0.1.9; python_version >= "3.6"
 zipp==3.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_version >= "3.6" and python_version < "3.8" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version >= "3.6" and python_version < "3.7" and python_full_version >= "3.4.0"


Test patch:
diff --git a/acme/tests/fields_test.py b/acme/tests/fields_test.py
index 69dde8b89bc..4cc167f9c19 100644
--- a/acme/tests/fields_test.py
+++ b/acme/tests/fields_test.py
@@ -10,8 +10,8 @@ class FixedTest(unittest.TestCase):
     """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 --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py
index 9143804f92f..a36b348e009 100644
--- a/certbot-ci/certbot_integration_tests/utils/misc.py
+++ b/certbot-ci/certbot_integration_tests/utils/misc.py
@@ -48,7 +48,8 @@ def _suppress_x509_verification_warnings() -> None:
         # Handle old versions of request with vendorized urllib3
         # pylint: disable=no-member
         from requests.packages.urllib3.exceptions import InsecureRequestWarning
-        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+        requests.packages.urllib3.disable_warnings(  # type: ignore[attr-defined]
+            InsecureRequestWarning)
 
 
 def check_until_timeout(url: str, attempts: int = 30) -> None:
diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py
index 1b27a219548..26b7660abeb 100644
--- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py
+++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py
@@ -106,7 +106,7 @@ def test_authenticator(plugin: common.Proxy, config: str, temp_dir: str) -> bool
 
 def _create_achalls(plugin: common.Proxy) -> List[achallenges.AnnotatedChallenge]:
     """Returns a list of annotated challenges to test on plugin"""
-    achalls = []
+    achalls: List[achallenges.AnnotatedChallenge] = []
     names = plugin.get_testable_domain_names()
     for domain in names:
         prefs = plugin.get_chall_pref(domain)
diff --git a/certbot/certbot/plugins/dns_test_common_lexicon.py b/certbot/certbot/plugins/dns_test_common_lexicon.py
index df91dac1f7d..7d844d13338 100644
--- a/certbot/certbot/plugins/dns_test_common_lexicon.py
+++ b/certbot/certbot/plugins/dns_test_common_lexicon.py
@@ -7,8 +7,8 @@
 from requests.exceptions import HTTPError
 from requests.exceptions import RequestException
 
-from acme.challenges import Challenge
 from certbot import errors
+from certbot.achallenges import AnnotatedChallenge
 from certbot.plugins import dns_test_common
 from certbot.plugins.dns_common_lexicon import LexiconClient
 from certbot.plugins.dns_test_common import _AuthenticatorCallableTestCase
@@ -33,7 +33,7 @@ class _AuthenticatorCallableLexiconTestCase(_AuthenticatorCallableTestCase, Prot
     a mocked LexiconClient instance.
     """
     mock_client: MagicMock
-    achall: Challenge
+    achall: AnnotatedChallenge
 
 
 class _LexiconAwareTestCase(Protocol):
diff --git a/certbot/certbot/tests/acme_util.py b/certbot/certbot/tests/acme_util.py
index 0c164ecb427..d8ee7f9a883 100644
--- a/certbot/certbot/tests/acme_util.py
+++ b/certbot/certbot/tests/acme_util.py
@@ -1,5 +1,7 @@
 """ACME utilities for testing."""
 import datetime
+from typing import Any
+from typing import Dict
 from typing import Iterable
 from typing import Tuple
 
@@ -74,7 +76,7 @@ def gen_authzr(authz_status: messages.Status, domain: str, challs: Iterable[chal
         chall_to_challb(chall, status)
         for chall, status in zip(challs, statuses)
     )
-    authz_kwargs = {
+    authz_kwargs: Dict[str, Any] = {
         "identifier": messages.Identifier(
             typ=messages.IDENTIFIER_FQDN, value=domain),
         "challenges": challbs,