mirror of
https://github.com/certbot/certbot.git
synced 2026-01-21 19:01:07 +03:00
http-01 for standalone
This commit is contained in:
@@ -249,7 +249,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
||||
"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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,7 +184,7 @@ class Authenticator(common.Plugin):
|
||||
@property
|
||||
def _necessary_ports(self):
|
||||
necessary_ports = set()
|
||||
if challenges.SimpleHTTP in self.supported_challenges:
|
||||
if challenges.HTTP01 in self.supported_challenges:
|
||||
necessary_ports.add(self.config.simple_http_port)
|
||||
if challenges.DVSNI in self.supported_challenges:
|
||||
necessary_ports.add(self.config.dvsni_port)
|
||||
@@ -193,9 +192,9 @@ class Authenticator(common.Plugin):
|
||||
|
||||
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.simple_http_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
|
||||
|
||||
@@ -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, simple_http_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()
|
||||
|
||||
@@ -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" \
|
||||
|
||||
Reference in New Issue
Block a user