Merge pull request #1271 from kuba/http-01

http-01
This commit is contained in:
bmw 2015-11-02 16:53:40 -08:00
commit 15fa5af934
31 changed files with 478 additions and 507 deletions

View file

@ -38,8 +38,9 @@ load-plugins=linter_plugin
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=fixme,locally-disabled,abstract-class-not-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name
# abstract-class-not-used cannot be disabled locally (at least in pylint 1.4.1)
disable=fixme,locally-disabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name
# abstract-class-not-used cannot be disabled locally (at least in
# pylint 1.4.1), same for abstract-class-little-used
[REPORTS]

View file

@ -1,9 +1,11 @@
"""ACME Identifier Validation Challenges."""
import abc
import functools
import hashlib
import logging
import socket
from cryptography.hazmat.primitives import hashes
import OpenSSL
import requests
@ -76,26 +78,21 @@ class UnrecognizedChallenge(Challenge):
return cls(jobj)
@Challenge.register
class SimpleHTTP(DVChallenge):
"""ACME "simpleHttp" challenge.
class _TokenDVChallenge(DVChallenge):
"""DV Challenge with token.
:ivar unicode token:
:ivar bytes token:
"""
typ = "simpleHttp"
TOKEN_SIZE = 128 / 8 # Based on the entropy value from the spec
"""Minimum size of the :attr:`token` in bytes."""
URI_ROOT_PATH = ".well-known/acme-challenge"
"""URI root path for the server provisioned resource."""
# TODO: acme-spec doesn't specify token as base64-encoded value
token = jose.Field(
"token", encoder=jose.encode_b64jose, decoder=functools.partial(
jose.decode_b64jose, size=TOKEN_SIZE, minimum=True))
# XXX: rename to ~token_good_for_url
@property
def good_token(self): # XXX: @token.decoder
"""Is `token` good?
@ -109,124 +106,130 @@ class SimpleHTTP(DVChallenge):
# URI_ROOT_PATH!
return b'..' not in self.token and b'/' not in self.token
@property
def path(self):
"""Path (starting with '/') for provisioned resource."""
return '/' + self.URI_ROOT_PATH + '/' + self.encode('token')
class KeyAuthorizationChallengeResponse(ChallengeResponse):
"""Response to Challenges based on Key Authorization.
@ChallengeResponse.register
class SimpleHTTPResponse(ChallengeResponse):
"""ACME "simpleHttp" challenge response.
:ivar bool tls:
:param unicode key_authorization:
"""
typ = "simpleHttp"
tls = jose.Field("tls", default=True, omitempty=True)
key_authorization = jose.Field("keyAuthorization")
thumbprint_hash_function = hashes.SHA256
URI_ROOT_PATH = SimpleHTTP.URI_ROOT_PATH
_URI_TEMPLATE = "{scheme}://{domain}/" + URI_ROOT_PATH + "/{token}"
def verify(self, chall, account_public_key):
"""Verify the key authorization.
CONTENT_TYPE = "application/jose+json"
PORT = 80
TLS_PORT = 443
@property
def scheme(self):
"""URL scheme for the provisioned resource."""
return "https" if self.tls else "http"
@property
def port(self):
"""Port that the ACME client should be listening for validation."""
return self.TLS_PORT if self.tls else self.PORT
def uri(self, domain, chall):
"""Create an URI to the provisioned resource.
Forms an URI to the HTTPS server provisioned resource
(containing :attr:`~SimpleHTTP.token`).
:param unicode domain: Domain name being verified.
:param challenges.SimpleHTTP chall:
"""
return self._URI_TEMPLATE.format(
scheme=self.scheme, domain=domain, token=chall.encode("token"))
def gen_resource(self, chall):
"""Generate provisioned resource.
:param challenges.SimpleHTTP chall:
:rtype: SimpleHTTPProvisionedResource
"""
return SimpleHTTPProvisionedResource(token=chall.token, tls=self.tls)
def gen_validation(self, chall, account_key, alg=jose.RS256, **kwargs):
"""Generate validation.
:param challenges.SimpleHTTP chall:
:param .JWK account_key: Private account key.
:param .JWA alg:
:returns: `.SimpleHTTPProvisionedResource` signed in `.JWS`
:rtype: .JWS
"""
return jose.JWS.sign(
payload=self.gen_resource(chall).json_dumps(
sort_keys=True).encode('utf-8'),
key=account_key, alg=alg, **kwargs)
def check_validation(self, validation, chall, account_public_key):
"""Check validation.
:param .JWS validation:
:param challenges.SimpleHTTP chall:
:type account_public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
wrapped in `.ComparableKey`
:param KeyAuthorization chall: Challenge that corresponds to
this response.
:param JWK account_public_key:
:return: ``True`` iff verification of the key authorization was
successful.
:rtype: bool
"""
if not validation.verify(key=account_public_key):
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)
return False
try:
resource = SimpleHTTPProvisionedResource.json_loads(
validation.payload.decode('utf-8'))
except jose.DeserializationError as error:
logger.debug(error)
if parts[0] != chall.encode("token"):
logger.debug("Mismatching token in key authorization: "
"%r instead of %r", parts[0], chall.encode("token"))
return False
return resource.token == chall.token and resource.tls == self.tls
thumbprint = jose.b64encode(account_public_key.thumbprint(
hash_function=self.thumbprint_hash_function)).decode()
if parts[1] != thumbprint:
logger.debug("Mismatching thumbprint in key authorization: "
"%r instead of %r", parts[0], thumbprint)
return False
return True
class KeyAuthorizationChallenge(_TokenDVChallenge):
# pylint: disable=abstract-class-little-used,too-many-ancestors
"""Challenge based on Key Authorization.
:param response_cls: Subclass of `KeyAuthorizationChallengeResponse`
that will be used to generate `response`.
"""
__metaclass__ = abc.ABCMeta
response_cls = NotImplemented
thumbprint_hash_function = (
KeyAuthorizationChallengeResponse.thumbprint_hash_function)
def key_authorization(self, account_key):
"""Generate Key Authorization.
:param JWK account_key:
:rtype unicode:
"""
return self.encode("token") + "." + jose.b64encode(
account_key.thumbprint(
hash_function=self.thumbprint_hash_function)).decode()
def response(self, account_key):
"""Generate response to the challenge.
:param JWK account_key:
:returns: Response (initialized `response_cls`) to the challenge.
:rtype: KeyAuthorizationChallengeResponse
"""
return self.response_cls(
key_authorization=self.key_authorization(account_key))
@abc.abstractmethod
def validation(self, account_key):
"""Generate validation for the challenge.
Subclasses must implement this method, but they are likely to
return completely different data structures, depending on what's
necessary to complete the challenge. Interepretation of that
return value must be known to the caller.
:param JWK account_key:
:returns: Challenge-specific validation.
"""
raise NotImplementedError() # pragma: no cover
def response_and_validation(self, account_key):
"""Generate response and validation.
Convenience function that return results of `response` and
`validation`.
:param JWK account_key:
:rtype: tuple
"""
return (self.response(account_key), self.validation(account_key))
@ChallengeResponse.register
class HTTP01Response(KeyAuthorizationChallengeResponse):
"""ACME http-01 challenge response."""
typ = "http-01"
PORT = 80
def simple_verify(self, chall, domain, account_public_key, port=None):
"""Simple verify.
According to the ACME specification, "the ACME server MUST
ignore the certificate provided by the HTTPS server", so
``requests.get`` is called with ``verify=False``.
:param challenges.SimpleHTTP chall: Corresponding challenge.
:param unicode domain: Domain name being verified.
:param account_public_key: Public key for the key pair
being authorized. If ``None`` key verification is not
performed!
:type account_public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
wrapped in `.ComparableKey`
:param JWK account_public_key:
:param int port: Port used in the validation.
:returns: ``True`` iff validation is successful, ``False``
@ -234,48 +237,89 @@ class SimpleHTTPResponse(ChallengeResponse):
:rtype: bool
"""
if not self.verify(chall, account_public_key):
logger.debug("Verification of key authorization in response failed")
return False
# TODO: ACME specification defines URI template that doesn't
# allow to use a custom port... Make sure port is not in the
# request URI, if it's standard.
if port is not None and port != self.port:
logger.warn(
if port is not None and port != self.PORT:
logger.warning(
"Using non-standard port for SimpleHTTP verification: %s", port)
domain += ":{0}".format(port)
uri = self.uri(domain, chall)
uri = chall.uri(domain)
logger.debug("Verifying %s at %s...", chall.typ, uri)
try:
http_response = requests.get(uri, verify=False)
http_response = requests.get(uri)
except requests.exceptions.RequestException as error:
logger.error("Unable to reach %s: %s", uri, error)
return False
logger.debug("Received %s: %s. Headers: %s", http_response,
http_response.text, http_response.headers)
if self.CONTENT_TYPE != http_response.headers.get(
"Content-Type", self.CONTENT_TYPE):
found_ct = http_response.headers.get(
"Content-Type", chall.CONTENT_TYPE)
if found_ct != chall.CONTENT_TYPE:
logger.debug("Wrong Content-Type: found %r, expected %r",
found_ct, chall.CONTENT_TYPE)
return False
try:
validation = jose.JWS.json_loads(http_response.text)
except jose.DeserializationError as error:
logger.debug(error)
if self.key_authorization != http_response.text:
logger.debug("Key authorization from response (%r) doesn't match "
"HTTP response (%r)", self.key_authorization,
http_response.text)
return False
return self.check_validation(validation, chall, account_public_key)
return True
class SimpleHTTPProvisionedResource(jose.JSONObjectWithFields):
"""SimpleHTTP provisioned resource."""
typ = fields.Fixed("type", SimpleHTTP.typ)
token = SimpleHTTP._fields["token"]
# If the "tls" field is not included in the response, then
# validation object MUST have its "tls" field set to "true".
tls = jose.Field("tls", omitempty=False)
@Challenge.register # pylint: disable=too-many-ancestors
class HTTP01(KeyAuthorizationChallenge):
"""ACME http-01 challenge."""
response_cls = HTTP01Response
typ = response_cls.typ
CONTENT_TYPE = "text/plain"
"""Content-Type header that must be used for provisioned resource."""
URI_ROOT_PATH = ".well-known/acme-challenge"
"""URI root path for the server provisioned resource."""
@property
def path(self):
"""Path (starting with '/') for provisioned resource.
:rtype: string
"""
return '/' + self.URI_ROOT_PATH + '/' + self.encode('token')
def uri(self, domain):
"""Create an URI to the provisioned resource.
Forms an URI to the HTTPS server provisioned resource
(containing :attr:`~SimpleHTTP.token`).
:param unicode domain: Domain name being verified.
:rtype: string
"""
return "http://" + domain + self.path
def validation(self, account_key):
"""Generate validation.
:param JWK account_key:
:rtype: unicode
"""
return self.key_authorization(account_key)
@Challenge.register
class DVSNI(DVChallenge):
@Challenge.register # pylint: disable=too-many-ancestors
class DVSNI(_TokenDVChallenge):
"""ACME "dvsni" challenge.
:ivar bytes token: Random data, **not** base64-encoded.
@ -286,13 +330,6 @@ class DVSNI(DVChallenge):
PORT = 443
"""Port to perform DVSNI challenge."""
TOKEN_SIZE = 128 / 8 # Based on the entropy value from the spec
"""Minimum size of the :attr:`token` in bytes."""
token = jose.Field(
"token", encoder=jose.encode_b64jose, decoder=functools.partial(
jose.decode_b64jose, size=TOKEN_SIZE, minimum=True))
def gen_response(self, account_key, alg=jose.RS256, **kwargs):
"""Generate response.
@ -406,17 +443,12 @@ class DVSNIResponse(ChallengeResponse):
:param .challenges.DVSNI chall: Corresponding challenge.
:param str domain: Domain name being validated.
:type account_public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
wrapped in `.ComparableKey`
:param JWK account_public_key:
:param OpenSSL.crypto.X509 cert: Optional certificate. If not
provided (``None``) certificate will be retrieved using
`probe_cert`.
:returns: ``True`` iff client's control of the domain has been
verified, ``False`` otherwise.
:rtype: bool
@ -491,7 +523,7 @@ class ProofOfPossession(ContinuityChallenge):
class Hints(jose.JSONObjectWithFields):
"""Hints for "proofOfPossession" challenge.
:ivar jwk: JSON Web Key (:class:`acme.jose.JWK`)
:ivar JWK jwk: JSON Web Key
:ivar tuple cert_fingerprints: `tuple` of `unicode`
:ivar tuple certs: Sequence of :class:`acme.jose.ComparableX509`
certificates.
@ -548,25 +580,14 @@ class ProofOfPossessionResponse(ChallengeResponse):
return self.signature.verify(self.nonce)
@Challenge.register
class DNS(DVChallenge):
"""ACME "dns" challenge.
:ivar unicode token:
"""
@Challenge.register # pylint: disable=too-many-ancestors
class DNS(_TokenDVChallenge):
"""ACME "dns" challenge."""
typ = "dns"
LABEL = "_acme-challenge"
"""Label clients prepend to the domain name being validated."""
TOKEN_SIZE = 128 / 8 # Based on the entropy value from the spec
"""Minimum size of the :attr:`token` in bytes."""
token = jose.Field(
"token", encoder=jose.encode_b64jose, decoder=functools.partial(
jose.decode_b64jose, size=TOKEN_SIZE, minimum=True))
def gen_validation(self, account_key, alg=jose.RS256, **kwargs):
"""Generate validation.
@ -585,14 +606,7 @@ class DNS(DVChallenge):
"""Check validation.
:param JWS validation:
:type account_public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
wrapped in `.ComparableKey`
:param JWK account_public_key:
:rtype: bool
"""
@ -641,13 +655,7 @@ class DNSResponse(ChallengeResponse):
"""Check validation.
:param challenges.DNS chall:
:type account_public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
wrapped in `.ComparableKey`
:param JWK account_public_key:
:rtype: bool

View file

@ -14,7 +14,7 @@ from acme import test_util
CERT = test_util.load_cert('cert.pem')
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
class ChallengeTest(unittest.TestCase):
@ -43,171 +43,149 @@ class UnrecognizedChallengeTest(unittest.TestCase):
self.chall, UnrecognizedChallenge.from_json(self.jobj))
class SimpleHTTPTest(unittest.TestCase):
class KeyAuthorizationChallengeResponseTest(unittest.TestCase):
def setUp(self):
from acme.challenges import SimpleHTTP
self.msg = SimpleHTTP(
token=jose.decode_b64jose(
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA'))
def _encode(name):
assert name == "token"
return "foo"
self.chall = mock.Mock()
self.chall.encode.side_effect = _encode
def test_verify_ok(self):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY')
self.assertTrue(response.verify(self.chall, KEY.public_key()))
def test_verify_wrong_token(self):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='bar.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY')
self.assertFalse(response.verify(self.chall, KEY.public_key()))
def test_verify_wrong_thumbprint(self):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxv')
self.assertFalse(response.verify(self.chall, KEY.public_key()))
def test_verify_wrong_form(self):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='.foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY')
self.assertFalse(response.verify(self.chall, KEY.public_key()))
class HTTP01ResponseTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.challenges import HTTP01Response
self.msg = HTTP01Response(key_authorization=u'foo')
self.jmsg = {
'type': 'simpleHttp',
'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA',
'resource': 'challenge',
'type': 'http-01',
'keyAuthorization': u'foo',
}
from acme.challenges import HTTP01
self.chall = HTTP01(token=(b'x' * 16))
self.response = self.chall.response(KEY)
self.good_headers = {'Content-Type': HTTP01.CONTENT_TYPE}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import SimpleHTTP
self.assertEqual(self.msg, SimpleHTTP.from_json(self.jmsg))
from acme.challenges import HTTP01Response
self.assertEqual(
self.msg, HTTP01Response.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import SimpleHTTP
hash(SimpleHTTP.from_json(self.jmsg))
from acme.challenges import HTTP01Response
hash(HTTP01Response.from_json(self.jmsg))
def test_good_token(self):
self.assertTrue(self.msg.good_token)
self.assertFalse(
self.msg.update(token=b'..').good_token)
class SimpleHTTPResponseTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.challenges import SimpleHTTPResponse
self.msg_http = SimpleHTTPResponse(tls=False)
self.msg_https = SimpleHTTPResponse(tls=True)
self.jmsg_http = {
'resource': 'challenge',
'type': 'simpleHttp',
'tls': False,
}
self.jmsg_https = {
'resource': 'challenge',
'type': 'simpleHttp',
'tls': True,
}
from acme.challenges import SimpleHTTP
self.chall = SimpleHTTP(token=(b"x" * 16))
self.resp_http = SimpleHTTPResponse(tls=False)
self.resp_https = SimpleHTTPResponse(tls=True)
self.good_headers = {'Content-Type': SimpleHTTPResponse.CONTENT_TYPE}
def test_to_partial_json(self):
self.assertEqual(self.jmsg_http, self.msg_http.to_partial_json())
self.assertEqual(self.jmsg_https, self.msg_https.to_partial_json())
def test_from_json(self):
from acme.challenges import SimpleHTTPResponse
self.assertEqual(
self.msg_http, SimpleHTTPResponse.from_json(self.jmsg_http))
self.assertEqual(
self.msg_https, SimpleHTTPResponse.from_json(self.jmsg_https))
def test_from_json_hashable(self):
from acme.challenges import SimpleHTTPResponse
hash(SimpleHTTPResponse.from_json(self.jmsg_http))
hash(SimpleHTTPResponse.from_json(self.jmsg_https))
def test_scheme(self):
self.assertEqual('http', self.msg_http.scheme)
self.assertEqual('https', self.msg_https.scheme)
def test_port(self):
self.assertEqual(80, self.msg_http.port)
self.assertEqual(443, self.msg_https.port)
def test_uri(self):
self.assertEqual(
'http://example.com/.well-known/acme-challenge/'
'eHh4eHh4eHh4eHh4eHh4eA', self.msg_http.uri(
'example.com', self.chall))
self.assertEqual(
'https://example.com/.well-known/acme-challenge/'
'eHh4eHh4eHh4eHh4eHh4eA', self.msg_https.uri(
'example.com', self.chall))
def test_gen_check_validation(self):
account_key = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
self.assertTrue(self.resp_http.check_validation(
validation=self.resp_http.gen_validation(self.chall, account_key),
chall=self.chall, account_public_key=account_key.public_key()))
def test_gen_check_validation_wrong_key(self):
key1 = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem'))
self.assertFalse(self.resp_http.check_validation(
validation=self.resp_http.gen_validation(self.chall, key1),
chall=self.chall, account_public_key=key2.public_key()))
def test_check_validation_wrong_payload(self):
account_key = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
validations = tuple(
jose.JWS.sign(payload=payload, alg=jose.RS256, key=account_key)
for payload in (b'', b'{}', self.chall.json_dumps().encode('utf-8'),
self.resp_http.json_dumps().encode('utf-8'))
)
for validation in validations:
self.assertFalse(self.resp_http.check_validation(
validation=validation, chall=self.chall,
account_public_key=account_key.public_key()))
def test_check_validation_wrong_fields(self):
resource = self.resp_http.gen_resource(self.chall)
account_key = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
validations = tuple(
jose.JWS.sign(payload=bad_resource.json_dumps().encode('utf-8'),
alg=jose.RS256, key=account_key)
for bad_resource in (resource.update(tls=True),
resource.update(token=(b'x' * 20)))
)
for validation in validations:
self.assertFalse(self.resp_http.check_validation(
validation=validation, chall=self.chall,
account_public_key=account_key.public_key()))
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.requests.get")
def test_simple_verify_good_validation(self, mock_get):
account_key = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
for resp in self.resp_http, self.resp_https:
mock_get.reset_mock()
validation = resp.gen_validation(self.chall, account_key)
mock_get.return_value = mock.MagicMock(
text=validation.json_dumps(), headers=self.good_headers)
self.assertTrue(resp.simple_verify(self.chall, "local", None))
mock_get.assert_called_once_with(resp.uri(
"local", self.chall), verify=False)
validation = self.chall.validation(KEY)
mock_get.return_value = mock.MagicMock(
text=validation, headers=self.good_headers)
self.assertTrue(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
mock_get.assert_called_once_with(self.chall.uri("local"))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_bad_validation(self, mock_get):
mock_get.return_value = mock.MagicMock(
text="!", headers=self.good_headers)
self.assertFalse(self.resp_http.simple_verify(
self.chall, "local", None))
self.assertFalse(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_bad_content_type(self, mock_get):
mock_get().text = self.chall.token
self.assertFalse(self.resp_http.simple_verify(
self.chall, "local", None))
self.assertFalse(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_connection_error(self, mock_get):
mock_get.side_effect = requests.exceptions.RequestException
self.assertFalse(self.resp_http.simple_verify(
self.chall, "local", None))
self.assertFalse(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_port(self, mock_get):
self.resp_http.simple_verify(
self.chall, domain="local", account_public_key=None, port=4430)
self.assertEqual("local:4430", urllib_parse.urlparse(
self.response.simple_verify(
self.chall, domain="local",
account_public_key=KEY.public_key(), port=8080)
self.assertEqual("local:8080", urllib_parse.urlparse(
mock_get.mock_calls[0][1][0]).netloc)
class HTTP01Test(unittest.TestCase):
def setUp(self):
from acme.challenges import HTTP01
self.msg = HTTP01(
token=jose.decode_b64jose(
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA'))
self.jmsg = {
'type': 'http-01',
'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA',
}
def test_path(self):
self.assertEqual(self.msg.path, '/.well-known/acme-challenge/'
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')
def test_uri(self):
self.assertEqual(
'http://example.com/.well-known/acme-challenge/'
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA',
self.msg.uri('example.com'))
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import HTTP01
self.assertEqual(self.msg, HTTP01.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import HTTP01
hash(HTTP01.from_json(self.jmsg))
def test_good_token(self):
self.assertTrue(self.msg.good_token)
self.assertFalse(
self.msg.update(token=b'..').good_token)
class DVSNITest(unittest.TestCase):
def setUp(self):
@ -237,18 +215,15 @@ class DVSNITest(unittest.TestCase):
jose.DeserializationError, DVSNI.from_json, self.jmsg)
def test_gen_response(self):
key = jose.JWKRSA(key=KEY)
from acme.challenges import DVSNI
self.assertEqual(self.msg, DVSNI.json_loads(
self.msg.gen_response(key).validation.payload.decode()))
self.msg.gen_response(KEY).validation.payload.decode()))
class DVSNIResponseTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
def setUp(self):
self.key = jose.JWKRSA(key=KEY)
from acme.challenges import DVSNI
self.chall = DVSNI(
token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e'))
@ -256,7 +231,7 @@ class DVSNIResponseTest(unittest.TestCase):
from acme.challenges import DVSNIResponse
self.validation = jose.JWS.sign(
payload=self.chall.json_dumps(sort_keys=True).encode(),
key=self.key, alg=jose.RS256)
key=KEY, alg=jose.RS256)
self.msg = DVSNIResponse(validation=self.validation)
self.jmsg_to = {
'resource': 'challenge',
@ -340,22 +315,22 @@ class DVSNIResponseTest(unittest.TestCase):
def test_simple_verify_wrong_payload(self):
for payload in b'', b'{}':
msg = self.msg.update(validation=jose.JWS.sign(
payload=payload, key=self.key, alg=jose.RS256))
payload=payload, key=KEY, alg=jose.RS256))
self.assertFalse(msg.simple_verify(
self.chall, self.domain, self.key.public_key()))
self.chall, self.domain, KEY.public_key()))
def test_simple_verify_wrong_token(self):
msg = self.msg.update(validation=jose.JWS.sign(
payload=self.chall.update(token=(b'b' * 20)).json_dumps().encode(),
key=self.key, alg=jose.RS256))
key=KEY, alg=jose.RS256))
self.assertFalse(msg.simple_verify(
self.chall, self.domain, self.key.public_key()))
self.chall, self.domain, KEY.public_key()))
@mock.patch('acme.challenges.DVSNIResponse.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.msg.simple_verify(
self.chall, self.domain, self.key.public_key(),
self.chall, self.domain, KEY.public_key(),
cert=mock.sentinel.cert))
mock_verify_cert.assert_called_once_with(self.msg, mock.sentinel.cert)
@ -363,7 +338,7 @@ class DVSNIResponseTest(unittest.TestCase):
def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
mock_probe_cert.side_effect = errors.Error
self.assertFalse(self.msg.simple_verify(
self.chall, self.domain, self.key.public_key()))
self.chall, self.domain, KEY.public_key()))
class RecoveryContactTest(unittest.TestCase):
@ -442,7 +417,7 @@ class RecoveryContactResponseTest(unittest.TestCase):
class ProofOfPossessionHintsTest(unittest.TestCase):
def setUp(self):
jwk = jose.JWKRSA(key=KEY.public_key())
jwk = KEY.public_key()
issuers = (
'C=US, O=SuperT LLC, CN=SuperTrustworthy Public CA',
'O=LessTrustworthy CA Inc, CN=LessTrustworthy But StillSecure',
@ -511,7 +486,7 @@ class ProofOfPossessionTest(unittest.TestCase):
def setUp(self):
from acme.challenges import ProofOfPossession
hints = ProofOfPossession.Hints(
jwk=jose.JWKRSA(key=KEY.public_key()), cert_fingerprints=(),
jwk=KEY.public_key(), cert_fingerprints=(),
certs=(), serial_numbers=(), subject_key_identifiers=(),
issuers=(), authorized_for=())
self.msg = ProofOfPossession(
@ -551,7 +526,7 @@ class ProofOfPossessionResponseTest(unittest.TestCase):
# nonce and challenge nonce are the same, don't make the same
# mistake here...
signature = other.Signature(
alg=jose.RS256, jwk=jose.JWKRSA(key=KEY.public_key()),
alg=jose.RS256, jwk=KEY.public_key(),
sig=b'\xa7\xc1\xe7\xe82o\xbc\xcd\xd0\x1e\x010#Z|\xaf\x15\x83'
b'\x94\x8f#\x9b\nQo(\x80\x15,\x08\xfcz\x1d\xfd\xfd.\xaap'
b'\xfa\x06\xd1\xa2f\x8d8X2>%d\xbd%\xe1T\xdd\xaa0\x18\xde'
@ -659,14 +634,12 @@ class DNSTest(unittest.TestCase):
class DNSResponseTest(unittest.TestCase):
def setUp(self):
self.key = jose.JWKRSA(key=KEY)
from acme.challenges import DNS
self.chall = DNS(token=jose.b64decode(
b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"))
self.validation = jose.JWS.sign(
payload=self.chall.json_dumps(sort_keys=True).encode(),
key=self.key, alg=jose.RS256)
key=KEY, alg=jose.RS256)
from acme.challenges import DNSResponse
self.msg = DNSResponse(validation=self.validation)
@ -694,7 +667,7 @@ class DNSResponseTest(unittest.TestCase):
def test_check_validation(self):
self.assertTrue(
self.msg.check_validation(self.chall, self.key.public_key()))
self.msg.check_validation(self.chall, KEY.public_key()))
if __name__ == '__main__':

View file

@ -47,6 +47,8 @@ class JWK(json_util.TypedJSONObjectWithFields):
https://tools.ietf.org/html/rfc7638
:returns bytes:
"""
digest = hashes.Hash(hash_function(), backend=default_backend())
digest.update(json.dumps(

View file

@ -104,7 +104,7 @@ class Header(json_util.JSONObjectWithFields):
.. todo:: Supports only "jwk" header parameter lookup.
:returns: (Public) key found in the header.
:rtype: :class:`acme.jose.jwk.JWK`
:rtype: .JWK
:raises acme.jose.errors.Error: if key could not be found
@ -194,8 +194,7 @@ class Signature(json_util.JSONObjectWithFields):
def verify(self, payload, key=None):
"""Verify.
:param key: Key used for verification.
:type key: :class:`acme.jose.jwk.JWK`
:param JWK key: Key used for verification.
"""
key = self.combined.find_key() if key is None else key
@ -208,8 +207,7 @@ class Signature(json_util.JSONObjectWithFields):
protect=frozenset(), **kwargs):
"""Sign.
:param key: Key for signature.
:type key: :class:`acme.jose.jwk.JWK`
:param JWK key: Key for signature.
"""
assert isinstance(key, alg.kty)

View file

@ -278,7 +278,7 @@ class AuthorizationTest(unittest.TestCase):
self.challbs = (
ChallengeBody(
uri='http://challb1', status=STATUS_VALID,
chall=challenges.SimpleHTTP(token=b'IlirfxKKXAsHtmzK29Pj8A')),
chall=challenges.HTTP01(token=b'IlirfxKKXAsHtmzK29Pj8A')),
ChallengeBody(uri='http://challb2', status=STATUS_VALID,
chall=challenges.DNS(
token=b'DGyRejmCefe7v4NfDGDKfA')),

View file

@ -58,26 +58,26 @@ class DVSNIServer(TLSServer, ACMEServerMixin):
self, server_address, socketserver.BaseRequestHandler, certs=certs)
class SimpleHTTPServer(BaseHTTPServer.HTTPServer, ACMEServerMixin):
"""SimpleHTTP Server."""
class HTTP01Server(BaseHTTPServer.HTTPServer, ACMEServerMixin):
"""HTTP01 Server."""
def __init__(self, server_address, resources):
BaseHTTPServer.HTTPServer.__init__(
self, server_address, SimpleHTTPRequestHandler.partial_init(
self, server_address, HTTP01RequestHandler.partial_init(
simple_http_resources=resources))
class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""SimpleHTTP challenge handler.
class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""HTTP01 challenge handler.
Adheres to the stdlib's `socketserver.BaseRequestHandler` interface.
:ivar set simple_http_resources: A set of `SimpleHTTPResource`
:ivar set simple_http_resources: A set of `HTTP01Resource`
objects. TODO: better name?
"""
SimpleHTTPResource = collections.namedtuple(
"SimpleHTTPResource", "chall response validation")
HTTP01Resource = collections.namedtuple(
"HTTP01Resource", "chall response validation")
def __init__(self, *args, **kwargs):
self.simple_http_resources = kwargs.pop("simple_http_resources", set())
@ -86,7 +86,7 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self): # pylint: disable=invalid-name,missing-docstring
if self.path == "/":
self.handle_index()
elif self.path.startswith("/" + challenges.SimpleHTTP.URI_ROOT_PATH):
elif self.path.startswith("/" + challenges.HTTP01.URI_ROOT_PATH):
self.handle_simple_http_resource()
else:
self.handle_404()
@ -106,15 +106,15 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.wfile.write(b"404")
def handle_simple_http_resource(self):
"""Handle SimpleHTTP provisioned resources."""
"""Handle HTTP01 provisioned resources."""
for resource in self.simple_http_resources:
if resource.chall.path == self.path:
logger.debug("Serving SimpleHTTP with token %r",
logger.debug("Serving HTTP01 with token %r",
resource.chall.encode("token"))
self.send_response(http_client.OK)
self.send_header("Content-type", resource.response.CONTENT_TYPE)
self.send_header("Content-type", resource.chall.CONTENT_TYPE)
self.end_headers()
self.wfile.write(resource.validation.json_dumps().encode())
self.wfile.write(resource.validation.encode())
return
else: # pylint: disable=useless-else-on-loop
logger.debug("No resources to serve")

View file

@ -54,16 +54,16 @@ class DVSNIServerTest(unittest.TestCase):
jose.ComparableX509(self.certs[b'localhost'][1]))
class SimpleHTTPServerTest(unittest.TestCase):
"""Tests for acme.standalone.SimpleHTTPServer."""
class HTTP01ServerTest(unittest.TestCase):
"""Tests for acme.standalone.HTTP01Server."""
def setUp(self):
self.account_key = jose.JWK.load(
test_util.load_vector('rsa1024_key.pem'))
self.resources = set()
from acme.standalone import SimpleHTTPServer
self.server = SimpleHTTPServer(('', 0), resources=self.resources)
from acme.standalone import HTTP01Server
self.server = HTTP01Server(('', 0), resources=self.resources)
# pylint: disable=no-member
self.port = self.server.socket.getsockname()[1]
@ -86,25 +86,24 @@ class SimpleHTTPServerTest(unittest.TestCase):
'http://localhost:{0}/foo'.format(self.port), verify=False)
self.assertEqual(response.status_code, http_client.NOT_FOUND)
def _test_simple_http(self, add):
chall = challenges.SimpleHTTP(token=(b'x' * 16))
response = challenges.SimpleHTTPResponse(tls=False)
def _test_http01(self, add):
chall = challenges.HTTP01(token=(b'x' * 16))
response, validation = chall.response_and_validation(self.account_key)
from acme.standalone import SimpleHTTPRequestHandler
resource = SimpleHTTPRequestHandler.SimpleHTTPResource(
chall=chall, response=response, validation=response.gen_validation(
chall, self.account_key))
from acme.standalone import HTTP01RequestHandler
resource = HTTP01RequestHandler.HTTP01Resource(
chall=chall, response=response, validation=validation)
if add:
self.resources.add(resource)
return resource.response.simple_verify(
resource.chall, 'localhost', self.account_key.public_key(),
port=self.port)
def test_simple_http_found(self):
self.assertTrue(self._test_simple_http(add=True))
def test_http01_found(self):
self.assertTrue(self._test_http01(add=True))
def test_simple_http_not_found(self):
self.assertFalse(self._test_simple_http(add=False))
def test_http01_not_found(self):
self.assertFalse(self._test_http01(add=False))
class TestSimpleDVSNIServer(unittest.TestCase):

View file

@ -152,7 +152,7 @@ the ACME server. From the protocol, there are essentially two
different types of challenges. Challenges that must be solved by
individual plugins in order to satisfy domain validation (subclasses
of `~.DVChallenge`, i.e. `~.challenges.DVSNI`,
`~.challenges.SimpleHTTPS`, `~.challenges.DNS`) and continuity specific
`~.challenges.HTTP01`, `~.challenges.DNS`) and continuity specific
challenges (subclasses of `~.ContinuityChallenge`,
i.e. `~.challenges.RecoveryToken`, `~.challenges.RecoveryContact`,
`~.challenges.ProofOfPossession`). Continuity challenges are

View file

@ -127,7 +127,7 @@ Officially supported plugins:
Plugin A I Notes and status
========== = = ================================================================
standalone Y N Very stable. Uses port 80 (force by
``--standalone-supported-challenges simpleHttp``) or 443
``--standalone-supported-challenges http-01``) or 443
(force by ``--standalone-supported-challenges dvsni``).
apache Y Y Alpha. Automates Apache installation, works fairly well but on
Debian-based distributions only for now.

View file

@ -20,7 +20,7 @@ class ApacheDvsni(common.Dvsni):
larger array. ApacheDvsni is capable of solving many challenges
at once which causes an indexing issue within ApacheConfigurator
who must return all responses in order. Imagine ApacheConfigurator
maintaining state about where all of the SimpleHTTP Challenges,
maintaining state about where all of the http-01 Challenges,
Dvsni Challenges belong in the response array. This is an optional
utility.

View file

@ -26,7 +26,7 @@ class NginxDvsni(common.Dvsni):
larger array. NginxDvsni is capable of solving many challenges
at once which causes an indexing issue within NginxConfigurator
who must return all responses in order. Imagine NginxConfigurator
maintaining state about where all of the SimpleHTTP Challenges,
maintaining state about where all of the http-01 Challenges,
Dvsni Challenges belong in the response array. This is an optional
utility.

View file

@ -45,6 +45,15 @@ class AnnotatedChallenge(jose.ImmutableMap):
return getattr(self.challb, name)
class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge):
"""Client annotated `KeyAuthorizationChallenge` challenge."""
__slots__ = ('challb', 'domain', 'account_key')
def response_and_validation(self):
"""Generate response and validation."""
return self.challb.chall.response_and_validation(self.account_key)
class DVSNI(AnnotatedChallenge):
"""Client annotated "dvsni" ACME challenge.
@ -76,31 +85,6 @@ class DVSNI(AnnotatedChallenge):
return response, cert, key
class SimpleHTTP(AnnotatedChallenge):
"""Client annotated "simpleHttp" ACME challenge."""
__slots__ = ('challb', 'domain', 'account_key')
acme_type = challenges.SimpleHTTP
def gen_response_and_validation(self, tls):
"""Generates a SimpleHTTP response and validation.
:param bool tls: True if TLS should be used
:returns: ``(response, validation)`` tuple, where ``response`` is
an instance of `acme.challenges.SimpleHTTPResponse` and
``validation`` is an instance of
`acme.challenges.SimpleHTTPProvisionedResource`.
:rtype: tuple
"""
response = challenges.SimpleHTTPResponse(tls=tls)
validation = response.gen_validation(
self.challb.chall, self.account_key)
logger.debug("Simple HTTP validation payload: %s", validation.payload)
return response, validation
class DNS(AnnotatedChallenge):
"""Client annotated "dns" ACME challenge."""
__slots__ = ('challb', 'domain')

View file

@ -347,9 +347,6 @@ def challb_to_achall(challb, account_key, domain):
if isinstance(chall, challenges.DVSNI):
return achallenges.DVSNI(
challb=challb, domain=domain, account_key=account_key)
elif isinstance(chall, challenges.SimpleHTTP):
return achallenges.SimpleHTTP(
challb=challb, domain=domain, account_key=account_key)
elif isinstance(chall, challenges.DNS):
return achallenges.DNS(challb=challb, domain=domain)
elif isinstance(chall, challenges.RecoveryContact):
@ -358,7 +355,9 @@ def challb_to_achall(challb, account_key, domain):
elif isinstance(chall, challenges.ProofOfPossession):
return achallenges.ProofOfPossession(
challb=challb, domain=domain)
elif isinstance(chall, challenges.KeyAuthorizationChallenge):
return achallenges.KeyAuthorizationAnnotatedChallenge(
challb=challb, domain=domain, account_key=account_key)
else:
raise errors.Error(
"Received unsupported challenge of type: %s", chall.typ)

View file

@ -854,8 +854,8 @@ def prepare_and_parse_args(plugins, args):
helpful.add(
"testing", "--dvsni-port", type=int, default=flag_default("dvsni_port"),
help=config_help("dvsni_port"))
helpful.add("testing", "--simple-http-port", type=int,
help=config_help("simple_http_port"))
helpful.add("testing", "--http-01-port", dest="http01_port", type=int,
help=config_help("http01_port"))
helpful.add_group(
"security", description="Security parameters & server settings")

View file

@ -37,9 +37,9 @@ class NamespaceConfig(object):
def __init__(self, namespace):
self.namespace = namespace
if self.simple_http_port == self.dvsni_port:
if self.http01_port == self.dvsni_port:
raise errors.Error(
"Trying to run SimpleHTTP and DVSNI "
"Trying to run http-01 and DVSNI "
"on the same port ({0})".format(self.dvsni_port))
def __getattr__(self, name):
@ -78,11 +78,11 @@ class NamespaceConfig(object):
self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR)
@property
def simple_http_port(self): # pylint: disable=missing-docstring
if self.namespace.simple_http_port is not None:
return self.namespace.simple_http_port
def http01_port(self): # pylint: disable=missing-docstring
if self.namespace.http01_port is not None:
return self.namespace.http01_port
else:
return challenges.SimpleHTTPResponse.PORT
return challenges.HTTP01Response.PORT
class RenewerConfiguration(object):

View file

@ -41,7 +41,7 @@ RENEWER_DEFAULTS = dict(
EXCLUSIVE_CHALLENGES = frozenset([frozenset([
challenges.DVSNI, challenges.SimpleHTTP])])
challenges.DVSNI, challenges.HTTP01])])
"""Mutually exclusive challenges."""

