1
0
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:
Jakub Warmuz
2015-10-28 21:27:04 +00:00
parent fd209eab66
commit ea3611afe6
7 changed files with 66 additions and 68 deletions

View File

@@ -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)

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

@@ -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

@@ -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

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, 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()

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" \