Remove SimpleHTTP TLS from acme.

This commit is contained in:
Jakub Warmuz
2015-10-08 21:10:12 +00:00
parent 1efec6b399
commit 304414a214
4 changed files with 96 additions and 145 deletions
-31
View File
@@ -70,20 +70,10 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
class FakeConnection(object): class FakeConnection(object):
"""Fake OpenSSL.SSL.Connection.""" """Fake OpenSSL.SSL.Connection."""
MAKEFILE_SUPPORT = hasattr(socket, "_fileobject")
"""Is `makefile` supported on your platform?
.. warning:: `makefile`, as currently implemented, is supported
on select platforms only, as it uses CPython's internal API.
You've been warned!
"""
# pylint: disable=missing-docstring # pylint: disable=missing-docstring
def __init__(self, connection): def __init__(self, connection):
self._wrapped = connection self._wrapped = connection
self._makefile_refs = 0
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self._wrapped, name) return getattr(self._wrapped, name)
@@ -92,27 +82,6 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
# OpenSSL.SSL.Connection.shutdown doesn't accept any args # OpenSSL.SSL.Connection.shutdown doesn't accept any args
return self._wrapped.shutdown() return self._wrapped.shutdown()
# stuff below ripped off from
# https://hg.python.org/cpython/file/2.7/Lib/ssl.py
def makefile(self, mode='r', bufsize=-1):
assert self.MAKEFILE_SUPPORT, (
"You need compatible version for makefile support")
self._makefile_refs += 1
# SocketServer.StreamRequesthandler.finish will try to
# close the wfile/rfile. close=True causes curl: (56)
# GnuTLS recv error (-110): The TLS connection was
# non-properly terminated.
# TODO: doesn't work in Python3
# pylint: disable=protected-access
return socket._fileobject(self._wrapped, mode, bufsize, close=False)
def close(self):
if self._makefile_refs < 1:
self._wrapped.close()
else:
self._makefile_refs -= 1
def accept(self): # pylint: disable=missing-docstring def accept(self): # pylint: disable=missing-docstring
sock, addr = self.sock.accept() sock, addr = self.sock.accept()
+19 -50
View File
@@ -44,15 +44,7 @@ class TLSServer(socketserver.TCPServer):
return socketserver.TCPServer.server_bind(self) return socketserver.TCPServer.server_bind(self)
class HTTPSServer(TLSServer, BaseHTTPServer.HTTPServer): class ACMEServerMixin: # pylint: disable=old-style-class
"""HTTPS Server."""
def server_bind(self):
self._wrap_sock()
BaseHTTPServer.HTTPServer.server_bind(self)
class ACMEServerMixin: # pylint: disable=old-style-class,no-init
"""ACME server common settings mixin.""" """ACME server common settings mixin."""
server_version = "ACME standalone client" server_version = "ACME standalone client"
allow_reuse_address = True allow_reuse_address = True
@@ -81,27 +73,23 @@ class ACMEServerMixin: # pylint: disable=old-style-class,no-init
self.server_close() self.server_close()
class ACMETLSServer(HTTPSServer, ACMEServerMixin): class DVSNIServer(TLSServer, ACMEServerMixin):
"""ACME TLS Server.""" """DVSNI Server."""
SIMPLE_HTTP_SUPPORT = crypto_util.SSLSocket.FakeConnection.MAKEFILE_SUPPORT def __init__(self, server_address, certs):
"""Is SimpleHTTP supported on your platform.
Please see a warning for `acme.crypto_util.SSLSocket.FakeConnection`.
"""
def __init__(self, *args, **kwargs):
ACMEServerMixin.__init__(self) ACMEServerMixin.__init__(self)
HTTPSServer.__init__(self, *args, **kwargs) TLSServer.__init__(
self, server_address, socketserver.BaseRequestHandler, certs=certs)
class ACMEServer(BaseHTTPServer.HTTPServer, ACMEServerMixin): class SimpleHTTPServer(BaseHTTPServer.HTTPServer, ACMEServerMixin):
"""ACME Server (non-TLS).""" """SimpleHTTP Server."""
def __init__(self, *args, **kwargs): def __init__(self, server_address, resources):
ACMEServerMixin.__init__(self) ACMEServerMixin.__init__(self)
BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs) BaseHTTPServer.HTTPServer.__init__(
self, server_address, SimpleHTTPRequestHandler.partial_init(
simple_http_resources=resources))
class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
@@ -133,14 +121,14 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.send_response(200) self.send_response(200)
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html")
self.end_headers() self.end_headers()
self.wfile.write(self.server.server_version) self.wfile.write(self.server.server_version.encode())
def handle_404(self): def handle_404(self):
"""Handler 404 Not Found errors.""" """Handler 404 Not Found errors."""
self.send_response(http_client.NOT_FOUND, message="Not Found") self.send_response(http_client.NOT_FOUND, message="Not Found")
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html")
self.end_headers() self.end_headers()
self.wfile.write("404") self.wfile.write(b"404")
def handle_simple_http_resource(self): def handle_simple_http_resource(self):
"""Handle SimpleHTTP provisioned resources.""" """Handle SimpleHTTP provisioned resources."""
@@ -171,24 +159,8 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
cls, simple_http_resources=simple_http_resources) cls, simple_http_resources=simple_http_resources)
class ACMERequestHandler(SimpleHTTPRequestHandler): def simple_dvsni_server(cli_args, forever=True):
"""ACME request handler.""" """Run simple standalone DVSNI server."""
def handle_one_request(self):
"""Handle single request.
Makes sure that DVSNI probers are ignored.
"""
try:
return SimpleHTTPRequestHandler.handle_one_request(self)
except OpenSSL.SSL.ZeroReturnError:
logger.debug("Client prematurely closed connection (prober?). "
"Ignoring request.")
def simple_server(cli_args, forever=True):
"""Run simple standalone client server."""
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@@ -198,7 +170,6 @@ def simple_server(cli_args, forever=True):
args = parser.parse_args(cli_args[1:]) args = parser.parse_args(cli_args[1:])
certs = {} certs = {}
resources = {}
_, hosts, _ = next(os.walk('.')) _, hosts, _ = next(os.walk('.'))
for host in hosts: for host in hosts:
@@ -206,15 +177,13 @@ def simple_server(cli_args, forever=True):
cert_contents = cert_file.read() cert_contents = cert_file.read()
with open(os.path.join(host, "key.pem")) as key_file: with open(os.path.join(host, "key.pem")) as key_file:
key_contents = key_file.read() key_contents = key_file.read()
certs[host] = ( certs[host.encode()] = (
OpenSSL.crypto.load_privatekey( OpenSSL.crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM, key_contents), OpenSSL.crypto.FILETYPE_PEM, key_contents),
OpenSSL.crypto.load_certificate( OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert_contents)) OpenSSL.crypto.FILETYPE_PEM, cert_contents))
handler = ACMERequestHandler.partial_init( server = DVSNIServer(('', int(args.port)), certs=certs)
simple_http_resources=resources)
server = ACMETLSServer(('', int(args.port)), handler, certs=certs)
six.print_("Serving at https://localhost:{0}...".format( six.print_("Serving at https://localhost:{0}...".format(
server.socket.getsockname()[1])) server.socket.getsockname()[1]))
if forever: # pragma: no cover if forever: # pragma: no cover
@@ -224,4 +193,4 @@ def simple_server(cli_args, forever=True):
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(simple_server(sys.argv)) # pragma: no cover sys.exit(simple_dvsni_server(sys.argv)) # pragma: no cover
+71 -51
View File
@@ -13,6 +13,7 @@ import requests
from acme import challenges from acme import challenges
from acme import crypto_util from acme import crypto_util
from acme import errors
from acme import jose from acme import jose
from acme import test_util from acme import test_util
@@ -30,25 +31,27 @@ class TLSServerTest(unittest.TestCase):
class ACMEServerMixinTest(unittest.TestCase): class ACMEServerMixinTest(unittest.TestCase):
"""Tests for acme.standalone.ACMEServerMixin.""" """Tests for acme.standalone.ACMEServerMixin."""
def setUp(self):
from acme.standalone import ACMEServerMixin
class _MockServer(socketserver.TCPServer, ACMEServerMixin):
def __init__(self, *args, **kwargs):
socketserver.TCPServer.__init__(self, *args, **kwargs)
ACMEServerMixin.__init__(self)
self.server = _MockServer(("", 0), socketserver.BaseRequestHandler)
def test_serve_shutdown(self):
thread = threading.Thread(target=self.server.serve_forever2)
thread.start()
self.server.shutdown2()
def test_shutdown2_not_running(self): def test_shutdown2_not_running(self):
from acme.standalone import ACMEServer self.server.shutdown2()
server = ACMEServer(("", 0), socketserver.BaseRequestHandler) self.server.shutdown2()
server.shutdown2()
server.shutdown2()
class ACMEServerTest(unittest.TestCase): class DVSNIServerTest(unittest.TestCase):
"""Test for acme.standalone.ACMEServer.""" """Test for acme.standalone.DVSNIServer."""
def test_init(self):
from acme.standalone import ACMEServer
server = ACMEServer(("", 0), socketserver.BaseRequestHandler)
# pylint: disable=protected-access
self.assertFalse(server._stopped)
class ACMESimpleHTTPTLSServerTestEndToEnd(unittest.TestCase):
"""End-to-end test for ACME TLS server with SimpleHTTP."""
def setUp(self): def setUp(self):
self.certs = { self.certs = {
@@ -56,36 +59,19 @@ class ACMESimpleHTTPTLSServerTestEndToEnd(unittest.TestCase):
# pylint: disable=protected-access # pylint: disable=protected-access
test_util.load_cert('cert.pem')._wrapped), test_util.load_cert('cert.pem')._wrapped),
} }
self.account_key = jose.JWK.load( from acme.standalone import DVSNIServer
test_util.load_vector('rsa1024_key.pem')) self.server = DVSNIServer(("", 0), certs=self.certs)
# pylint: disable=no-member
from acme.standalone import ACMETLSServer self.thread = threading.Thread(target=self.server.handle_request)
from acme.standalone import ACMERequestHandler self.thread.start()
self.resources = set()
handler = ACMERequestHandler.partial_init(
simple_http_resources=self.resources)
self.server = ACMETLSServer(('', 0), handler, certs=self.certs)
self.server_thread = threading.Thread(
# pylint: disable=no-member
target=self.server.serve_forever2)
self.server_thread.start()
self.port = self.server.socket.getsockname()[1]
def tearDown(self): def tearDown(self):
self.server.shutdown2() self.server.shutdown2()
self.server_thread.join() self.thread.join()
def test_index(self): def test_init(self):
response = requests.get( # pylint: disable=protected-access
'https://localhost:{0}'.format(self.port), verify=False) self.assertFalse(self.server._stopped)
self.assertEqual(response.text, 'ACME standalone client')
self.assertTrue(response.ok)
def test_404(self):
response = requests.get(
'https://localhost:{0}/foo'.format(self.port), verify=False)
self.assertEqual(response.status_code, http_client.NOT_FOUND)
def test_dvsni(self): def test_dvsni(self):
cert = crypto_util.probe_sni( cert = crypto_util.probe_sni(
@@ -93,9 +79,41 @@ class ACMESimpleHTTPTLSServerTestEndToEnd(unittest.TestCase):
self.assertEqual(jose.ComparableX509(cert), self.assertEqual(jose.ComparableX509(cert),
jose.ComparableX509(self.certs[b'localhost'][1])) jose.ComparableX509(self.certs[b'localhost'][1]))
class SimpleHTTPServerTest(unittest.TestCase):
"""Tests for acme.standalone.SimpleHTTPServer."""
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)
# pylint: disable=no-member
self.port = self.server.socket.getsockname()[1]
self.thread = threading.Thread(target=self.server.handle_request)
self.thread.start()
def tearDown(self):
self.server.shutdown2()
self.thread.join()
def test_index(self):
response = requests.get(
'http://localhost:{0}'.format(self.port), verify=False)
self.assertEqual(response.text, 'ACME standalone client')
self.assertTrue(response.ok)
def test_404(self):
response = requests.get(
'http://localhost:{0}/foo'.format(self.port), verify=False)
self.assertEqual(response.status_code, http_client.NOT_FOUND)
def _test_simple_http(self, add): def _test_simple_http(self, add):
chall = challenges.SimpleHTTP(token=(b'x' * 16)) chall = challenges.SimpleHTTP(token=(b'x' * 16))
response = challenges.SimpleHTTPResponse(tls=True) response = challenges.SimpleHTTPResponse(tls=False)
from acme.standalone import SimpleHTTPRequestHandler from acme.standalone import SimpleHTTPRequestHandler
resource = SimpleHTTPRequestHandler.SimpleHTTPResource( resource = SimpleHTTPRequestHandler.SimpleHTTPResource(
@@ -114,8 +132,8 @@ class ACMESimpleHTTPTLSServerTestEndToEnd(unittest.TestCase):
self.assertFalse(self._test_simple_http(add=False)) self.assertFalse(self._test_simple_http(add=False))
class TestSimpleServer(unittest.TestCase): class TestSimpleDVSNIServer(unittest.TestCase):
"""Tests for acme.standalone.simple_server.""" """Tests for acme.standalone.simple_dvsni_server."""
def setUp(self): def setUp(self):
# mirror ../examples/standalone # mirror ../examples/standalone
@@ -126,9 +144,10 @@ class TestSimpleServer(unittest.TestCase):
shutil.copy(test_util.vector_path('rsa512_key.pem'), shutil.copy(test_util.vector_path('rsa512_key.pem'),
os.path.join(localhost_dir, 'key.pem')) os.path.join(localhost_dir, 'key.pem'))
from acme.standalone import simple_server from acme.standalone import simple_dvsni_server
self.thread = threading.Thread(target=simple_server, kwargs={ self.port = 1234
'cli_args': ('xxx', '--port', '1234'), self.thread = threading.Thread(target=simple_dvsni_server, kwargs={
'cli_args': ('xxx', '--port', str(self.port)),
'forever': False, 'forever': False,
}) })
self.old_cwd = os.getcwd() self.old_cwd = os.getcwd()
@@ -145,12 +164,13 @@ class TestSimpleServer(unittest.TestCase):
while max_attempts: while max_attempts:
max_attempts -= 1 max_attempts -= 1
try: try:
response = requests.get('https://localhost:1234', verify=False) cert = crypto_util.probe_sni(b'localhost', b'0.0.0.0', self.port)
except requests.ConnectionError: except errors.Error:
self.assertTrue(max_attempts > 0, "Timeout!") self.assertTrue(max_attempts > 0, "Timeout!")
time.sleep(1) # wait until thread starts time.sleep(1) # wait until thread starts
else: else:
self.assertEqual(response.text, 'ACME standalone client') self.assertEqual(jose.ComparableX509(cert),
test_util.load_cert('cert.pem'))
break break
+6 -13
View File
@@ -1,7 +1,6 @@
"""Standalone Authenticator.""" """Standalone Authenticator."""
import argparse import argparse
import collections import collections
import functools
import logging import logging
import random import random
import socket import socket
@@ -61,25 +60,19 @@ class ServerManager(object):
if port in self._instances: if port in self._instances:
return self._instances[port].server return self._instances[port].server
logger.debug("Starting new server at %s (tls=%s)", port, tls) address = ("", port)
handler = acme_standalone.ACMERequestHandler.partial_init(
self.simple_http_resources)
if tls:
cls = functools.partial(
acme_standalone.ACMETLSServer, certs=self.certs)
else:
cls = acme_standalone.ACMEServer
try: try:
server = cls(("", port), handler) if tls:
server = acme_standalone.DVSNIServer(address, self.certs)
else:
server = acme_standalone.SimpleHTTPServer(
address, self.simple_http_resources)
except socket.error as error: except socket.error as error:
raise errors.StandaloneBindError(error, port) raise errors.StandaloneBindError(error, port)
# if port == 0, then random free port on OS is taken # if port == 0, then random free port on OS is taken
# pylint: disable=no-member # pylint: disable=no-member
host, real_port = server.socket.getsockname() host, real_port = server.socket.getsockname()
thread = threading.Thread(target=server.serve_forever2) thread = threading.Thread(target=server.serve_forever2)
logger.debug("Starting server at %s:%d", host, real_port) logger.debug("Starting server at %s:%d", host, real_port)
thread.start() thread.start()