View file

@ -223,7 +223,7 @@ class IConfig(zope.interface.Interface):
"Port number to perform DVSNI challenge. "
"Boulder in testing mode defaults to 5001.")
simple_http_port = zope.interface.Attribute(
http01_port = zope.interface.Attribute(
"Port used in the SimpleHttp challenge.")

View file

@ -27,9 +27,9 @@ class Authenticator(common.Plugin):
"""Manual Authenticator.
This plugin requires user's manual intervention in setting up a HTTP
server for solving SimpleHTTP challenges and thus does not need to
be run as a privileged process. Alternatively shows instructions on
how to use Python's built-in HTTP server.
server for solving http-01 challenges and thus does not need to be
run as a privileged process. Alternatively shows instructions on how
to use Python's built-in HTTP server.
.. todo:: Support for `~.challenges.DVSNI`.
@ -69,9 +69,9 @@ Are you OK with your IP being logged?
# anything recursively under the cwd
CMD_TEMPLATE = """\
mkdir -p {root}/public_html/{response.URI_ROOT_PATH}
mkdir -p {root}/public_html/{achall.URI_ROOT_PATH}
cd {root}/public_html
printf "%s" {validation} > {response.URI_ROOT_PATH}/{encoded_token}
printf "%s" {validation} > {achall.URI_ROOT_PATH}/{encoded_token}
# run only once per server:
$(command -v python2 || command -v python2.7 || command -v python2.6) -c \\
"import BaseHTTPServer, SimpleHTTPServer; \\
@ -96,14 +96,14 @@ s.serve_forever()" """
def more_info(self): # pylint: disable=missing-docstring,no-self-use
return ("This plugin requires user's manual intervention in setting "
"up an HTTP server for solving SimpleHTTP challenges and thus "
"up an HTTP server for solving http-01 challenges and thus "
"does not need to be run as a privileged process. "
"Alternatively shows instructions on how to use Python's "
"built-in HTTP server.")
def get_chall_pref(self, domain):
# pylint: disable=missing-docstring,no-self-use,unused-argument
return [challenges.SimpleHTTP]
return [challenges.HTTP01]
def perform(self, achalls): # pylint: disable=missing-docstring
responses = []
@ -131,16 +131,16 @@ s.serve_forever()" """
# same path for each challenge response would be easier for
# users, but will not work if multiple domains point at the
# same server: default command doesn't support virtual hosts
response, validation = achall.gen_response_and_validation(
tls=False) # SimpleHTTP TLS is dead: ietf-wg-acme/acme#7
response, validation = achall.response_and_validation()
port = (response.port if self.config.simple_http_port is None
else int(self.config.simple_http_port))
port = (response.port if self.config.http01_port is None
else int(self.config.http01_port))
command = self.CMD_TEMPLATE.format(
root=self._root, achall=achall, response=response,
validation=pipes.quote(validation.json_dumps()),
# TODO(kuba): pipes still necessary?
validation=pipes.quote(validation),
encoded_token=achall.chall.encode("token"),
ct=response.CONTENT_TYPE, port=port)
ct=achall.CONTENT_TYPE, port=port)
if self.conf("test-mode"):
logger.debug("Test mode. Executing the manual command: %s", command)
# sh shipped with OS X does't support echo -n, but supports printf
@ -169,13 +169,13 @@ s.serve_forever()" """
raise errors.PluginError("Must agree to IP logging to proceed")
self._notify_and_wait(self.MESSAGE_TEMPLATE.format(
validation=validation.json_dumps(), response=response,
uri=response.uri(achall.domain, achall.challb.chall),
ct=response.CONTENT_TYPE, command=command))
validation=validation, response=response,
uri=achall.chall.uri(achall.domain),
ct=achall.CONTENT_TYPE, command=command))
if response.simple_verify(
achall.chall, achall.domain,
achall.account_key.public_key(), self.config.simple_http_port):
achall.account_key.public_key(), self.config.http01_port):
return response
else:
logger.error(

View file

@ -23,13 +23,13 @@ class AuthenticatorTest(unittest.TestCase):
def setUp(self):
from letsencrypt.plugins.manual import Authenticator
self.config = mock.MagicMock(
simple_http_port=8080, manual_test_mode=False)
http01_port=8080, manual_test_mode=False)
self.auth = Authenticator(config=self.config, name="manual")
self.achalls = [achallenges.SimpleHTTP(
challb=acme_util.SIMPLE_HTTP_P, domain="foo.com", account_key=KEY)]
self.achalls = [achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.HTTP01_P, domain="foo.com", account_key=KEY)]
config_test_mode = mock.MagicMock(
simple_http_port=8080, manual_test_mode=True)
http01_port=8080, manual_test_mode=True)
self.auth_test_mode = Authenticator(
config=config_test_mode, name="manual")
@ -45,13 +45,13 @@ class AuthenticatorTest(unittest.TestCase):
@mock.patch("letsencrypt.plugins.manual.zope.component.getUtility")
@mock.patch("letsencrypt.plugins.manual.sys.stdout")
@mock.patch("acme.challenges.SimpleHTTPResponse.simple_verify")
@mock.patch("acme.challenges.HTTP01Response.simple_verify")
@mock.patch("__builtin__.raw_input")
def test_perform(self, mock_raw_input, mock_verify, mock_stdout, mock_interaction):
mock_verify.return_value = True
mock_interaction().yesno.return_value = True
resp = challenges.SimpleHTTPResponse(tls=False)
resp = self.achalls[0].response(KEY)
self.assertEqual([resp], self.auth.perform(self.achalls))
self.assertEqual(1, mock_raw_input.call_count)
mock_verify.assert_called_with(
@ -89,7 +89,7 @@ class AuthenticatorTest(unittest.TestCase):
@mock.patch("letsencrypt.plugins.manual.socket.socket")
@mock.patch("letsencrypt.plugins.manual.time.sleep", autospec=True)
@mock.patch("acme.challenges.SimpleHTTPResponse.simple_verify",
@mock.patch("acme.challenges.HTTP01Response.simple_verify",
autospec=True)
@mock.patch("letsencrypt.plugins.manual.subprocess.Popen", autospec=True)
def test_perform_test_mode(self, mock_popen, mock_verify, mock_sleep,

View file

@ -14,7 +14,6 @@ from acme import challenges
from acme import crypto_util as acme_crypto_util
from acme import standalone as acme_standalone
from letsencrypt import achallenges
from letsencrypt import errors
from letsencrypt import interfaces
@ -33,7 +32,7 @@ class ServerManager(object):
`acme.crypto_util.SSLSocket.certs` and
`acme.crypto_util.SSLSocket.simple_http_resources` respectively. All
created servers share the same certificates and resources, so if
you're running both TLS and non-TLS instances, SimpleHTTP handlers
you're running both TLS and non-TLS instances, HTTP01 handlers
will serve the same URLs!
"""
@ -52,13 +51,13 @@ class ServerManager(object):
:param int port: Port to run the server on.
:param challenge_type: Subclass of `acme.challenges.Challenge`,
either `acme.challenge.SimpleHTTP` or `acme.challenges.DVSNI`.
either `acme.challenge.HTTP01` or `acme.challenges.DVSNI`.
:returns: Server instance.
:rtype: ACMEServerMixin
"""
assert challenge_type in (challenges.DVSNI, challenges.SimpleHTTP)
assert challenge_type in (challenges.DVSNI, challenges.HTTP01)
if port in self._instances:
return self._instances[port].server
@ -66,8 +65,8 @@ class ServerManager(object):
try:
if challenge_type is challenges.DVSNI:
server = acme_standalone.DVSNIServer(address, self.certs)
else: # challenges.SimpleHTTP
server = acme_standalone.SimpleHTTPServer(
else: # challenges.HTTP01
server = acme_standalone.HTTP01Server(
address, self.simple_http_resources)
except socket.error as error:
raise errors.StandaloneBindError(error, port)
@ -110,7 +109,7 @@ class ServerManager(object):
in six.iteritems(self._instances))
SUPPORTED_CHALLENGES = set([challenges.DVSNI, challenges.SimpleHTTP])
SUPPORTED_CHALLENGES = set([challenges.DVSNI, challenges.HTTP01])
def supported_challenges_validator(data):
@ -139,7 +138,7 @@ class Authenticator(common.Plugin):
"""Standalone Authenticator.
This authenticator creates its own ephemeral TCP listener on the
necessary port in order to respond to incoming DVSNI and SimpleHTTP
necessary port in order to respond to incoming DVSNI and HTTP01
challenges from the certificate authority. Therefore, it does not
rely on any existing server program.
"""
@ -151,10 +150,10 @@ class Authenticator(common.Plugin):
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
# one self-signed key for all DVSNI and SimpleHTTP certificates
# one self-signed key for all DVSNI and HTTP01 certificates
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, bits=2048)
# TODO: generate only when the first SimpleHTTP challenge is solved
# TODO: generate only when the first HTTP01 challenge is solved
self.simple_http_cert = acme_crypto_util.gen_ss_cert(
self.key, domains=["temp server"])
@ -185,17 +184,17 @@ class Authenticator(common.Plugin):
@property
def _necessary_ports(self):
necessary_ports = set()
if challenges.SimpleHTTP in self.supported_challenges:
necessary_ports.add(self.config.simple_http_port)
if challenges.HTTP01 in self.supported_challenges:
necessary_ports.add(self.config.http01_port)
if challenges.DVSNI in self.supported_challenges:
necessary_ports.add(self.config.dvsni_port)
return necessary_ports
def more_info(self): # pylint: disable=missing-docstring
return("This authenticator creates its own ephemeral TCP listener "
"on the necessary port in order to respond to incoming DVSNI "
"and SimpleHTTP challenges from the certificate authority. "
"Therefore, it does not rely on any existing server program.")
"on the necessary port in order to respond to incoming DVSNI "
"and HTTP01 challenges from the certificate authority. "
"Therefore, it does not rely on any existing server program.")
def prepare(self): # pylint: disable=missing-docstring
pass
@ -237,13 +236,12 @@ class Authenticator(common.Plugin):
responses = []
for achall in achalls:
if isinstance(achall, achallenges.SimpleHTTP):
if isinstance(achall.chall, challenges.HTTP01):
server = self.servers.run(
self.config.simple_http_port, challenges.SimpleHTTP)
response, validation = achall.gen_response_and_validation(
tls=False)
self.config.http01_port, challenges.HTTP01)
response, validation = achall.response_and_validation()
self.simple_http_resources.add(
acme_standalone.SimpleHTTPRequestHandler.SimpleHTTPResource(
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
chall=achall.chall, response=response,
validation=validation))
cert = self.simple_http_cert

