Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/certbot/certbot.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/acme
diff options
context:
space:
mode:
Diffstat (limited to 'acme')
-rw-r--r--acme/MANIFEST.in4
-rw-r--r--acme/acme/__init__.py2
-rw-r--r--acme/acme/challenges.py185
-rw-r--r--acme/acme/client.py132
-rw-r--r--acme/acme/crypto_util.py33
-rw-r--r--acme/acme/errors.py16
-rw-r--r--acme/acme/fields.py1
-rw-r--r--acme/acme/jws.py4
-rw-r--r--acme/acme/magic_typing.py1
-rw-r--r--acme/acme/messages.py62
-rw-r--r--acme/acme/standalone.py88
-rw-r--r--acme/docs/conf.py5
-rw-r--r--acme/examples/http01_example.py241
-rw-r--r--acme/readthedocs.org.requirements.txt6
-rw-r--r--acme/setup.py21
-rw-r--r--acme/tests/challenges_test.py (renamed from acme/acme/challenges_test.py)165
-rw-r--r--acme/tests/client_test.py (renamed from acme/acme/client_test.py)55
-rw-r--r--acme/tests/crypto_util_test.py (renamed from acme/acme/crypto_util_test.py)13
-rw-r--r--acme/tests/errors_test.py (renamed from acme/acme/errors_test.py)0
-rw-r--r--acme/tests/fields_test.py (renamed from acme/acme/fields_test.py)0
-rw-r--r--acme/tests/jose_test.py (renamed from acme/acme/jose_test.py)10
-rw-r--r--acme/tests/jws_test.py (renamed from acme/acme/jws_test.py)3
-rw-r--r--acme/tests/magic_typing_test.py (renamed from acme/acme/magic_typing_test.py)0
-rw-r--r--acme/tests/messages_test.py (renamed from acme/acme/messages_test.py)25
-rw-r--r--acme/tests/standalone_test.py (renamed from acme/acme/standalone_test.py)123
-rw-r--r--acme/tests/test_util.py (renamed from acme/acme/test_util.py)42
-rw-r--r--acme/tests/testdata/README (renamed from acme/acme/testdata/README)0
-rw-r--r--acme/tests/testdata/cert-100sans.pem (renamed from acme/acme/testdata/cert-100sans.pem)0
-rw-r--r--acme/tests/testdata/cert-idnsans.pem (renamed from acme/acme/testdata/cert-idnsans.pem)0
-rw-r--r--acme/tests/testdata/cert-nocn.der (renamed from acme/acme/testdata/cert-nocn.der)bin1397 -> 1397 bytes
-rw-r--r--acme/tests/testdata/cert-san.pem (renamed from acme/acme/testdata/cert-san.pem)0
-rw-r--r--acme/tests/testdata/cert.der (renamed from acme/acme/testdata/cert.der)bin771 -> 771 bytes
-rw-r--r--acme/tests/testdata/cert.pem (renamed from acme/acme/testdata/cert.pem)0
-rw-r--r--acme/tests/testdata/critical-san.pem (renamed from acme/acme/testdata/critical-san.pem)0
-rw-r--r--acme/tests/testdata/csr-100sans.pem (renamed from acme/acme/testdata/csr-100sans.pem)0
-rw-r--r--acme/tests/testdata/csr-6sans.pem (renamed from acme/acme/testdata/csr-6sans.pem)0
-rw-r--r--acme/tests/testdata/csr-idnsans.pem (renamed from acme/acme/testdata/csr-idnsans.pem)0
-rw-r--r--acme/tests/testdata/csr-nosans.pem (renamed from acme/acme/testdata/csr-nosans.pem)0
-rw-r--r--acme/tests/testdata/csr-san.pem (renamed from acme/acme/testdata/csr-san.pem)0
-rw-r--r--acme/tests/testdata/csr.der (renamed from acme/acme/testdata/csr.der)bin607 -> 607 bytes
-rw-r--r--acme/tests/testdata/csr.pem (renamed from acme/acme/testdata/csr.pem)0
-rw-r--r--acme/tests/testdata/dsa512_key.pem (renamed from acme/acme/testdata/dsa512_key.pem)0
-rw-r--r--acme/tests/testdata/rsa1024_key.pem (renamed from acme/acme/testdata/rsa1024_key.pem)0
-rw-r--r--acme/tests/testdata/rsa2048_cert.pem (renamed from acme/acme/testdata/rsa2048_cert.pem)0
-rw-r--r--acme/tests/testdata/rsa2048_key.pem (renamed from acme/acme/testdata/rsa2048_key.pem)0
-rw-r--r--acme/tests/testdata/rsa256_key.pem (renamed from acme/acme/testdata/rsa256_key.pem)0
-rw-r--r--acme/tests/testdata/rsa512_key.pem (renamed from acme/acme/testdata/rsa512_key.pem)0
-rw-r--r--acme/tests/util_test.py (renamed from acme/acme/util_test.py)0
48 files changed, 476 insertions, 761 deletions
diff --git a/acme/MANIFEST.in b/acme/MANIFEST.in
index 1619bef69..de254250e 100644
--- a/acme/MANIFEST.in
+++ b/acme/MANIFEST.in
@@ -3,4 +3,6 @@ include README.rst
include pytest.ini
recursive-include docs *
recursive-include examples *
-recursive-include acme/testdata *
+recursive-include tests *
+global-exclude __pycache__
+global-exclude *.py[cod]
diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py
index d91072a3b..d1679fcad 100644
--- a/acme/acme/__init__.py
+++ b/acme/acme/__init__.py
@@ -6,13 +6,13 @@ This module is an implementation of the `ACME protocol`_.
"""
import sys
+import warnings
# This code exists to keep backwards compatibility with people using acme.jose
# before it became the standalone josepy package.
#
# It is based on
# https://github.com/requests/requests/blob/1278ecdf71a312dc2268f3bfc0aabfab3c006dcf/requests/packages.py
-
import josepy as jose
for mod in list(sys.modules):
diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py
index 29b9bbb50..39c8d6269 100644
--- a/acme/acme/challenges.py
+++ b/acme/acme/challenges.py
@@ -3,27 +3,19 @@ import abc
import functools
import hashlib
import logging
-import socket
-import warnings
from cryptography.hazmat.primitives import hashes # type: ignore
import josepy as jose
-import OpenSSL
import requests
import six
-from acme import errors
-from acme import crypto_util
from acme import fields
logger = logging.getLogger(__name__)
-# pylint: disable=too-few-public-methods
-
-
class Challenge(jose.TypedJSONObjectWithFields):
- # _fields_to_partial_json | pylint: disable=abstract-method
+ # _fields_to_partial_json
"""ACME challenge."""
TYPES = {} # type: dict
@@ -37,7 +29,7 @@ class Challenge(jose.TypedJSONObjectWithFields):
class ChallengeResponse(jose.TypedJSONObjectWithFields):
- # _fields_to_partial_json | pylint: disable=abstract-method
+ # _fields_to_partial_json
"""ACME challenge response."""
TYPES = {} # type: dict
resource_type = 'challenge'
@@ -62,8 +54,7 @@ class UnrecognizedChallenge(Challenge):
object.__setattr__(self, "jobj", jobj)
def to_partial_json(self):
- # pylint: disable=no-member
- return self.jobj
+ return self.jobj # pylint: disable=no-member
@classmethod
def from_json(cls, jobj):
@@ -96,6 +87,7 @@ class _TokenChallenge(Challenge):
"""
# TODO: check that path combined with uri does not go above
# URI_ROOT_PATH!
+ # pylint: disable=unsupported-membership-test
return b'..' not in self.token and b'/' not in self.token
@@ -120,7 +112,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
:rtype: bool
"""
- parts = self.key_authorization.split('.') # pylint: disable=no-member
+ parts = self.key_authorization.split('.')
if len(parts) != 2:
logger.debug("Key authorization (%r) is not well formed",
self.key_authorization)
@@ -140,10 +132,14 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
return True
+ def to_partial_json(self):
+ jobj = super(KeyAuthorizationChallengeResponse, self).to_partial_json()
+ jobj.pop('keyAuthorization', None)
+ return jobj
+
@six.add_metaclass(abc.ABCMeta)
class KeyAuthorizationChallenge(_TokenChallenge):
- # pylint: disable=abstract-class-little-used,too-many-ancestors
"""Challenge based on Key Authorization.
:param response_cls: Subclass of `KeyAuthorizationChallengeResponse`
@@ -175,7 +171,7 @@ class KeyAuthorizationChallenge(_TokenChallenge):
:rtype: KeyAuthorizationChallengeResponse
"""
- return self.response_cls(
+ return self.response_cls( # pylint: disable=not-callable
key_authorization=self.key_authorization(account_key))
@abc.abstractmethod
@@ -212,7 +208,7 @@ class DNS01Response(KeyAuthorizationChallengeResponse):
"""ACME dns-01 challenge response."""
typ = "dns-01"
- def simple_verify(self, chall, domain, account_public_key):
+ def simple_verify(self, chall, domain, account_public_key): # pylint: disable=unused-argument
"""Simple verify.
This method no longer checks DNS records and is a simple wrapper
@@ -228,14 +224,13 @@ class DNS01Response(KeyAuthorizationChallengeResponse):
:rtype: bool
"""
- # pylint: disable=unused-argument
verified = self.verify(chall, account_public_key)
if not verified:
logger.debug("Verification of key authorization in response failed")
return verified
-@Challenge.register # pylint: disable=too-many-ancestors
+@Challenge.register
class DNS01(KeyAuthorizationChallenge):
"""ACME dns-01 challenge."""
response_cls = DNS01Response
@@ -325,7 +320,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
return True
-@Challenge.register # pylint: disable=too-many-ancestors
+@Challenge.register
class HTTP01(KeyAuthorizationChallenge):
"""ACME http-01 challenge."""
response_cls = HTTP01Response
@@ -366,154 +361,6 @@ class HTTP01(KeyAuthorizationChallenge):
@ChallengeResponse.register
-class TLSSNI01Response(KeyAuthorizationChallengeResponse):
- """ACME tls-sni-01 challenge response."""
- typ = "tls-sni-01"
-
- DOMAIN_SUFFIX = b".acme.invalid"
- """Domain name suffix."""
-
- PORT = 443
- """Verification port as defined by the protocol.
-
- You can override it (e.g. for testing) by passing ``port`` to
- `simple_verify`.
-
- """
-
- @property
- def z(self): # pylint: disable=invalid-name
- """``z`` value used for verification.
-
- :rtype bytes:
-
- """
- return hashlib.sha256(
- self.key_authorization.encode("utf-8")).hexdigest().lower().encode()
-
- @property
- def z_domain(self):
- """Domain name used for verification, generated from `z`.
-
- :rtype bytes:
-
- """
- return self.z[:32] + b'.' + self.z[32:] + self.DOMAIN_SUFFIX
-
- def gen_cert(self, key=None, bits=2048):
- """Generate tls-sni-01 certificate.
-
- :param OpenSSL.crypto.PKey key: Optional private key used in
- certificate generation. If not provided (``None``), then
- fresh key will be generated.
- :param int bits: Number of bits for newly generated key.
-
- :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
-
- """
- if key is None:
- key = OpenSSL.crypto.PKey()
- key.generate_key(OpenSSL.crypto.TYPE_RSA, bits)
- return crypto_util.gen_ss_cert(key, [
- # z_domain is too big to fit into CN, hence first dummy domain
- 'dummy', self.z_domain.decode()], force_san=True), key
-
- def probe_cert(self, domain, **kwargs):
- """Probe tls-sni-01 challenge certificate.
-
- :param unicode domain:
-
- """
- # TODO: domain is not necessary if host is provided
- if "host" not in kwargs:
- host = socket.gethostbyname(domain)
- logger.debug('%s resolved to %s', domain, host)
- kwargs["host"] = host
-
- kwargs.setdefault("port", self.PORT)
- kwargs["name"] = self.z_domain
- # TODO: try different methods?
- # pylint: disable=protected-access
- return crypto_util.probe_sni(**kwargs)
-
- def verify_cert(self, cert):
- """Verify tls-sni-01 challenge certificate.
-
- :param OpensSSL.crypto.X509 cert: Challenge certificate.
-
- :returns: Whether the certificate was successfully verified.
- :rtype: bool
-
- """
- # pylint: disable=protected-access
- sans = crypto_util._pyopenssl_cert_or_req_san(cert)
- logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), sans)
- return self.z_domain.decode() in sans
-
- def simple_verify(self, chall, domain, account_public_key,
- cert=None, **kwargs):
- """Simple verify.
-
- Verify ``validation`` using ``account_public_key``, optionally
- probe tls-sni-01 certificate and check using `verify_cert`.
-
- :param .challenges.TLSSNI01 chall: Corresponding challenge.
- :param str domain: Domain name being validated.
- :param JWK account_public_key:
- :param OpenSSL.crypto.X509 cert: Optional certificate. If not
- provided (``None``) certificate will be retrieved using
- `probe_cert`.
- :param int port: Port used to probe the certificate.
-
-
- :returns: ``True`` iff client's control of the domain has been
- verified.
- :rtype: bool
-
- """
- if not self.verify(chall, account_public_key):
- logger.debug("Verification of key authorization in response failed")
- return False
-
- if cert is None:
- try:
- cert = self.probe_cert(domain=domain, **kwargs)
- except errors.Error as error:
- logger.debug(str(error), exc_info=True)
- return False
-
- return self.verify_cert(cert)
-
-
-@Challenge.register # pylint: disable=too-many-ancestors
-class TLSSNI01(KeyAuthorizationChallenge):
- """ACME tls-sni-01 challenge."""
- response_cls = TLSSNI01Response
- typ = response_cls.typ
-
- # boulder#962, ietf-wg-acme#22
- #n = jose.Field("n", encoder=int, decoder=int)
-
- def __init__(self, *args, **kwargs):
- warnings.warn("TLS-SNI-01 is deprecated, and will stop working soon.",
- DeprecationWarning, stacklevel=2)
- super(TLSSNI01, self).__init__(*args, **kwargs)
-
- def validation(self, account_key, **kwargs):
- """Generate validation.
-
- :param JWK account_key:
- :param OpenSSL.crypto.PKey cert_key: Optional private key used
- in certificate generation. If not provided (``None``), then
- fresh key will be generated.
-
- :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
-
- """
- return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))
-
-
-@ChallengeResponse.register
class TLSALPN01Response(KeyAuthorizationChallengeResponse):
"""ACME TLS-ALPN-01 challenge response.
@@ -524,7 +371,7 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse):
typ = "tls-alpn-01"
-@Challenge.register # pylint: disable=too-many-ancestors
+@Challenge.register
class TLSALPN01(KeyAuthorizationChallenge):
"""ACME tls-alpn-01 challenge.
@@ -540,7 +387,7 @@ class TLSALPN01(KeyAuthorizationChallenge):
raise NotImplementedError()
-@Challenge.register # pylint: disable=too-many-ancestors
+@Challenge.register
class DNS(_TokenChallenge):
"""ACME "dns" challenge."""
typ = "dns"
diff --git a/acme/acme/client.py b/acme/acme/client.py
index 41338e17e..f48ff40b2 100644
--- a/acme/acme/client.py
+++ b/acme/acme/client.py
@@ -5,25 +5,26 @@ import datetime
from email.utils import parsedate_tz
import heapq
import logging
+import re
+import sys
import time
-import six
-from six.moves import http_client # pylint: disable=import-error
import josepy as jose
import OpenSSL
-import re
-from requests_toolbelt.adapters.source import SourceAddressAdapter
import requests
from requests.adapters import HTTPAdapter
-import sys
+from requests_toolbelt.adapters.source import SourceAddressAdapter
+import six
+from six.moves import http_client # pylint: disable=import-error
from acme import crypto_util
from acme import errors
from acme import jws
from acme import messages
-# pylint: disable=unused-import, no-name-in-module
-from acme.magic_typing import Dict, List, Set, Text
-
+from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import Text # pylint: disable=unused-import, no-name-in-module
logger = logging.getLogger(__name__)
@@ -33,7 +34,6 @@ logger = logging.getLogger(__name__)
# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
if sys.version_info < (2, 7, 9): # pragma: no cover
try:
- # pylint: disable=no-member
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore
except AttributeError:
import urllib3.contrib.pyopenssl # pylint: disable=import-error
@@ -44,7 +44,7 @@ DEFAULT_NETWORK_TIMEOUT = 45
DER_CONTENT_TYPE = 'application/pkix-cert'
-class ClientBase(object): # pylint: disable=too-many-instance-attributes
+class ClientBase(object):
"""ACME client base object.
:ivar messages.Directory directory:
@@ -123,14 +123,21 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
"""
return self.update_registration(regr, update={'status': 'deactivated'})
- def query_registration(self, regr):
- """Query server about registration.
+ def deactivate_authorization(self, authzr):
+ # type: (messages.AuthorizationResource) -> messages.AuthorizationResource
+ """Deactivate authorization.
- :param messages.RegistrationResource: Existing Registration
- Resource.
+ :param messages.AuthorizationResource authzr: The Authorization resource
+ to be deactivated.
+
+ :returns: The Authorization resource that was deactivated.
+ :rtype: `.AuthorizationResource`
"""
- return self._send_recv_regr(regr, messages.UpdateRegistration())
+ body = messages.UpdateAuthorization(status='deactivated')
+ response = self._post(authzr.uri, body)
+ return self._authzr_from_response(response,
+ authzr.body.identifier, authzr.uri)
def _authzr_from_response(self, response, identifier=None, uri=None):
authzr = messages.AuthorizationResource(
@@ -247,7 +254,6 @@ class Client(ClientBase):
URI from which the resource will be downloaded.
"""
- # pylint: disable=too-many-arguments
self.key = key
if net is None:
net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl)
@@ -273,9 +279,17 @@ class Client(ClientBase):
assert response.status_code == http_client.CREATED
# "Instance of 'Field' has no key/contact member" bug:
- # pylint: disable=no-member
return self._regr_from_response(response)
+ def query_registration(self, regr):
+ """Query server about registration.
+
+ :param messages.RegistrationResource: Existing Registration
+ Resource.
+
+ """
+ return self._send_recv_regr(regr, messages.UpdateRegistration())
+
def agree_to_tos(self, regr):
"""Agree to the terms-of-service.
@@ -419,7 +433,6 @@ class Client(ClientBase):
was marked by the CA as invalid
"""
- # pylint: disable=too-many-locals
assert max_attempts > 0
attempts = collections.defaultdict(int) # type: Dict[messages.AuthorizationResource, int]
exhausted = set()
@@ -450,7 +463,6 @@ class Client(ClientBase):
updated[authzr] = updated_authzr
attempts[authzr] += 1
- # pylint: disable=no-member
if updated_authzr.body.status not in (
messages.STATUS_VALID, messages.STATUS_INVALID):
if attempts[authzr] < max_attempts:
@@ -591,7 +603,6 @@ class ClientV2(ClientBase):
if response.status_code == 200 and 'Location' in response.headers:
raise errors.ConflictError(response.headers.get('Location'))
# "Instance of 'Field' has no key/contact member" bug:
- # pylint: disable=no-member
regr = self._regr_from_response(response)
self.net.account = regr
return regr
@@ -603,10 +614,13 @@ class ClientV2(ClientBase):
Resource.
"""
- self.net.account = regr
- updated_regr = super(ClientV2, self).query_registration(regr)
- self.net.account = updated_regr
- return updated_regr
+ self.net.account = regr # See certbot/certbot#6258
+ # ACME v2 requires to use a POST-as-GET request (POST an empty JWS) here.
+ # This is done by passing None instead of an empty UpdateRegistration to _post().
+ response = self._post(regr.uri, None)
+ self.net.account = self._regr_from_response(response, uri=regr.uri,
+ terms_of_service=regr.terms_of_service)
+ return self.net.account
def update_registration(self, regr, update=None):
"""Update registration.
@@ -652,7 +666,7 @@ class ClientV2(ClientBase):
response = self._post(self.directory['newOrder'], order)
body = messages.Order.from_json(response.json())
authorizations = []
- for url in body.authorizations:
+ for url in body.authorizations: # pylint: disable=not-an-iterable
authorizations.append(self._authzr_from_response(self._post_as_get(url), uri=url))
return messages.OrderResource(
body=body,
@@ -712,9 +726,9 @@ class ClientV2(ClientBase):
for authzr in responses:
if authzr.body.status != messages.STATUS_VALID:
for chall in authzr.body.challenges:
- if chall.error != None:
+ if chall.error is not None:
failed.append(authzr)
- if len(failed) > 0:
+ if failed:
raise errors.ValidationError(failed)
return orderr.update(authorizations=responses)
@@ -739,8 +753,7 @@ class ClientV2(ClientBase):
if body.error is not None:
raise errors.IssuanceError(body.error)
if body.certificate is not None:
- certificate_response = self._post_as_get(body.certificate,
- content_type=DER_CONTENT_TYPE).text
+ certificate_response = self._post_as_get(body.certificate).text
return orderr.update(body=body, fullchain_pem=certificate_response)
raise errors.TimeoutError()
@@ -759,36 +772,17 @@ class ClientV2(ClientBase):
def external_account_required(self):
"""Checks if ACME server requires External Account Binding authentication."""
- if hasattr(self.directory, 'meta') and self.directory.meta.external_account_required:
- return True
- else:
- return False
+ return hasattr(self.directory, 'meta') and self.directory.meta.external_account_required
def _post_as_get(self, *args, **kwargs):
"""
- Send GET request using the POST-as-GET protocol if needed.
- The request will be first issued using POST-as-GET for ACME v2. If the ACME CA servers do
- not support this yet and return an error, request will be retried using GET.
- For ACME v1, only GET request will be tried, as POST-as-GET is not supported.
+ Send GET request using the POST-as-GET protocol.
:param args:
:param kwargs:
:return:
"""
- if self.acme_version >= 2:
- # We add an empty payload for POST-as-GET requests
- new_args = args[:1] + (None,) + args[1:]
- try:
- return self._post(*new_args, **kwargs) # pylint: disable=star-args
- except messages.Error as error:
- if error.code == 'malformed':
- logger.debug('Error during a POST-as-GET request, '
- 'your ACME CA may not support it:\n%s', error)
- logger.debug('Retrying request with GET.')
- else: # pragma: no cover
- raise
-
- # If POST-as-GET is not supported yet, we use a GET instead.
- return self.net.get(*args, **kwargs)
+ new_args = args[:1] + (None,) + args[1:]
+ return self._post(*new_args, **kwargs)
class BackwardsCompatibleClientV2(object):
@@ -866,8 +860,7 @@ class BackwardsCompatibleClientV2(object):
for domain in dnsNames:
authorizations.append(self.client.request_domain_challenges(domain))
return messages.OrderResource(authorizations=authorizations, csr_pem=csr_pem)
- else:
- return self.client.new_order(csr_pem)
+ return self.client.new_order(csr_pem)
def finalize_order(self, orderr, deadline):
"""Finalize an order and obtain a certificate.
@@ -904,8 +897,7 @@ class BackwardsCompatibleClientV2(object):
chain = crypto_util.dump_pyopenssl_chain(chain).decode()
return orderr.update(fullchain_pem=(cert + chain))
- else:
- return self.client.finalize_order(orderr, deadline)
+ return self.client.finalize_order(orderr, deadline)
def revoke(self, cert, rsn):
"""Revoke certificate.
@@ -923,8 +915,7 @@ class BackwardsCompatibleClientV2(object):
def _acme_version_from_directory(self, directory):
if hasattr(directory, 'newNonce'):
return 2
- else:
- return 1
+ return 1
def external_account_required(self):
"""Checks if the server requires an external account for ACMEv2 servers.
@@ -932,11 +923,10 @@ class BackwardsCompatibleClientV2(object):
Always return False for ACMEv1 servers, as it doesn't use External Account Binding."""
if self.acme_version == 1:
return False
- else:
- return self.client.external_account_required()
+ return self.client.external_account_required()
-class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
+class ClientNetwork(object):
"""Wrapper around requests that signs POSTs for authentication.
Also adds user agent, and handles Content-Type.
@@ -952,7 +942,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
:param messages.RegistrationResource account: Account object. Required if you are
planning to use .post() with acme_version=2 for anything other than
creating a new account; may be set later after registering.
- :param josepy.JWASignature alg: Algoritm to use in signing JWS.
+ :param josepy.JWASignature alg: Algorithm to use in signing JWS.
:param bool verify_ssl: Whether to verify certificates on SSL connections.
:param str user_agent: String to send as User-Agent header.
:param float timeout: Timeout for requests.
@@ -962,7 +952,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
def __init__(self, key, account=None, alg=jose.RS256, verify_ssl=True,
user_agent='acme-python', timeout=DEFAULT_NETWORK_TIMEOUT,
source_address=None):
- # pylint: disable=too-many-arguments
self.key = key
self.account = account
self.alg = alg
@@ -1011,7 +1000,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
if self.account is not None:
kwargs["kid"] = self.account["uri"]
kwargs["key"] = self.key
- # pylint: disable=star-args
return jws.JWS.sign(jobj, **kwargs).json_dumps(indent=2)
@classmethod
@@ -1071,7 +1059,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
return response
def _send_request(self, method, url, *args, **kwargs):
- # pylint: disable=too-many-locals
"""Send HTTP request.
Makes sure that `verify_ssl` is respected. Logs request and
@@ -1118,10 +1105,9 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
err_regex = r".*host='(\S*)'.*Max retries exceeded with url\: (\/\w*).*(\[Errno \d+\])([A-Za-z ]*)"
m = re.match(err_regex, str(e))
if m is None:
- raise # pragma: no cover
- else:
- host, path, _err_no, err_msg = m.groups()
- raise ValueError("Requesting {0}{1}:{2}".format(host, path, err_msg))
+ raise # pragma: no cover
+ host, path, _err_no, err_msg = m.groups()
+ raise ValueError("Requesting {0}{1}:{2}".format(host, path, err_msg))
# If content is DER, log the base64 of it instead of raw bytes, to keep
# binary data out of the logs.
@@ -1187,15 +1173,11 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
if error.code == 'badNonce':
logger.debug('Retrying request after error:\n%s', error)
return self._post_once(*args, **kwargs)
- else:
- raise
+ raise
def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE,
acme_version=1, **kwargs):
- try:
- new_nonce_url = kwargs.pop('new_nonce_url')
- except KeyError:
- new_nonce_url = None
+ new_nonce_url = kwargs.pop('new_nonce_url', None)
data = self._wrap_in_jws(obj, self._get_nonce(url, new_nonce_url), url, acme_version)
kwargs.setdefault('headers', {'Content-Type': content_type})
response = self._send_request('POST', url, data=data, **kwargs)
diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py
index c88cab943..66dfc738c 100644
--- a/acme/acme/crypto_util.py
+++ b/acme/acme/crypto_util.py
@@ -6,32 +6,29 @@ import os
import re
import socket
-from OpenSSL import crypto
-from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052
import josepy as jose
+from OpenSSL import crypto
+from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052
from acme import errors
-# pylint: disable=unused-import, no-name-in-module
-from acme.magic_typing import Callable, Union, Tuple, Optional
-# pylint: enable=unused-import, no-name-in-module
-
+from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module
logger = logging.getLogger(__name__)
-# TLSSNI01 certificate serving and probing is not affected by SSL
-# vulnerabilities: prober needs to check certificate for expected
-# contents anyway. Working SNI is the only thing that's necessary for
-# the challenge and thus scoping down SSL/TLS method (version) would
-# cause interoperability issues: TLSv1_METHOD is only compatible with
+# Default SSL method selected here is the most compatible, while secure
+# SSL method: TLSv1_METHOD is only compatible with
# TLSv1_METHOD, while SSLv23_METHOD is compatible with all other
# methods, including TLSv2_METHOD (read more at
# https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni
# should be changed to use "set_options" to disable SSLv2 and SSLv3,
# in case it's used for things other than probing/serving!
-_DEFAULT_TLSSNI01_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore
+_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore
-class SSLSocket(object): # pylint: disable=too-few-public-methods
+class SSLSocket(object):
"""SSL wrapper for sockets.
:ivar socket sock: Original wrapped socket.
@@ -40,7 +37,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
:ivar method: See `OpenSSL.SSL.Context` for allowed values.
"""
- def __init__(self, sock, certs, method=_DEFAULT_TLSSNI01_SSL_METHOD):
+ def __init__(self, sock, certs, method=_DEFAULT_SSL_METHOD):
self.sock = sock
self.certs = certs
self.method = method
@@ -77,7 +74,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
class FakeConnection(object):
"""Fake OpenSSL.SSL.Connection."""
- # pylint: disable=too-few-public-methods,missing-docstring
+ # pylint: disable=missing-docstring
def __init__(self, connection):
self._wrapped = connection
@@ -112,7 +109,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
def probe_sni(name, host, port=443, timeout=300,
- method=_DEFAULT_TLSSNI01_SSL_METHOD, source_address=('', 0)):
+ method=_DEFAULT_SSL_METHOD, source_address=('', 0)):
"""Probe SNI server for SSL certificate.
:param bytes name: Byte string to send as the server name in the
@@ -137,7 +134,6 @@ def probe_sni(name, host, port=443, timeout=300,
socket_kwargs = {'source_address': source_address}
try:
- # pylint: disable=star-args
logger.debug(
"Attempting to connect to %s:%d%s.", host, port,
" from {0}:{1}".format(
@@ -198,8 +194,7 @@ def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req):
if common_name is None:
return sans
- else:
- return [common_name] + [d for d in sans if d != common_name]
+ return [common_name] + [d for d in sans if d != common_name]
def _pyopenssl_cert_or_req_san(cert_or_req):
"""Get Subject Alternative Names from certificate or CSR using pyOpenSSL.
diff --git a/acme/acme/errors.py b/acme/acme/errors.py
index 3a0f8c596..806657940 100644
--- a/acme/acme/errors.py
+++ b/acme/acme/errors.py
@@ -29,7 +29,12 @@ class NonceError(ClientError):
class BadNonce(NonceError):
"""Bad nonce error."""
def __init__(self, nonce, error, *args, **kwargs):
- super(BadNonce, self).__init__(*args, **kwargs)
+ # MyPy complains here that there is too many arguments for BaseException constructor.
+ # This is an error fixed in typeshed, see https://github.com/python/mypy/issues/4183
+ # The fix is included in MyPy>=0.740, but upgrading it would bring dozen of errors due to
+ # new types definitions. So we ignore the error until the code base is fixed to match
+ # with MyPy>=0.740 referential.
+ super(BadNonce, self).__init__(*args, **kwargs) # type: ignore
self.nonce = nonce
self.error = error
@@ -48,7 +53,8 @@ class MissingNonce(NonceError):
"""
def __init__(self, response, *args, **kwargs):
- super(MissingNonce, self).__init__(*args, **kwargs)
+ # See comment in BadNonce constructor above for an explanation of type: ignore here.
+ super(MissingNonce, self).__init__(*args, **kwargs) # type: ignore
self.response = response
def __str__(self):
@@ -83,6 +89,7 @@ class PollError(ClientError):
return '{0}(exhausted={1!r}, updated={2!r})'.format(
self.__class__.__name__, self.exhausted, self.updated)
+
class ValidationError(Error):
"""Error for authorization failures. Contains a list of authorization
resources, each of which is invalid and should have an error field.
@@ -91,9 +98,11 @@ class ValidationError(Error):
self.failed_authzrs = failed_authzrs
super(ValidationError, self).__init__()
-class TimeoutError(Error):
+
+class TimeoutError(Error): # pylint: disable=redefined-builtin
"""Error for when polling an authorization or an order times out."""
+
class IssuanceError(Error):
"""Error sent by the server after requesting issuance of a certificate."""
@@ -105,6 +114,7 @@ class IssuanceError(Error):
self.error = error
super(IssuanceError, self).__init__()
+
class ConflictError(ClientError):
"""Error for when the server returns a 409 (Conflict) HTTP status.
diff --git a/acme/acme/fields.py b/acme/acme/fields.py
index d7ec78403..3b5672283 100644
--- a/acme/acme/fields.py
+++ b/acme/acme/fields.py
@@ -4,7 +4,6 @@ import logging
import josepy as jose
import pyrfc3339
-
logger = logging.getLogger(__name__)
diff --git a/acme/acme/jws.py b/acme/acme/jws.py
index c92d226d4..894e69f3d 100644
--- a/acme/acme/jws.py
+++ b/acme/acme/jws.py
@@ -40,10 +40,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 # pylint: disable=no-member
+ __slots__ = jose.JWS._orig_slots
@classmethod
- # pylint: disable=arguments-differ,too-many-arguments
+ # pylint: disable=arguments-differ
def sign(cls, payload, key, alg, nonce, url=None, kid=None):
# Per ACME spec, jwk and kid are mutually exclusive, so only include a
# jwk field if kid is not provided.
diff --git a/acme/acme/magic_typing.py b/acme/acme/magic_typing.py
index 471b8dfa9..5a6358c69 100644
--- a/acme/acme/magic_typing.py
+++ b/acme/acme/magic_typing.py
@@ -1,6 +1,7 @@
"""Shim class to not have to depend on typing module in prod."""
import sys
+
class TypingClass(object):
"""Ignore import errors by getting anything"""
def __getattr__(self, name):
diff --git a/acme/acme/messages.py b/acme/acme/messages.py
index 7c82c8507..e82d12890 100644
--- a/acme/acme/messages.py
+++ b/acme/acme/messages.py
@@ -1,37 +1,55 @@
"""ACME protocol messages."""
-import six
import json
-try:
- from collections.abc import Hashable # pylint: disable=no-name-in-module
-except ImportError:
- from collections import Hashable
import josepy as jose
+import six
from acme import challenges
from acme import errors
from acme import fields
-from acme import util
from acme import jws
+from acme import util
+
+try:
+ from collections.abc import Hashable # pylint: disable=no-name-in-module
+except ImportError: # pragma: no cover
+ from collections import Hashable
+
+
OLD_ERROR_PREFIX = "urn:acme:error:"
ERROR_PREFIX = "urn:ietf:params:acme:error:"
ERROR_CODES = {
+ 'accountDoesNotExist': 'The request specified an account that does not exist',
+ 'alreadyRevoked': 'The request specified a certificate to be revoked that has' \
+ ' already been revoked',
'badCSR': 'The CSR is unacceptable (e.g., due to a short key)',
'badNonce': 'The client sent an unacceptable anti-replay nonce',
+ 'badPublicKey': 'The JWS was signed by a public key the server does not support',
+ 'badRevocationReason': 'The revocation reason provided is not allowed by the server',
+ 'badSignatureAlgorithm': 'The JWS was signed with an algorithm the server does not support',
+ 'caa': 'Certification Authority Authorization (CAA) records forbid the CA from issuing' \
+ ' a certificate',
+ 'compound': 'Specific error conditions are indicated in the "subproblems" array',
'connection': ('The server could not connect to the client to verify the'
' domain'),
+ 'dns': 'There was a problem with a DNS query during identifier validation',
'dnssec': 'The server could not validate a DNSSEC signed domain',
+ 'incorrectResponse': 'Response received didn\'t match the challenge\'s requirements',
# deprecate invalidEmail
'invalidEmail': 'The provided email for a registration was invalid',
'invalidContact': 'The provided contact URI was invalid',
'malformed': 'The request message was malformed',
+ 'rejectedIdentifier': 'The server will not issue certificates for the identifier',
+ 'orderNotReady': 'The request attempted to finalize an order that is not ready to be finalized',
'rateLimited': 'There were too many requests of a given type',
'serverInternal': 'The server experienced an internal error',
'tls': 'The server experienced a TLS error during domain verification',
'unauthorized': 'The client lacks sufficient authorization',
+ 'unsupportedContact': 'A contact URL for an account used an unsupported protocol scheme',
'unknownHost': 'The server could not resolve a domain name',
+ 'unsupportedIdentifier': 'An identifier is of an unsupported type',
'externalAccountRequired': 'The server requires external account binding',
}
@@ -46,8 +64,7 @@ def is_acme_error(err):
"""Check if argument is an ACME error."""
if isinstance(err, Error) and (err.typ is not None):
return (ERROR_PREFIX in err.typ) or (OLD_ERROR_PREFIX in err.typ)
- else:
- return False
+ return False
@six.python_2_unicode_compatible
@@ -102,6 +119,7 @@ class Error(jose.JSONObjectWithFields, errors.Error):
code = str(self.typ).split(':')[-1]
if code in ERROR_CODES:
return code
+ return None
def __str__(self):
return b' :: '.join(
@@ -116,18 +134,19 @@ class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore
POSSIBLE_NAMES = NotImplemented
def __init__(self, name):
- self.POSSIBLE_NAMES[name] = self
+ super(_Constant, self).__init__()
+ self.POSSIBLE_NAMES[name] = self # pylint: disable=unsupported-assignment-operation
self.name = name
def to_partial_json(self):
return self.name
@classmethod
- def from_json(cls, value):
- if value not in cls.POSSIBLE_NAMES:
+ def from_json(cls, jobj):
+ if jobj not in cls.POSSIBLE_NAMES: # pylint: disable=unsupported-membership-test
raise jose.DeserializationError(
'{0} not recognized'.format(cls.__name__))
- return cls.POSSIBLE_NAMES[value]
+ return cls.POSSIBLE_NAMES[jobj]
def __repr__(self):
return '{0}({1})'.format(self.__class__.__name__, self.name)
@@ -152,6 +171,7 @@ STATUS_VALID = Status('valid')
STATUS_INVALID = Status('invalid')
STATUS_REVOKED = Status('revoked')
STATUS_READY = Status('ready')
+STATUS_DEACTIVATED = Status('deactivated')
class IdentifierType(_Constant):
@@ -186,7 +206,6 @@ class Directory(jose.JSONDeSerializable):
def __init__(self, **kwargs):
kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items())
- # pylint: disable=star-args
super(Directory.Meta, self).__init__(**kwargs)
@property
@@ -226,13 +245,13 @@ class Directory(jose.JSONDeSerializable):
try:
return self[name.replace('_', '-')]
except KeyError as error:
- raise AttributeError(str(error) + ': ' + name)
+ raise AttributeError(str(error))
def __getitem__(self, name):
try:
return self._jobj[self._canon_key(name)]
except KeyError:
- raise KeyError('Directory field not found')
+ raise KeyError('Directory field "' + self._canon_key(name) + '" not found')
def to_partial_json(self):
return self._jobj
@@ -322,7 +341,7 @@ class Registration(ResourceBody):
def _filter_contact(self, prefix):
return tuple(
- detail[len(prefix):] for detail in self.contact
+ detail[len(prefix):] for detail in self.contact # pylint: disable=not-an-iterable
if detail.startswith(prefix))
@property
@@ -394,7 +413,6 @@ class ChallengeBody(ResourceBody):
def __init__(self, **kwargs):
kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items())
- # pylint: disable=star-args
super(ChallengeBody, self).__init__(**kwargs)
def encode(self, name):
@@ -457,7 +475,7 @@ class Authorization(ResourceBody):
:ivar datetime.datetime expires:
"""
- identifier = jose.Field('identifier', decoder=Identifier.from_json)
+ identifier = jose.Field('identifier', decoder=Identifier.from_json, omitempty=True)
challenges = jose.Field('challenges', omitempty=True)
combinations = jose.Field('combinations', omitempty=True)
@@ -477,7 +495,7 @@ class Authorization(ResourceBody):
def resolved_combinations(self):
"""Combinations with challenges instead of indices."""
return tuple(tuple(self.challenges[idx] for idx in combo)
- for combo in self.combinations)
+ for combo in self.combinations) # pylint: disable=not-an-iterable
@Directory.register
@@ -487,6 +505,12 @@ class NewAuthorization(Authorization):
resource = fields.Resource(resource_type)
+class UpdateAuthorization(Authorization):
+ """Update authorization."""
+ resource_type = 'authz'
+ resource = fields.Resource(resource_type)
+
+
class AuthorizationResource(ResourceWithURI):
"""Authorization Resource.
diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py
index ff9159933..cf0da4e86 100644
--- a/acme/acme/standalone.py
+++ b/acme/acme/standalone.py
@@ -1,28 +1,22 @@
"""Support for standalone client challenge solvers. """
-import argparse
import collections
import functools
import logging
-import os
import socket
-import sys
import threading
from six.moves import BaseHTTPServer # type: ignore # pylint: disable=import-error
from six.moves import http_client # pylint: disable=import-error
from six.moves import socketserver # type: ignore # pylint: disable=import-error
-import OpenSSL
-
from acme import challenges
from acme import crypto_util
-from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
-
+from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
logger = logging.getLogger(__name__)
# six.moves.* | pylint: disable=no-member,attribute-defined-outside-init
-# pylint: disable=too-few-public-methods,no-init
+# pylint: disable=no-init
class TLSServer(socketserver.TCPServer):
@@ -37,7 +31,7 @@ class TLSServer(socketserver.TCPServer):
self.certs = kwargs.pop("certs", {})
self.method = kwargs.pop(
# pylint: disable=protected-access
- "method", crypto_util._DEFAULT_TLSSNI01_SSL_METHOD)
+ "method", crypto_util._DEFAULT_SSL_METHOD)
self.allow_reuse_address = kwargs.pop("allow_reuse_address", True)
socketserver.TCPServer.__init__(self, *args, **kwargs)
@@ -50,7 +44,7 @@ class TLSServer(socketserver.TCPServer):
return socketserver.TCPServer.server_bind(self)
-class ACMEServerMixin: # pylint: disable=old-style-class
+class ACMEServerMixin:
"""ACME server common settings mixin."""
# TODO: c.f. #858
server_version = "ACME client standalone challenge solver"
@@ -82,7 +76,7 @@ class BaseDualNetworkedServers(object):
kwargs["ipv6"] = ip_version
new_address = (server_address[0],) + (port,) + server_address[2:]
new_args = (new_address,) + remaining_args
- server = ServerClass(*new_args, **kwargs) # pylint: disable=star-args
+ server = ServerClass(*new_args, **kwargs)
logger.debug(
"Successfully bound to %s:%s using %s", new_address[0],
new_address[1], "IPv6" if ip_version else "IPv4")
@@ -90,8 +84,8 @@ class BaseDualNetworkedServers(object):
if self.servers:
# Already bound using IPv6.
logger.debug(
- "Certbot wasn't able to bind to %s:%s using %s, this " +
- "is often expected due to the dual stack nature of " +
+ "Certbot wasn't able to bind to %s:%s using %s, this "
+ "is often expected due to the dual stack nature of "
"IPv6 socket implementations.",
new_address[0], new_address[1],
"IPv6" if ip_version else "IPv4")
@@ -104,14 +98,13 @@ class BaseDualNetworkedServers(object):
# If two servers are set up and port 0 was passed in, ensure we always
# bind to the same port for both servers.
port = server.socket.getsockname()[1]
- if len(self.servers) == 0:
+ if not self.servers:
raise socket.error("Could not bind to IPv4 or IPv6.")
def serve_forever(self):
"""Wraps socketserver.TCPServer.serve_forever"""
for server in self.servers:
thread = threading.Thread(
- # pylint: disable=no-member
target=server.serve_forever)
thread.start()
self.threads.append(thread)
@@ -131,35 +124,6 @@ class BaseDualNetworkedServers(object):
self.threads = []
-class TLSSNI01Server(TLSServer, ACMEServerMixin):
- """TLSSNI01 Server."""
-
- def __init__(self, server_address, certs, ipv6=False):
- TLSServer.__init__(
- self, server_address, BaseRequestHandlerWithLogging, certs=certs, ipv6=ipv6)
-
-
-class TLSSNI01DualNetworkedServers(BaseDualNetworkedServers):
- """TLSSNI01Server Wrapper. Tries everything for both. Failures for one don't
- affect the other."""
-
- def __init__(self, *args, **kwargs):
- BaseDualNetworkedServers.__init__(self, TLSSNI01Server, *args, **kwargs)
-
-
-class BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler):
- """BaseRequestHandler with logging."""
-
- def log_message(self, format, *args): # pylint: disable=redefined-builtin
- """Log arbitrary message."""
- logger.debug("%s - - %s", self.client_address[0], format % args)
-
- def handle(self):
- """Handle request."""
- self.log_message("Incoming request")
- socketserver.BaseRequestHandler.handle(self)
-
-
class HTTPServer(BaseHTTPServer.HTTPServer):
"""Generic HTTP Server."""
@@ -262,39 +226,3 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""
return functools.partial(
cls, simple_http_resources=simple_http_resources)
-
-
-def simple_tls_sni_01_server(cli_args, forever=True):
- """Run simple standalone TLSSNI01 server."""
- logging.basicConfig(level=logging.DEBUG)
-
- parser = argparse.ArgumentParser()
- parser.add_argument(
- "-p", "--port", default=0, help="Port to serve at. By default "
- "picks random free port.")
- args = parser.parse_args(cli_args[1:])
-
- certs = {}
-
- _, hosts, _ = next(os.walk('.')) # type: ignore # https://github.com/python/mypy/issues/465
- for host in hosts:
- with open(os.path.join(host, "cert.pem")) as cert_file:
- cert_contents = cert_file.read()
- with open(os.path.join(host, "key.pem")) as key_file:
- key_contents = key_file.read()
- certs[host.encode()] = (
- OpenSSL.crypto.load_privatekey(
- OpenSSL.crypto.FILETYPE_PEM, key_contents),
- OpenSSL.crypto.load_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert_contents))
-
- server = TLSSNI01Server(('', int(args.port)), certs=certs)
- logger.info("Serving at https://%s:%s...", *server.socket.getsockname()[:2])
- if forever: # pragma: no cover
- server.serve_forever()
- else:
- server.handle_request()
-
-
-if __name__ == "__main__":
- sys.exit(simple_tls_sni_01_server(sys.argv)) # pragma: no cover
diff --git a/acme/docs/conf.py b/acme/docs/conf.py
index e70651648..8c1689128 100644
--- a/acme/docs/conf.py
+++ b/acme/docs/conf.py
@@ -12,10 +12,9 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys
import os
import shlex
-
+import sys
here = os.path.abspath(os.path.dirname(__file__))
@@ -42,7 +41,7 @@ extensions = [
]
autodoc_member_order = 'bysource'
-autodoc_default_flags = ['show-inheritance', 'private-members']
+autodoc_default_flags = ['show-inheritance']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
diff --git a/acme/examples/http01_example.py b/acme/examples/http01_example.py
new file mode 100644
index 000000000..2dc197d09
--- /dev/null
+++ b/acme/examples/http01_example.py
@@ -0,0 +1,241 @@
+"""Example ACME-V2 API for HTTP-01 challenge.
+
+Brief:
+
+This a complete usage example of the python-acme API.
+
+Limitations of this example:
+ - Works for only one Domain name
+ - Performs only HTTP-01 challenge
+ - Uses ACME-v2
+
+Workflow:
+ (Account creation)
+ - Create account key
+ - Register account and accept TOS
+ (Certificate actions)
+ - Select HTTP-01 within offered challenges by the CA server
+ - Set up http challenge resource
+ - Set up standalone web server
+ - Create domain private key and CSR
+ - Issue certificate
+ - Renew certificate
+ - Revoke certificate
+ (Account update actions)
+ - Change contact information
+ - Deactivate Account
+"""
+from contextlib import contextmanager
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import rsa
+import josepy as jose
+import OpenSSL
+
+from acme import challenges
+from acme import client
+from acme import crypto_util
+from acme import errors
+from acme import messages
+from acme import standalone
+
+# Constants:
+
+# This is the staging point for ACME-V2 within Let's Encrypt.
+DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'
+
+USER_AGENT = 'python-acme-example'
+
+# Account key size
+ACC_KEY_BITS = 2048
+
+# Certificate private key size
+CERT_PKEY_BITS = 2048
+
+# Domain name for the certificate.
+DOMAIN = 'client.example.com'
+
+# If you are running Boulder locally, it is possible to configure any port
+# number to execute the challenge, but real CA servers will always use port
+# 80, as described in the ACME specification.
+PORT = 80
+
+
+# Useful methods and classes:
+
+
+def new_csr_comp(domain_name, pkey_pem=None):
+ """Create certificate signing request."""
+ if pkey_pem is None:
+ # Create private key.
+ pkey = OpenSSL.crypto.PKey()
+ pkey.generate_key(OpenSSL.crypto.TYPE_RSA, CERT_PKEY_BITS)
+ pkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM,
+ pkey)
+ csr_pem = crypto_util.make_csr(pkey_pem, [domain_name])
+ return pkey_pem, csr_pem
+
+
+def select_http01_chall(orderr):
+ """Extract authorization resource from within order resource."""
+ # Authorization Resource: authz.
+ # This object holds the offered challenges by the server and their status.
+ authz_list = orderr.authorizations
+
+ for authz in authz_list:
+ # Choosing challenge.
+ # authz.body.challenges is a set of ChallengeBody objects.
+ for i in authz.body.challenges:
+ # Find the supported challenge.
+ if isinstance(i.chall, challenges.HTTP01):
+ return i
+
+ raise Exception('HTTP-01 challenge was not offered by the CA server.')
+
+
+@contextmanager
+def challenge_server(http_01_resources):
+ """Manage standalone server set up and shutdown."""
+
+ # Setting up a fake server that binds at PORT and any address.
+ address = ('', PORT)
+ try:
+ servers = standalone.HTTP01DualNetworkedServers(address,
+ http_01_resources)
+ # Start client standalone web server.
+ servers.serve_forever()
+ yield servers
+ finally:
+ # Shutdown client web server and unbind from PORT
+ servers.shutdown_and_server_close()
+
+
+def perform_http01(client_acme, challb, orderr):
+ """Set up standalone webserver and perform HTTP-01 challenge."""
+
+ response, validation = challb.response_and_validation(client_acme.net.key)
+
+ resource = standalone.HTTP01RequestHandler.HTTP01Resource(
+ chall=challb.chall, response=response, validation=validation)
+
+ with challenge_server({resource}):
+ # Let the CA server know that we are ready for the challenge.
+ client_acme.answer_challenge(challb, response)
+
+ # Wait for challenge status and then issue a certificate.
+ # It is possible to set a deadline time.
+ finalized_orderr = client_acme.poll_and_finalize(orderr)
+
+ return finalized_orderr.fullchain_pem
+
+
+# Main examples:
+
+
+def example_http():
+ """This example executes the whole process of fulfilling a HTTP-01
+ challenge for one specific domain.
+
+ The workflow consists of:
+ (Account creation)
+ - Create account key
+ - Register account and accept TOS
+ (Certificate actions)
+ - Select HTTP-01 within offered challenges by the CA server
+ - Set up http challenge resource
+ - Set up standalone web server
+ - Create domain private key and CSR
+ - Issue certificate
+ - Renew certificate
+ - Revoke certificate
+ (Account update actions)
+ - Change contact information
+ - Deactivate Account
+
+ """
+ # Create account key
+
+ acc_key = jose.JWKRSA(
+ key=rsa.generate_private_key(public_exponent=65537,
+ key_size=ACC_KEY_BITS,
+ backend=default_backend()))
+
+ # Register account and accept TOS
+
+ net = client.ClientNetwork(acc_key, user_agent=USER_AGENT)
+ directory = messages.Directory.from_json(net.get(DIRECTORY_URL).json())
+ client_acme = client.ClientV2(directory, net=net)
+
+ # Terms of Service URL is in client_acme.directory.meta.terms_of_service
+ # Registration Resource: regr
+ # Creates account with contact information.
+ email = ('fake@example.com')
+ regr = client_acme.new_account(
+ messages.NewRegistration.from_data(
+ email=email, terms_of_service_agreed=True))
+
+ # Create domain private key and CSR
+ pkey_pem, csr_pem = new_csr_comp(DOMAIN)
+
+ # Issue certificate
+
+ orderr = client_acme.new_order(csr_pem)
+
+ # Select HTTP-01 within offered challenges by the CA server
+ challb = select_http01_chall(orderr)
+
+ # The certificate is ready to be used in the variable "fullchain_pem".
+ fullchain_pem = perform_http01(client_acme, challb, orderr)
+
+ # Renew certificate
+
+ _, csr_pem = new_csr_comp(DOMAIN, pkey_pem)
+
+ orderr = client_acme.new_order(csr_pem)
+
+ challb = select_http01_chall(orderr)
+
+ # Performing challenge
+ fullchain_pem = perform_http01(client_acme, challb, orderr)
+
+ # Revoke certificate
+
+ fullchain_com = jose.ComparableX509(
+ OpenSSL.crypto.load_certificate(
+ OpenSSL.crypto.FILETYPE_PEM, fullchain_pem))
+
+ try:
+ client_acme.revoke(fullchain_com, 0) # revocation reason = 0
+ except errors.ConflictError:
+ # Certificate already revoked.
+ pass
+
+ # Query registration status.
+ client_acme.net.account = regr
+ try:
+ regr = client_acme.query_registration(regr)
+ except errors.Error as err:
+ if err.typ == messages.OLD_ERROR_PREFIX + 'unauthorized' \
+ or err.typ == messages.ERROR_PREFIX + 'unauthorized':
+ # Status is deactivated.
+ pass
+ raise
+
+ # Change contact information
+
+ email = 'newfake@example.com'
+ regr = client_acme.update_registration(
+ regr.update(
+ body=regr.body.update(
+ contact=('mailto:' + email,)
+ )
+ )
+ )
+
+ # Deactivate account/registration
+
+ regr = client_acme.deactivate_registration(regr)
+
+
+if __name__ == "__main__":
+ example_http()
diff --git a/acme/readthedocs.org.requirements.txt b/acme/readthedocs.org.requirements.txt
index 65e6c7cf3..168af8013 100644
--- a/acme/readthedocs.org.requirements.txt
+++ b/acme/readthedocs.org.requirements.txt
@@ -1,10 +1,10 @@
# readthedocs.org gives no way to change the install command to "pip
-# install -e .[docs]" (that would in turn install documentation
+# install -e acme[docs]" (that would in turn install documentation
# dependencies), but it allows to specify a requirements.txt file at
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
# Although ReadTheDocs certainly doesn't need to install the project
-# in --editable mode (-e), just "pip install .[docs]" does not work as
-# expected and "pip install -e .[docs]" must be used instead
+# in --editable mode (-e), just "pip install acme[docs]" does not work as
+# expected and "pip install -e acme[docs]" must be used instead
-e acme[docs]
diff --git a/acme/setup.py b/acme/setup.py
index eac3974fa..0e11779ba 100644
--- a/acme/setup.py
+++ b/acme/setup.py
@@ -1,9 +1,10 @@
-from setuptools import setup
+import sys
+
from setuptools import find_packages
+from setuptools import setup
from setuptools.command.test import test as TestCommand
-import sys
-version = '0.31.0.dev0'
+version = '1.3.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
@@ -11,9 +12,11 @@ install_requires = [
# rsa_recover_prime_factors (>=0.8)
'cryptography>=1.2.3',
# formerly known as acme.jose:
- 'josepy>=1.0.0',
- # Connection.set_tlsext_host_name (>=0.13)
+ # 1.1.0+ is required to avoid the warnings described at
+ # https://github.com/certbot/josepy/issues/13.
+ 'josepy>=1.1.0',
'mock',
+ # Connection.set_tlsext_host_name (>=0.13)
'PyOpenSSL>=0.13.1',
'pyrfc3339',
'pytz',
@@ -34,6 +37,7 @@ docs_extras = [
'sphinx_rtd_theme',
]
+
class PyTest(TestCommand):
user_options = []
@@ -48,6 +52,7 @@ class PyTest(TestCommand):
errno = pytest.main(shlex.split(self.pytest_args))
sys.exit(errno)
+
setup(
name='acme',
version=version,
@@ -56,7 +61,7 @@ setup(
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
- python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
+ python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
@@ -65,10 +70,10 @@ setup(
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],
@@ -80,7 +85,7 @@ setup(
'dev': dev_extras,
'docs': docs_extras,
},
- tests_require=["pytest"],
test_suite='acme',
+ tests_require=["pytest"],
cmdclass={"test": PyTest},
)
diff --git a/acme/acme/challenges_test.py b/acme/tests/challenges_test.py
index be15e5b1a..adebaffc5 100644
--- a/acme/acme/challenges_test.py
+++ b/acme/tests/challenges_test.py
@@ -1,16 +1,12 @@
"""Tests for acme.challenges."""
import unittest
-import warnings
import josepy as jose
import mock
-import OpenSSL
import requests
+from six.moves.urllib import parse as urllib_parse
-from six.moves.urllib import parse as urllib_parse # pylint: disable=import-error
-
-from acme import errors
-from acme import test_util
+import test_util
CERT = test_util.load_comparable_cert('cert.pem')
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
@@ -22,7 +18,6 @@ class ChallengeTest(unittest.TestCase):
from acme.challenges import Challenge
from acme.challenges import UnrecognizedChallenge
chall = UnrecognizedChallenge({"type": "foo"})
- # pylint: disable=no-member
self.assertEqual(chall, Challenge.from_json(chall.jobj))
@@ -78,7 +73,6 @@ class KeyAuthorizationChallengeResponseTest(unittest.TestCase):
class DNS01ResponseTest(unittest.TestCase):
- # pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.challenges import DNS01Response
@@ -94,7 +88,8 @@ class DNS01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
- self.assertEqual(self.jmsg, self.msg.to_partial_json())
+ self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
+ self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import DNS01Response
@@ -149,7 +144,6 @@ class DNS01Test(unittest.TestCase):
class HTTP01ResponseTest(unittest.TestCase):
- # pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.challenges import HTTP01Response
@@ -165,7 +159,8 @@ class HTTP01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
- self.assertEqual(self.jmsg, self.msg.to_partial_json())
+ self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
+ self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import HTTP01Response
@@ -258,152 +253,7 @@ class HTTP01Test(unittest.TestCase):
self.msg.update(token=b'..').good_token)
-class TLSSNI01ResponseTest(unittest.TestCase):
- # pylint: disable=too-many-instance-attributes
-
- def setUp(self):
- from acme.challenges import TLSSNI01
- self.chall = TLSSNI01(
- token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e'))
-
- self.response = self.chall.response(KEY)
- self.jmsg = {
- 'resource': 'challenge',
- 'type': 'tls-sni-01',
- 'keyAuthorization': self.response.key_authorization,
- }
-
- # pylint: disable=invalid-name
- label1 = b'dc38d9c3fa1a4fdcc3a5501f2d38583f'
- label2 = b'b7793728f084394f2a1afd459556bb5c'
- self.z = label1 + label2
- self.z_domain = label1 + b'.' + label2 + b'.acme.invalid'
- self.domain = 'foo.com'
-
- def test_z_and_domain(self):
- self.assertEqual(self.z, self.response.z)
- self.assertEqual(self.z_domain, self.response.z_domain)
-
- def test_to_partial_json(self):
- self.assertEqual(self.jmsg, self.response.to_partial_json())
-
- def test_from_json(self):
- from acme.challenges import TLSSNI01Response
- self.assertEqual(self.response, TLSSNI01Response.from_json(self.jmsg))
-
- def test_from_json_hashable(self):
- from acme.challenges import TLSSNI01Response
- hash(TLSSNI01Response.from_json(self.jmsg))
-
- @mock.patch('acme.challenges.socket.gethostbyname')
- @mock.patch('acme.challenges.crypto_util.probe_sni')
- def test_probe_cert(self, mock_probe_sni, mock_gethostbyname):
- mock_gethostbyname.return_value = '127.0.0.1'
- self.response.probe_cert('foo.com')
- mock_gethostbyname.assert_called_once_with('foo.com')
- mock_probe_sni.assert_called_once_with(
- host='127.0.0.1', port=self.response.PORT,
- name=self.z_domain)
-
- self.response.probe_cert('foo.com', host='8.8.8.8')
- mock_probe_sni.assert_called_with(
- host='8.8.8.8', port=mock.ANY, name=mock.ANY)
-
- self.response.probe_cert('foo.com', port=1234)
- mock_probe_sni.assert_called_with(
- host=mock.ANY, port=1234, name=mock.ANY)
-
- self.response.probe_cert('foo.com', bar='baz')
- mock_probe_sni.assert_called_with(
- host=mock.ANY, port=mock.ANY, name=mock.ANY, bar='baz')
-
- self.response.probe_cert('foo.com', name=b'xxx')
- mock_probe_sni.assert_called_with(
- host=mock.ANY, port=mock.ANY,
- name=self.z_domain)
-
- def test_gen_verify_cert(self):
- key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem')
- cert, key2 = self.response.gen_cert(key1)
- self.assertEqual(key1, key2)
- self.assertTrue(self.response.verify_cert(cert))
-
- def test_gen_verify_cert_gen_key(self):
- cert, key = self.response.gen_cert()
- self.assertTrue(isinstance(key, OpenSSL.crypto.PKey))
- self.assertTrue(self.response.verify_cert(cert))
-
- def test_verify_bad_cert(self):
- self.assertFalse(self.response.verify_cert(
- test_util.load_cert('cert.pem')))
-
- def test_simple_verify_bad_key_authorization(self):
- key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
- self.response.simple_verify(self.chall, "local", key2.public_key())
-
- @mock.patch('acme.challenges.TLSSNI01Response.verify_cert', autospec=True)
- def test_simple_verify(self, mock_verify_cert):
- mock_verify_cert.return_value = mock.sentinel.verification
- self.assertEqual(
- mock.sentinel.verification, self.response.simple_verify(
- self.chall, self.domain, KEY.public_key(),
- cert=mock.sentinel.cert))
- mock_verify_cert.assert_called_once_with(
- self.response, mock.sentinel.cert)
-
- @mock.patch('acme.challenges.TLSSNI01Response.probe_cert')
- def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
- mock_probe_cert.side_effect = errors.Error
- self.assertFalse(self.response.simple_verify(
- self.chall, self.domain, KEY.public_key()))
-
-
-class TLSSNI01Test(unittest.TestCase):
-
- def setUp(self):
- self.jmsg = {
- 'type': 'tls-sni-01',
- 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e',
- }
-
- def _msg(self):
- from acme.challenges import TLSSNI01
- with warnings.catch_warnings(record=True) as warn:
- warnings.simplefilter("always")
- msg = TLSSNI01(
- token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e'))
- assert warn is not None # using a raw assert for mypy
- self.assertTrue(len(warn) == 1)
- self.assertTrue(issubclass(warn[-1].category, DeprecationWarning))
- self.assertTrue('deprecated' in str(warn[-1].message))
- return msg
-
- def test_to_partial_json(self):
- self.assertEqual(self.jmsg, self._msg().to_partial_json())
-
- def test_from_json(self):
- from acme.challenges import TLSSNI01
- self.assertEqual(self._msg(), TLSSNI01.from_json(self.jmsg))
-
- def test_from_json_hashable(self):
- from acme.challenges import TLSSNI01
- hash(TLSSNI01.from_json(self.jmsg))
-
- def test_from_json_invalid_token_length(self):
- from acme.challenges import TLSSNI01
- self.jmsg['token'] = jose.encode_b64jose(b'abcd')
- self.assertRaises(
- jose.DeserializationError, TLSSNI01.from_json, self.jmsg)
-
- @mock.patch('acme.challenges.TLSSNI01Response.gen_cert')
- def test_validation(self, mock_gen_cert):
- mock_gen_cert.return_value = ('cert', 'key')
- self.assertEqual(('cert', 'key'), self._msg().validation(
- KEY, cert_key=mock.sentinel.cert_key))
- mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key)
-
class TLSALPN01ResponseTest(unittest.TestCase):
- # pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.challenges import TLSALPN01Response
@@ -419,7 +269,8 @@ class TLSALPN01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
- self.assertEqual(self.jmsg, self.msg.to_partial_json())
+ self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
+ self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import TLSALPN01Response
diff --git a/acme/acme/client_test.py b/acme/tests/client_test.py
index b3d0f1921..a38fedbd6 100644
--- a/acme/acme/client_test.py
+++ b/acme/tests/client_test.py
@@ -5,21 +5,19 @@ import datetime
import json
import unittest
-from six.moves import http_client # pylint: disable=import-error
-
import josepy as jose
import mock
import OpenSSL
import requests
+from six.moves import http_client # pylint: disable=import-error
from acme import challenges
from acme import errors
from acme import jws as acme_jws
from acme import messages
-from acme import messages_test
-from acme import test_util
-from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
-
+from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
+import messages_test
+import test_util
CERT_DER = test_util.load_vector('cert.der')
CERT_SAN_PEM = test_util.load_vector('cert-san.pem')
@@ -63,8 +61,8 @@ class ClientTestBase(unittest.TestCase):
self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212')
reg = messages.Registration(
contact=self.contact, key=KEY.public_key())
- the_arg = dict(reg) # type: Dict
- self.new_reg = messages.NewRegistration(**the_arg) # pylint: disable=star-args
+ the_arg = dict(reg) # type: Dict
+ self.new_reg = messages.NewRegistration(**the_arg)
self.regr = messages.RegistrationResource(
body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1')
@@ -318,7 +316,6 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
class ClientTest(ClientTestBase):
"""Tests for acme.client.Client."""
- # pylint: disable=too-many-instance-attributes,too-many-public-methods
def setUp(self):
super(ClientTest, self).setUp()
@@ -358,7 +355,6 @@ class ClientTest(ClientTestBase):
def test_register(self):
# "Instance of 'Field' has no to_json/update member" bug:
- # pylint: disable=no-member
self.response.status_code = http_client.CREATED
self.response.json.return_value = self.regr.body.to_json()
self.response.headers['Location'] = self.regr.uri
@@ -371,7 +367,6 @@ class ClientTest(ClientTestBase):
def test_update_registration(self):
# "Instance of 'Field' has no to_json/update member" bug:
- # pylint: disable=no-member
self.response.headers['Location'] = self.regr.uri
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr, self.client.update_registration(self.regr))
@@ -639,6 +634,14 @@ class ClientTest(ClientTestBase):
errors.PollError, self.client.poll_and_request_issuance,
csr, authzrs, mintime=mintime, max_attempts=2)
+ def test_deactivate_authorization(self):
+ authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED)
+ self.response.json.return_value = authzb.to_json()
+ authzr = self.client.deactivate_authorization(self.authzr)
+ self.assertEqual(authzb, authzr.body)
+ self.assertEqual(self.client.net.post.call_count, 1)
+ self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0])
+
def test_check_cert(self):
self.response.headers['Location'] = self.certr.uri
self.response.content = CERT_DER
@@ -844,7 +847,6 @@ class ClientV2Test(ClientTestBase):
def test_update_registration(self):
# "Instance of 'Field' has no to_json/update member" bug:
- # pylint: disable=no-member
self.response.headers['Location'] = self.regr.uri
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr, self.client.update_registration(self.regr))
@@ -883,19 +885,6 @@ class ClientV2Test(ClientTestBase):
new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce')
self.client.net.get.assert_not_called()
- class FakeError(messages.Error): # pylint: disable=too-many-ancestors
- """Fake error to reproduce a malformed request ACME error"""
- def __init__(self): # pylint: disable=super-init-not-called
- pass
- @property
- def code(self):
- return 'malformed'
- self.client.net.post.side_effect = FakeError()
-
- self.client.poll(self.authzr2) # pylint: disable=protected-access
-
- self.client.net.get.assert_called_once_with(self.authzr2.uri)
-
class MockJSONDeSerializable(jose.JSONDeSerializable):
# pylint: disable=missing-docstring
@@ -906,13 +895,12 @@ class MockJSONDeSerializable(jose.JSONDeSerializable):
return {'foo': self.value}
@classmethod
- def from_json(cls, value):
+ def from_json(cls, jobj):
pass # pragma: no cover
class ClientNetworkTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork."""
- # pylint: disable=too-many-public-methods
def setUp(self):
self.verify_ssl = mock.MagicMock()
@@ -962,8 +950,8 @@ class ClientNetworkTest(unittest.TestCase):
def test_check_response_not_ok_jobj_error(self):
self.response.ok = False
- self.response.json.return_value = messages.Error(
- detail='foo', typ='serverInternal', title='some title').to_json()
+ self.response.json.return_value = messages.Error.with_code(
+ 'serverInternal', detail='foo', title='some title').to_json()
# pylint: disable=protected-access
self.assertRaises(
messages.Error, self.net._check_response, self.response)
@@ -988,7 +976,7 @@ class ClientNetworkTest(unittest.TestCase):
self.response.json.side_effect = ValueError
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
- # pylint: disable=protected-access,no-value-for-parameter
+ # pylint: disable=protected-access
self.assertEqual(
self.response, self.net._check_response(self.response))
@@ -1002,7 +990,7 @@ class ClientNetworkTest(unittest.TestCase):
self.response.json.return_value = {}
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
- # pylint: disable=protected-access,no-value-for-parameter
+ # pylint: disable=protected-access
self.assertEqual(
self.response, self.net._check_response(self.response))
@@ -1118,7 +1106,6 @@ class ClientNetworkTest(unittest.TestCase):
class ClientNetworkWithMockedResponseTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork which mock out response."""
- # pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.client import ClientNetwork
@@ -1128,8 +1115,8 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.response.headers = {}
self.response.links = {}
self.response.checked = False
- self.acmev1_nonce_response = mock.MagicMock(ok=False,
- status_code=http_client.METHOD_NOT_ALLOWED)
+ self.acmev1_nonce_response = mock.MagicMock(
+ ok=False, status_code=http_client.METHOD_NOT_ALLOWED)
self.acmev1_nonce_response.headers = {}
self.obj = mock.MagicMock()
self.wrapped_obj = mock.MagicMock()
diff --git a/acme/acme/crypto_util_test.py b/acme/tests/crypto_util_test.py
index 44b245bbe..41640ed60 100644
--- a/acme/acme/crypto_util_test.py
+++ b/acme/tests/crypto_util_test.py
@@ -5,15 +5,14 @@ import threading
import time
import unittest
-import six
-from six.moves import socketserver #type: ignore # pylint: disable=import-error
-
import josepy as jose
import OpenSSL
+import six
+from six.moves import socketserver # type: ignore # pylint: disable=import-error
from acme import errors
-from acme import test_util
-from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
+import test_util
class SSLSocketAndProbeSNITest(unittest.TestCase):
@@ -30,7 +29,6 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
class _TestServer(socketserver.TCPServer):
- # pylint: disable=too-few-public-methods
# six.moves.* | pylint: disable=attribute-defined-outside-init,no-init
def server_bind(self): # pylint: disable=missing-docstring
@@ -40,7 +38,6 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
self.server = _TestServer(('', 0), socketserver.BaseRequestHandler)
self.port = self.server.socket.getsockname()[1]
self.server_thread = threading.Thread(
- # pylint: disable=no-member
target=self.server.handle_request)
def tearDown(self):
@@ -67,7 +64,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
def test_probe_connection_error(self):
# pylint has a hard time with six
- self.server.server_close() # pylint: disable=no-member
+ self.server.server_close()
original_timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(1)
diff --git a/acme/acme/errors_test.py b/acme/tests/errors_test.py
index 1e5f3d479..1e5f3d479 100644
--- a/acme/acme/errors_test.py
+++ b/acme/tests/errors_test.py
diff --git a/acme/acme/fields_test.py b/acme/tests/fields_test.py
index 69dde8b89..69dde8b89 100644
--- a/acme/acme/fields_test.py
+++ b/acme/tests/fields_test.py
diff --git a/acme/acme/jose_test.py b/acme/tests/jose_test.py
index 340624a4f..e008cb6fc 100644
--- a/acme/acme/jose_test.py
+++ b/acme/tests/jose_test.py
@@ -2,6 +2,7 @@
import importlib
import unittest
+
class JoseTest(unittest.TestCase):
"""Tests for acme.jose shim."""
@@ -20,11 +21,10 @@ class JoseTest(unittest.TestCase):
# We use the imports below with eval, but pylint doesn't
# understand that.
- # pylint: disable=eval-used,unused-variable
- import acme
- import josepy
- acme_jose_mod = eval(acme_jose_path)
- josepy_mod = eval(josepy_path)
+ import acme # pylint: disable=unused-import
+ import josepy # pylint: disable=unused-import
+ acme_jose_mod = eval(acme_jose_path) # pylint: disable=eval-used
+ josepy_mod = eval(josepy_path) # pylint: disable=eval-used
self.assertIs(acme_jose_mod, josepy_mod)
self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute))
diff --git a/acme/acme/jws_test.py b/acme/tests/jws_test.py
index aa3ccb700..2e6ad72dd 100644
--- a/acme/acme/jws_test.py
+++ b/acme/tests/jws_test.py
@@ -3,8 +3,7 @@ import unittest
import josepy as jose
-from acme import test_util
-
+import test_util
KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
diff --git a/acme/acme/magic_typing_test.py b/acme/tests/magic_typing_test.py
index 23dfe3367..23dfe3367 100644
--- a/acme/acme/magic_typing_test.py
+++ b/acme/tests/magic_typing_test.py
diff --git a/acme/acme/messages_test.py b/acme/tests/messages_test.py
index 7efaaa1a3..b9b70266b 100644
--- a/acme/acme/messages_test.py
+++ b/acme/tests/messages_test.py
@@ -5,9 +5,8 @@ import josepy as jose
import mock
from acme import challenges
-from acme import test_util
-from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
-
+from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
+import test_util
CERT = test_util.load_comparable_cert('cert.der')
CSR = test_util.load_comparable_csr('csr.der')
@@ -19,8 +18,7 @@ class ErrorTest(unittest.TestCase):
def setUp(self):
from acme.messages import Error, ERROR_PREFIX
- self.error = Error(
- detail='foo', typ=ERROR_PREFIX + 'malformed', title='title')
+ self.error = Error.with_code('malformed', detail='foo', title='title')
self.jobj = {
'detail': 'foo',
'title': 'some title',
@@ -28,7 +26,6 @@ class ErrorTest(unittest.TestCase):
}
self.error_custom = Error(typ='custom', detail='bar')
self.empty_error = Error()
- self.jobj_custom = {'type': 'custom', 'detail': 'bar'}
def test_default_typ(self):
from acme.messages import Error
@@ -43,8 +40,7 @@ class ErrorTest(unittest.TestCase):
hash(Error.from_json(self.error.to_json()))
def test_description(self):
- self.assertEqual(
- 'The request message was malformed', self.error.description)
+ self.assertEqual('The request message was malformed', self.error.description)
self.assertTrue(self.error_custom.description is None)
def test_code(self):
@@ -54,17 +50,17 @@ class ErrorTest(unittest.TestCase):
self.assertEqual(None, Error().code)
def test_is_acme_error(self):
- from acme.messages import is_acme_error
+ from acme.messages import is_acme_error, Error
self.assertTrue(is_acme_error(self.error))
self.assertFalse(is_acme_error(self.error_custom))
+ self.assertFalse(is_acme_error(Error()))
self.assertFalse(is_acme_error(self.empty_error))
self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}"))
def test_unicode_error(self):
- from acme.messages import Error, ERROR_PREFIX, is_acme_error
- arabic_error = Error(
- detail=u'\u0639\u062f\u0627\u0644\u0629', typ=ERROR_PREFIX + 'malformed',
- title='title')
+ from acme.messages import Error, is_acme_error
+ arabic_error = Error.with_code(
+ 'malformed', detail=u'\u0639\u062f\u0627\u0644\u0629', title='title')
self.assertTrue(is_acme_error(arabic_error))
def test_with_code(self):
@@ -305,8 +301,7 @@ class ChallengeBodyTest(unittest.TestCase):
from acme.messages import Error
from acme.messages import STATUS_INVALID
self.status = STATUS_INVALID
- error = Error(typ='urn:ietf:params:acme:error:serverInternal',
- detail='Unable to communicate with DNS server')
+ error = Error.with_code('serverInternal', detail='Unable to communicate with DNS server')
self.challb = ChallengeBody(
uri='http://challb', chall=self.chall, status=self.status,
error=error)
diff --git a/acme/acme/standalone_test.py b/acme/tests/standalone_test.py
index ee527782a..83ced12b0 100644
--- a/acme/acme/standalone_test.py
+++ b/acme/tests/standalone_test.py
@@ -1,23 +1,17 @@
"""Tests for acme.standalone."""
-import os
-import shutil
import socket
import threading
-import tempfile
import unittest
-from six.moves import http_client # pylint: disable=import-error
-from six.moves import queue # pylint: disable=import-error
-from six.moves import socketserver # type: ignore # pylint: disable=import-error
-
import josepy as jose
import mock
import requests
+from six.moves import http_client # pylint: disable=import-error
+from six.moves import socketserver # type: ignore # pylint: disable=import-error
from acme import challenges
-from acme import crypto_util
-from acme import test_util
-from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
+import test_util
class TLSServerTest(unittest.TestCase):
@@ -28,41 +22,14 @@ class TLSServerTest(unittest.TestCase):
from acme.standalone import TLSServer
server = TLSServer(
('', 0), socketserver.BaseRequestHandler, bind_and_activate=True)
- server.server_close() # pylint: disable=no-member
+ server.server_close()
def test_ipv6(self):
if socket.has_ipv6:
from acme.standalone import TLSServer
server = TLSServer(
('', 0), socketserver.BaseRequestHandler, bind_and_activate=True, ipv6=True)
- server.server_close() # pylint: disable=no-member
-
-
-class TLSSNI01ServerTest(unittest.TestCase):
- """Test for acme.standalone.TLSSNI01Server."""
-
-
- def setUp(self):
- self.certs = {b'localhost': (
- test_util.load_pyopenssl_private_key('rsa2048_key.pem'),
- test_util.load_cert('rsa2048_cert.pem'),
- )}
- from acme.standalone import TLSSNI01Server
- self.server = TLSSNI01Server(('localhost', 0), certs=self.certs)
- # pylint: disable=no-member
- self.thread = threading.Thread(target=self.server.serve_forever)
- self.thread.start()
-
- def tearDown(self):
- self.server.shutdown() # pylint: disable=no-member
- self.thread.join()
-
- def test_it(self):
- host, port = self.server.socket.getsockname()[:2]
- cert = crypto_util.probe_sni(
- b'localhost', host=host, port=port, timeout=1)
- self.assertEqual(jose.ComparableX509(cert),
- jose.ComparableX509(self.certs[b'localhost'][1]))
+ server.server_close()
class HTTP01ServerTest(unittest.TestCase):
@@ -77,13 +44,12 @@ class HTTP01ServerTest(unittest.TestCase):
from acme.standalone import HTTP01Server
self.server = HTTP01Server(('', 0), resources=self.resources)
- # pylint: disable=no-member
self.port = self.server.socket.getsockname()[1]
self.thread = threading.Thread(target=self.server.serve_forever)
self.thread.start()
def tearDown(self):
- self.server.shutdown() # pylint: disable=no-member
+ self.server.shutdown()
self.thread.join()
def test_index(self):
@@ -136,7 +102,6 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
# NB: On Windows, socket.IPPROTO_IPV6 constant may be missing.
# We use the corresponding value (41) instead.
level = getattr(socket, "IPPROTO_IPV6", 41)
- # pylint: disable=no-member
self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1)
try:
self.server_bind()
@@ -170,33 +135,6 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
prev_port = port
-class TLSSNI01DualNetworkedServersTest(unittest.TestCase):
- """Test for acme.standalone.TLSSNI01DualNetworkedServers."""
-
-
- def setUp(self):
- self.certs = {b'localhost': (
- test_util.load_pyopenssl_private_key('rsa2048_key.pem'),
- test_util.load_cert('rsa2048_cert.pem'),
- )}
- from acme.standalone import TLSSNI01DualNetworkedServers
- self.servers = TLSSNI01DualNetworkedServers(('localhost', 0), certs=self.certs)
- self.servers.serve_forever()
-
- def tearDown(self):
- self.servers.shutdown_and_server_close()
-
- def test_connect(self):
- socknames = self.servers.getsocknames()
- # connect to all addresses
- for sockname in socknames:
- host, port = sockname[:2]
- cert = crypto_util.probe_sni(
- b'localhost', host=host, port=port, timeout=1)
- self.assertEqual(jose.ComparableX509(cert),
- jose.ComparableX509(self.certs[b'localhost'][1]))
-
-
class HTTP01DualNetworkedServersTest(unittest.TestCase):
"""Tests for acme.standalone.HTTP01DualNetworkedServers."""
@@ -209,7 +147,6 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase):
from acme.standalone import HTTP01DualNetworkedServers
self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources)
- # pylint: disable=no-member
self.port = self.servers.getsocknames()[0][1]
self.servers.serve_forever()
@@ -248,51 +185,5 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase):
self.assertFalse(self._test_http01(add=False))
-@test_util.broken_on_windows
-class TestSimpleTLSSNI01Server(unittest.TestCase):
- """Tests for acme.standalone.simple_tls_sni_01_server."""
-
-
- def setUp(self):
- # mirror ../examples/standalone
- self.test_cwd = tempfile.mkdtemp()
- localhost_dir = os.path.join(self.test_cwd, 'localhost')
- os.makedirs(localhost_dir)
- shutil.copy(test_util.vector_path('rsa2048_cert.pem'),
- os.path.join(localhost_dir, 'cert.pem'))
- shutil.copy(test_util.vector_path('rsa2048_key.pem'),
- os.path.join(localhost_dir, 'key.pem'))
-
- from acme.standalone import simple_tls_sni_01_server
- self.thread = threading.Thread(
- target=simple_tls_sni_01_server, kwargs={
- 'cli_args': ('filename',),
- 'forever': False,
- },
- )
- self.old_cwd = os.getcwd()
- os.chdir(self.test_cwd)
-
- def tearDown(self):
- os.chdir(self.old_cwd)
- self.thread.join()
- shutil.rmtree(self.test_cwd)
-
- @mock.patch('acme.standalone.logger')
- def test_it(self, mock_logger):
- # Use a Queue because mock objects aren't thread safe.
- q = queue.Queue() # type: queue.Queue[int]
- # Add port number to the queue.
- mock_logger.info.side_effect = lambda *args: q.put(args[-1])
- self.thread.start()
-
- # After the timeout, an exception is raised if the queue is empty.
- port = q.get(timeout=5)
- cert = crypto_util.probe_sni(b'localhost', b'0.0.0.0', port)
- self.assertEqual(jose.ComparableX509(cert),
- test_util.load_comparable_cert(
- 'rsa2048_cert.pem'))
-
-
if __name__ == "__main__":
unittest.main() # pragma: no cover
diff --git a/acme/acme/test_util.py b/acme/tests/test_util.py
index f97614700..d4a45272d 100644
--- a/acme/acme/test_util.py
+++ b/acme/tests/test_util.py
@@ -4,20 +4,12 @@
"""
import os
-import sys
-import pkg_resources
-import unittest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import josepy as jose
from OpenSSL import crypto
-
-
-def vector_path(*names):
- """Path to a test vector."""
- return pkg_resources.resource_filename(
- __name__, os.path.join('testdata', *names))
+import pkg_resources
def load_vector(*names):
@@ -33,8 +25,7 @@ def _guess_loader(filename, loader_pem, loader_der):
return loader_pem
elif ext.lower() == '.der':
return loader_der
- else: # pragma: no cover
- raise ValueError("Loader could not be recognized based on extension")
+ raise ValueError("Loader could not be recognized based on extension") # pragma: no cover
def load_cert(*names):
@@ -74,32 +65,3 @@ def load_pyopenssl_private_key(*names):
loader = _guess_loader(
names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
return crypto.load_privatekey(loader, load_vector(*names))
-
-
-def skip_unless(condition, reason): # pragma: no cover
- """Skip tests unless a condition holds.
-
- This implements the basic functionality of unittest.skipUnless
- which is only available on Python 2.7+.
-
- :param bool condition: If ``False``, the test will be skipped
- :param str reason: the reason for skipping the test
-
- :rtype: callable
- :returns: decorator that hides tests unless condition is ``True``
-
- """
- if hasattr(unittest, "skipUnless"):
- return unittest.skipUnless(condition, reason)
- elif condition:
- return lambda cls: cls
- else:
- return lambda cls: None
-
-def broken_on_windows(function):
- """Decorator to skip temporarily a broken test on Windows."""
- reason = 'Test is broken and ignored on windows but should be fixed.'
- return unittest.skipIf(
- sys.platform == 'win32'
- and os.environ.get('SKIP_BROKEN_TESTS_ON_WINDOWS', 'true') == 'true',
- reason)(function)
diff --git a/acme/acme/testdata/README b/acme/tests/testdata/README
index dfe3f5405..dfe3f5405 100644
--- a/acme/acme/testdata/README
+++ b/acme/tests/testdata/README
diff --git a/acme/acme/testdata/cert-100sans.pem b/acme/tests/testdata/cert-100sans.pem
index 3fdc9404f..3fdc9404f 100644
--- a/acme/acme/testdata/cert-100sans.pem
+++ b/acme/tests/testdata/cert-100sans.pem
diff --git a/acme/acme/testdata/cert-idnsans.pem b/acme/tests/testdata/cert-idnsans.pem
index 932649692..932649692 100644
--- a/acme/acme/testdata/cert-idnsans.pem
+++ b/acme/tests/testdata/cert-idnsans.pem
diff --git a/acme/acme/testdata/cert-nocn.der b/acme/tests/testdata/cert-nocn.der
index 59da83ccc..59da83ccc 100644
--- a/acme/acme/testdata/cert-nocn.der
+++ b/acme/tests/testdata/cert-nocn.der
Binary files differ
diff --git a/acme/acme/testdata/cert-san.pem b/acme/tests/testdata/cert-san.pem
index dcb835994..dcb835994 100644
--- a/acme/acme/testdata/cert-san.pem
+++ b/acme/tests/testdata/cert-san.pem
diff --git a/acme/acme/testdata/cert.der b/acme/tests/testdata/cert.der
index ab231982f..ab231982f 100644
--- a/acme/acme/testdata/cert.der
+++ b/acme/tests/testdata/cert.der
Binary files differ
diff --git a/acme/acme/testdata/cert.pem b/acme/tests/testdata/cert.pem
index 96c55cbf4..96c55cbf4 100644
--- a/acme/acme/testdata/cert.pem
+++ b/acme/tests/testdata/cert.pem
diff --git a/acme/acme/testdata/critical-san.pem b/acme/tests/testdata/critical-san.pem
index 7aec8ab1c..7aec8ab1c 100644
--- a/acme/acme/testdata/critical-san.pem
+++ b/acme/tests/testdata/critical-san.pem
diff --git a/acme/acme/testdata/csr-100sans.pem b/acme/tests/testdata/csr-100sans.pem
index 199814126..199814126 100644
--- a/acme/acme/testdata/csr-100sans.pem
+++ b/acme/tests/testdata/csr-100sans.pem
diff --git a/acme/acme/testdata/csr-6sans.pem b/acme/tests/testdata/csr-6sans.pem
index 8f6b52bd7..8f6b52bd7 100644
--- a/acme/acme/testdata/csr-6sans.pem
+++ b/acme/tests/testdata/csr-6sans.pem
diff --git a/acme/acme/testdata/csr-idnsans.pem b/acme/tests/testdata/csr-idnsans.pem
index d6e91a420..d6e91a420 100644
--- a/acme/acme/testdata/csr-idnsans.pem
+++ b/acme/tests/testdata/csr-idnsans.pem
diff --git a/acme/acme/testdata/csr-nosans.pem b/acme/tests/testdata/csr-nosans.pem
index 813db67b0..813db67b0 100644
--- a/acme/acme/testdata/csr-nosans.pem
+++ b/acme/tests/testdata/csr-nosans.pem
diff --git a/acme/acme/testdata/csr-san.pem b/acme/tests/testdata/csr-san.pem
index a7128e35c..a7128e35c 100644
--- a/acme/acme/testdata/csr-san.pem
+++ b/acme/tests/testdata/csr-san.pem
diff --git a/acme/acme/testdata/csr.der b/acme/tests/testdata/csr.der
index d43ac85a1..d43ac85a1 100644
--- a/acme/acme/testdata/csr.der
+++ b/acme/tests/testdata/csr.der
Binary files differ
diff --git a/acme/acme/testdata/csr.pem b/acme/tests/testdata/csr.pem
index b6818e39d..b6818e39d 100644
--- a/acme/acme/testdata/csr.pem
+++ b/acme/tests/testdata/csr.pem
diff --git a/acme/acme/testdata/dsa512_key.pem b/acme/tests/testdata/dsa512_key.pem
index 78e164712..78e164712 100644
--- a/acme/acme/testdata/dsa512_key.pem
+++ b/acme/tests/testdata/dsa512_key.pem
diff --git a/acme/acme/testdata/rsa1024_key.pem b/acme/tests/testdata/rsa1024_key.pem
index de5339d03..de5339d03 100644
--- a/acme/acme/testdata/rsa1024_key.pem
+++ b/acme/tests/testdata/rsa1024_key.pem
diff --git a/acme/acme/testdata/rsa2048_cert.pem b/acme/tests/testdata/rsa2048_cert.pem
index 3944cd1db..3944cd1db 100644
--- a/acme/acme/testdata/rsa2048_cert.pem
+++ b/acme/tests/testdata/rsa2048_cert.pem
diff --git a/acme/acme/testdata/rsa2048_key.pem b/acme/tests/testdata/rsa2048_key.pem
index 5847aed55..5847aed55 100644
--- a/acme/acme/testdata/rsa2048_key.pem
+++ b/acme/tests/testdata/rsa2048_key.pem
diff --git a/acme/acme/testdata/rsa256_key.pem b/acme/tests/testdata/rsa256_key.pem
index 659274d1d..659274d1d 100644
--- a/acme/acme/testdata/rsa256_key.pem
+++ b/acme/tests/testdata/rsa256_key.pem
diff --git a/acme/acme/testdata/rsa512_key.pem b/acme/tests/testdata/rsa512_key.pem
index 610c8d315..610c8d315 100644
--- a/acme/acme/testdata/rsa512_key.pem
+++ b/acme/tests/testdata/rsa512_key.pem
diff --git a/acme/acme/util_test.py b/acme/tests/util_test.py
index 00aa8b02d..00aa8b02d 100644
--- a/acme/acme/util_test.py
+++ b/acme/tests/util_test.py