From ea3611afe6b7e5e605dff6bcd31eb1dbdf235486 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 28 Oct 2015 21:27:04 +0000 Subject: [PATCH] http-01 for standalone --- acme/acme/challenges.py | 2 +- acme/acme/standalone.py | 26 +++++++++--------- acme/acme/standalone_test.py | 29 ++++++++++---------- docs/using.rst | 2 +- letsencrypt/plugins/standalone.py | 36 ++++++++++++------------- letsencrypt/plugins/standalone_test.py | 37 +++++++++++++------------- tests/boulder-integration.sh | 2 +- 7 files changed, 66 insertions(+), 68 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 750f2be8d..86ff0a902 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -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) diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 310e61995..1466671e3 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -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") diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index ed015b826..85ef6ab14 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -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): diff --git a/docs/using.rst b/docs/using.rst index 73c3fe140..312e55510 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -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. diff --git a/letsencrypt/plugins/standalone.py b/letsencrypt/plugins/standalone.py index ccff03319..c45337507 100644 --- a/letsencrypt/plugins/standalone.py +++ b/letsencrypt/plugins/standalone.py @@ -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 diff --git a/letsencrypt/plugins/standalone_test.py b/letsencrypt/plugins/standalone_test.py index 3a6941be8..6bf84cb73 100644 --- a/letsencrypt/plugins/standalone_test.py +++ b/letsencrypt/plugins/standalone_test.py @@ -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() diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh index 7c0ee8fea..18a996926 100755 --- a/tests/boulder-integration.sh +++ b/tests/boulder-integration.sh @@ -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" \