View file

@ -43,12 +43,12 @@ class ServerManagerTest(unittest.TestCase):
self._test_run_stop(challenges.DVSNI)
def test_run_stop_simplehttp(self):
self._test_run_stop(challenges.SimpleHTTP)
self._test_run_stop(challenges.HTTP01)
def test_run_idempotent(self):
server = self.mgr.run(port=0, challenge_type=challenges.SimpleHTTP)
server = self.mgr.run(port=0, challenge_type=challenges.HTTP01)
port = server.socket.getsockname()[1] # pylint: disable=no-member
server2 = self.mgr.run(port=port, challenge_type=challenges.SimpleHTTP)
server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01)
self.assertEqual(self.mgr.running(), {port: server})
self.assertTrue(server is server2)
self.mgr.stop(port)
@ -60,7 +60,7 @@ class ServerManagerTest(unittest.TestCase):
port = some_server.getsockname()[1]
self.assertRaises(
errors.StandaloneBindError, self.mgr.run, port,
challenge_type=challenges.SimpleHTTP)
challenge_type=challenges.HTTP01)
self.assertEqual(self.mgr.running(), {})
@ -74,9 +74,9 @@ class SupportedChallengesValidatorTest(unittest.TestCase):
def test_correct(self):
self.assertEqual("dvsni", self._call("dvsni"))
self.assertEqual("simpleHttp", self._call("simpleHttp"))
self.assertEqual("dvsni,simpleHttp", self._call("dvsni,simpleHttp"))
self.assertEqual("simpleHttp,dvsni", self._call("simpleHttp,dvsni"))
self.assertEqual("http-01", self._call("http-01"))
self.assertEqual("dvsni,http-01", self._call("dvsni,http-01"))
self.assertEqual("http-01,dvsni", self._call("http-01,dvsni"))
def test_unrecognized(self):
assert "foo" not in challenges.Challenge.TYPES
@ -91,25 +91,26 @@ class AuthenticatorTest(unittest.TestCase):
def setUp(self):
from letsencrypt.plugins.standalone import Authenticator
self.config = mock.MagicMock(dvsni_port=1234, simple_http_port=4321,
standalone_supported_challenges="dvsni,simpleHttp")
self.config = mock.MagicMock(
dvsni_port=1234, http01_port=4321,
standalone_supported_challenges="dvsni,http-01")
self.auth = Authenticator(self.config, name="standalone")
def test_supported_challenges(self):
self.assertEqual(self.auth.supported_challenges,
set([challenges.DVSNI, challenges.SimpleHTTP]))
set([challenges.DVSNI, challenges.HTTP01]))
def test_more_info(self):
self.assertTrue(isinstance(self.auth.more_info(), six.string_types))
def test_get_chall_pref(self):
self.assertEqual(set(self.auth.get_chall_pref(domain=None)),
set([challenges.DVSNI, challenges.SimpleHTTP]))
set([challenges.DVSNI, challenges.HTTP01]))
@mock.patch("letsencrypt.plugins.standalone.util")
def test_perform_alredy_listening(self, mock_util):
for chall, port in ((challenges.DVSNI.typ, 1234),
(challenges.SimpleHTTP.typ, 4321)):
(challenges.HTTP01.typ, 4321)):
mock_util.already_listening.return_value = True
self.config.standalone_supported_challenges = chall
self.assertRaises(
@ -152,8 +153,8 @@ class AuthenticatorTest(unittest.TestCase):
def test_perform2(self):
domain = b'localhost'
key = jose.JWK.load(test_util.load_vector('rsa512_key.pem'))
simple_http = achallenges.SimpleHTTP(
challb=acme_util.SIMPLE_HTTP_P, domain=domain, account_key=key)
simple_http = achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.HTTP01_P, domain=domain, account_key=key)
dvsni = achallenges.DVSNI(
challb=acme_util.DVSNI_P, domain=domain, account_key=key)
@ -167,11 +168,11 @@ class AuthenticatorTest(unittest.TestCase):
self.assertTrue(isinstance(responses, list))
self.assertEqual(2, len(responses))
self.assertTrue(isinstance(responses[0], challenges.SimpleHTTPResponse))
self.assertTrue(isinstance(responses[0], challenges.HTTP01Response))
self.assertTrue(isinstance(responses[1], challenges.DVSNIResponse))
self.assertEqual(self.auth.servers.run.mock_calls, [
mock.call(4321, challenges.SimpleHTTP),
mock.call(4321, challenges.HTTP01),
mock.call(1234, challenges.DVSNI),
])
self.assertEqual(self.auth.served, {
@ -181,8 +182,8 @@ class AuthenticatorTest(unittest.TestCase):
self.assertEqual(1, len(self.auth.simple_http_resources))
self.assertEqual(2, len(self.auth.certs))
self.assertEqual(list(self.auth.simple_http_resources), [
acme_standalone.SimpleHTTPRequestHandler.SimpleHTTPResource(
acme_util.SIMPLE_HTTP, responses[0], mock.ANY)])
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
acme_util.HTTP01, responses[0], mock.ANY)])
def test_cleanup(self):
self.auth.servers = mock.Mock()

View file

@ -62,7 +62,7 @@ class Authenticator(common.Plugin):
description = "Webroot Authenticator"
MORE_INFO = """\
Authenticator plugin that performs SimpleHTTP challenge by saving
Authenticator plugin that performs http-01 challenge by saving
necessary validation resources to appropriate paths on the file
system. It expects that there is some other HTTP server configured
to serve all files under specified web root ({0})."""
@ -76,7 +76,7 @@ to serve all files under specified web root ({0})."""
def get_chall_pref(self, domain): # pragma: no cover
# pylint: disable=missing-docstring,no-self-use,unused-argument
return [challenges.SimpleHTTP]
return [challenges.HTTP01]
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
@ -90,8 +90,7 @@ to serve all files under specified web root ({0})."""
if not os.path.isdir(path):
raise errors.PluginError(
path + " does not exist or is not a directory")
self.full_root = os.path.join(
path, challenges.SimpleHTTPResponse.URI_ROOT_PATH)
self.full_root = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)
logger.debug("Creating root challenges validation dir at %s",
self.full_root)
@ -100,7 +99,7 @@ to serve all files under specified web root ({0})."""
except OSError as exception:
if exception.errno != errno.EEXIST:
raise errors.PluginError(
"Couldn't create root for SimpleHTTP "
"Couldn't create root for http-01 "
"challenge responses: {0}", exception)
def perform(self, achalls): # pylint: disable=missing-docstring
@ -111,11 +110,11 @@ to serve all files under specified web root ({0})."""
return os.path.join(self.full_root, achall.chall.encode("token"))
def _perform_single(self, achall):
response, validation = achall.gen_response_and_validation(tls=False)
response, validation = achall.response_and_validation()
path = self._path_for_achall(achall)
logger.debug("Attempting to save validation to %s", path)
with open(path, "w") as validation_file:
validation_file.write(validation.json_dumps())
validation_file.write(validation.encode())
return response
def cleanup(self, achalls): # pylint: disable=missing-docstring

View file

@ -6,6 +6,7 @@ import unittest
import mock
from acme import challenges
from acme import jose
from letsencrypt import achallenges
@ -21,8 +22,8 @@ KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem"))
class AuthenticatorTest(unittest.TestCase):
"""Tests for letsencrypt.plugins.webroot.Authenticator."""
achall = achallenges.SimpleHTTP(
challb=acme_util.SIMPLE_HTTP_P, domain=None, account_key=KEY)
achall = achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.HTTP01_P, domain=None, account_key=KEY)
def setUp(self):
from letsencrypt.plugins.webroot import Authenticator
@ -70,9 +71,11 @@ class AuthenticatorTest(unittest.TestCase):
self.assertEqual(1, len(responses))
self.assertTrue(os.path.exists(self.validation_path))
with open(self.validation_path) as validation_f:
validation = jose.JWS.json_loads(validation_f.read())
self.assertTrue(responses[0].check_validation(
validation, self.achall.chall, KEY.public_key()))
validation = validation_f.read()
self.assertTrue(
challenges.KeyAuthorizationChallengeResponse(
key_authorization=validation).verify(
self.achall.chall, KEY.public_key()))
self.auth.cleanup([self.achall])
self.assertFalse(os.path.exists(self.validation_path))

View file

@ -76,7 +76,7 @@ def renew(cert, old_version):
# was an int, not a str)
config.rsa_key_size = int(config.rsa_key_size)
config.dvsni_port = int(config.dvsni_port)
config.namespace.simple_http_port = int(config.namespace.simple_http_port)
config.namespace.http01_port = int(config.namespace.http01_port)
zope.component.provideUtility(config)
try:
authenticator = plugins[renewalparams["authenticator"]]

View file

@ -12,7 +12,7 @@ from letsencrypt.tests import test_util
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
# Challenges
SIMPLE_HTTP = challenges.SimpleHTTP(
HTTP01 = challenges.HTTP01(
token="evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA")
DVSNI = challenges.DVSNI(
token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA"))
@ -41,7 +41,7 @@ POP = challenges.ProofOfPossession(
)
)
CHALLENGES = [SIMPLE_HTTP, DVSNI, DNS, RECOVERY_CONTACT, POP]
CHALLENGES = [HTTP01, DVSNI, DNS, RECOVERY_CONTACT, POP]
DV_CHALLENGES = [chall for chall in CHALLENGES
if isinstance(chall, challenges.DVChallenge)]
CONT_CHALLENGES = [chall for chall in CHALLENGES
@ -80,12 +80,12 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name
# Pending ChallengeBody objects
DVSNI_P = chall_to_challb(DVSNI, messages.STATUS_PENDING)
SIMPLE_HTTP_P = chall_to_challb(SIMPLE_HTTP, messages.STATUS_PENDING)
HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING)
DNS_P = chall_to_challb(DNS, messages.STATUS_PENDING)
RECOVERY_CONTACT_P = chall_to_challb(RECOVERY_CONTACT, messages.STATUS_PENDING)
POP_P = chall_to_challb(POP, messages.STATUS_PENDING)
CHALLENGES_P = [SIMPLE_HTTP_P, DVSNI_P, DNS_P, RECOVERY_CONTACT_P, POP_P]
CHALLENGES_P = [HTTP01_P, DVSNI_P, DNS_P, RECOVERY_CONTACT_P, POP_P]
DV_CHALLENGES_P = [challb for challb in CHALLENGES_P
if isinstance(challb.chall, challenges.DVChallenge)]
CONT_CHALLENGES_P = [

View file

@ -9,21 +9,13 @@ from acme import challenges
from acme import client as acme_client
from acme import messages
from letsencrypt import achallenges
from letsencrypt import errors
from letsencrypt import le_util
from letsencrypt.tests import acme_util
TRANSLATE = {
"dvsni": "DVSNI",
"simpleHttp": "SimpleHTTP",
"dns": "DNS",
"recoveryContact": "RecoveryContact",
"proofOfPossession": "ProofOfPossession",
}
class ChallengeFactoryTest(unittest.TestCase):
# pylint: disable=protected-access
@ -283,6 +275,22 @@ class PollChallengesTest(unittest.TestCase):
return (new_authzr, "response")
class ChallbToAchallTest(unittest.TestCase):
"""Tests for letsencrypt.auth_handler.challb_to_achall."""
def _call(self, challb):
from letsencrypt.auth_handler import challb_to_achall
return challb_to_achall(challb, "account_key", "domain")
def test_it(self):
self.assertEqual(
self._call(acme_util.HTTP01_P),
achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.HTTP01_P, account_key="account_key",
domain="domain"),
)
class GenChallengePathTest(unittest.TestCase):
"""Tests for letsencrypt.auth_handler.gen_challenge_path.
@ -301,8 +309,8 @@ class GenChallengePathTest(unittest.TestCase):
return gen_challenge_path(challbs, preferences, combinations)
def test_common_case(self):
"""Given DVSNI and SimpleHTTP with appropriate combos."""
challbs = (acme_util.DVSNI_P, acme_util.SIMPLE_HTTP_P)
"""Given DVSNI and HTTP01 with appropriate combos."""
challbs = (acme_util.DVSNI_P, acme_util.HTTP01_P)
prefs = [challenges.DVSNI]
combos = ((0,), (1,))
@ -317,7 +325,7 @@ class GenChallengePathTest(unittest.TestCase):
challbs = (acme_util.POP_P,
acme_util.RECOVERY_CONTACT_P,
acme_util.DVSNI_P,
acme_util.SIMPLE_HTTP_P)
acme_util.HTTP01_P)
prefs = [challenges.ProofOfPossession, challenges.DVSNI]
combos = acme_util.gen_combos(challbs)
self.assertEqual(self._call(challbs, prefs, combos), (0, 2))
@ -329,12 +337,12 @@ class GenChallengePathTest(unittest.TestCase):
challbs = (acme_util.RECOVERY_CONTACT_P,
acme_util.POP_P,
acme_util.DVSNI_P,
acme_util.SIMPLE_HTTP_P,
acme_util.HTTP01_P,
acme_util.DNS_P)
# Typical webserver client that can do everything except DNS
# Attempted to make the order realistic
prefs = [challenges.ProofOfPossession,
challenges.SimpleHTTP,
challenges.HTTP01,
challenges.DVSNI,
challenges.RecoveryContact]
combos = acme_util.gen_combos(challbs)
@ -403,8 +411,8 @@ class IsPreferredTest(unittest.TestCase):
def _call(cls, chall, satisfied):
from letsencrypt.auth_handler import is_preferred
return is_preferred(chall, satisfied, exclusive_groups=frozenset([
frozenset([challenges.DVSNI, challenges.SimpleHTTP]),
frozenset([challenges.DNS, challenges.SimpleHTTP]),
frozenset([challenges.DVSNI, challenges.HTTP01]),
frozenset([challenges.DNS, challenges.HTTP01]),
]))
def test_empty_satisfied(self):
@ -413,7 +421,7 @@ class IsPreferredTest(unittest.TestCase):
def test_mutually_exclusvie(self):
self.assertFalse(
self._call(
acme_util.DVSNI_P, frozenset([acme_util.SIMPLE_HTTP_P])))
acme_util.DVSNI_P, frozenset([acme_util.HTTP01_P])))
def test_mutually_exclusive_same_type(self):
self.assertTrue(
@ -425,16 +433,14 @@ class ReportFailedChallsTest(unittest.TestCase):
# pylint: disable=protected-access
def setUp(self):
from letsencrypt import achallenges
kwargs = {
"chall": acme_util.SIMPLE_HTTP,
"chall": acme_util.HTTP01,
"uri": "uri",
"status": messages.STATUS_INVALID,
"error": messages.Error(typ="tls", detail="detail"),
}
self.simple_http = achallenges.SimpleHTTP(
self.http01 = achallenges.KeyAuthorizationAnnotatedChallenge(
# pylint: disable=star-args
challb=messages.ChallengeBody(**kwargs),
domain="example.com",
@ -458,7 +464,7 @@ class ReportFailedChallsTest(unittest.TestCase):
def test_same_error_and_domain(self, mock_zope):
from letsencrypt import auth_handler
auth_handler._report_failed_challs([self.simple_http, self.dvsni_same])
auth_handler._report_failed_challs([self.http01, self.dvsni_same])
call_list = mock_zope().add_message.call_args_list
self.assertTrue(len(call_list) == 1)
self.assertTrue("Domains: example.com\n" in call_list[0][0][0])
@ -467,7 +473,7 @@ class ReportFailedChallsTest(unittest.TestCase):
def test_different_errors_and_domains(self, mock_zope):
from letsencrypt import auth_handler
auth_handler._report_failed_challs([self.simple_http, self.dvsni_diff])
auth_handler._report_failed_challs([self.http01, self.dvsni_diff])
self.assertTrue(mock_zope().add_message.call_count == 2)

View file

@ -14,7 +14,7 @@ class NamespaceConfigTest(unittest.TestCase):
self.namespace = mock.MagicMock(
config_dir='/tmp/config', work_dir='/tmp/foo', foo='bar',
server='https://acme-server.org:443/new',
dvsni_port=1234, simple_http_port=4321)
dvsni_port=1234, http01_port=4321)
from letsencrypt.configuration import NamespaceConfig
self.config = NamespaceConfig(self.namespace)
@ -54,10 +54,10 @@ class NamespaceConfigTest(unittest.TestCase):
self.assertEqual(self.config.key_dir, '/tmp/config/keys')
self.assertEqual(self.config.temp_checkpoint_dir, '/tmp/foo/t')
def test_simple_http_port(self):
self.assertEqual(4321, self.config.simple_http_port)
self.namespace.simple_http_port = None
self.assertEqual(80, self.config.simple_http_port)
def test_http01_port(self):
self.assertEqual(4321, self.config.http01_port)
self.namespace.http01_port = None
self.assertEqual(80, self.config.http01_port)
class RenewerConfigurationTest(unittest.TestCase):

View file

@ -689,7 +689,7 @@ class RenewableCertTests(BaseRenewableCertTest):
self.test_rc.configfile["renewalparams"]["server"] = "acme.example.com"
self.test_rc.configfile["renewalparams"]["authenticator"] = "fake"
self.test_rc.configfile["renewalparams"]["dvsni_port"] = "4430"
self.test_rc.configfile["renewalparams"]["simple_http_port"] = "1234"
self.test_rc.configfile["renewalparams"]["http01_port"] = "1234"
self.test_rc.configfile["renewalparams"]["account"] = "abcde"
mock_auth = mock.MagicMock()
mock_pd.PluginsRegistry.find_all.return_value = {"apache": mock_auth}

View file

@ -28,7 +28,7 @@ common() {
}
common --domains le1.wtf --standalone-supported-challenges dvsni auth
common --domains le2.wtf --standalone-supported-challenges simpleHttp run
common --domains le2.wtf --standalone-supported-challenges http-01 run
common -a manual -d le.wtf auth
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \

View file

@ -16,7 +16,7 @@ letsencrypt_test () {
--server "${SERVER:-http://localhost:4000/directory}" \
--no-verify-ssl \
--dvsni-port 5001 \
--simple-http-port 5002 \
--http-01-port 5002 \
--manual-test-mode \
$store_flags \
--text \