tls-sni-01 for standalone

This commit is contained in:
Jakub Warmuz 2015-11-01 12:22:19 +00:00
parent 31706a5ef9
commit 93e69ef7de
No known key found for this signature in database
GPG key ID: 2A7BAD3A489B52EA
6 changed files with 37 additions and 34 deletions

View file

@ -135,7 +135,7 @@ Plugin A I Notes and status
========== = = ================================================================
standalone Y N Very stable. Uses port 80 (force by
``--standalone-supported-challenges http-01``) or 443
(force by ``--standalone-supported-challenges dvsni``).
(force by ``--standalone-supported-challenges tls-sni-01``).
apache Y Y Alpha. Automates Apache installation, works fairly well but on
Debian-based distributions only for now.
webroot Y N Works with already running webserver, by writing necessary files

View file

@ -16,7 +16,7 @@ server = https://acme-staging.api.letsencrypt.org/directory
# Uncomment to use the standalone authenticator on port 443
# authenticator = standalone
# standalone-supported-challenges = dvsni
# standalone-supported-challenges = tls-sni-01
# Uncomment to use the webroot authenticator. Replace webroot-path with the
# path to the public_html / webroot folder being served by your web server.

View file

@ -49,9 +49,10 @@ class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge):
"""Client annotated `KeyAuthorizationChallenge` challenge."""
__slots__ = ('challb', 'domain', 'account_key')
def response_and_validation(self):
def response_and_validation(self, *args, **kwargs):
"""Generate response and validation."""
return self.challb.chall.response_and_validation(self.account_key)
return self.challb.chall.response_and_validation(
self.account_key, *args, **kwargs)
class DVSNI(AnnotatedChallenge):

View file

@ -11,7 +11,6 @@ import six
import zope.interface
from acme import challenges
from acme import crypto_util as acme_crypto_util
from acme import standalone as acme_standalone
from letsencrypt import errors
@ -51,19 +50,19 @@ class ServerManager(object):
:param int port: Port to run the server on.
:param challenge_type: Subclass of `acme.challenges.Challenge`,
either `acme.challenge.HTTP01` or `acme.challenges.DVSNI`.
either `acme.challenge.HTTP01` or `acme.challenges.TLSSNI01`.
:returns: Server instance.
:rtype: ACMEServerMixin
"""
assert challenge_type in (challenges.DVSNI, challenges.HTTP01)
assert challenge_type in (challenges.TLSSNI01, challenges.HTTP01)
if port in self._instances:
return self._instances[port].server
address = ("", port)
try:
if challenge_type is challenges.DVSNI:
if challenge_type is challenges.TLSSNI01:
server = acme_standalone.DVSNIServer(address, self.certs)
else: # challenges.HTTP01
server = acme_standalone.HTTP01Server(
@ -109,7 +108,7 @@ class ServerManager(object):
in six.iteritems(self._instances))
SUPPORTED_CHALLENGES = set([challenges.DVSNI, challenges.HTTP01])
SUPPORTED_CHALLENGES = set([challenges.TLSSNI01, challenges.HTTP01])
def supported_challenges_validator(data):
@ -138,7 +137,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 HTTP01
necessary port in order to respond to incoming tls-sni-01 and http-01
challenges from the certificate authority. Therefore, it does not
rely on any existing server program.
"""
@ -150,7 +149,7 @@ class Authenticator(common.Plugin):
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
# one self-signed key for all DVSNI certificates
# one self-signed key for all tls-sni-01 certificates
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, bits=2048)
@ -183,15 +182,16 @@ class Authenticator(common.Plugin):
necessary_ports = set()
if challenges.HTTP01 in self.supported_challenges:
necessary_ports.add(self.config.http01_port)
if challenges.DVSNI in self.supported_challenges:
if challenges.TLSSNI01 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 HTTP01 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 "
"tls-sni-01 and http-01 challenges from the certificate "
"authority. Therefore, it does not rely on any existing "
"server program.")
def prepare(self): # pylint: disable=missing-docstring
pass
@ -241,9 +241,11 @@ class Authenticator(common.Plugin):
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
chall=achall.chall, response=response,
validation=validation))
else: # DVSNI
server = self.servers.run(self.config.dvsni_port, challenges.DVSNI)
response, cert, _ = achall.gen_cert_and_response(self.key)
else: # tls-sni-01
server = self.servers.run(
self.config.dvsni_port, challenges.TLSSNI01)
response, (cert, _) = achall.response_and_validation(
cert_key=self.key)
self.certs[response.z_domain] = (self.key, cert)
self.served[server].add(achall)
responses.append(response)

View file

@ -39,8 +39,8 @@ class ServerManagerTest(unittest.TestCase):
self.mgr.stop(port=port)
self.assertEqual(self.mgr.running(), {})
def test_run_stop_dvsni(self):
self._test_run_stop(challenges.DVSNI)
def test_run_stop_tls_sni_01(self):
self._test_run_stop(challenges.TLSSNI01)
def test_run_stop_http_01(self):
self._test_run_stop(challenges.HTTP01)
@ -73,10 +73,10 @@ class SupportedChallengesValidatorTest(unittest.TestCase):
return supported_challenges_validator(data)
def test_correct(self):
self.assertEqual("dvsni", self._call("dvsni"))
self.assertEqual("tls-sni-01", self._call("tls-sni-01"))
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"))
self.assertEqual("tls-sni-01,http-01", self._call("tls-sni-01,http-01"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,tls-sni-01"))
def test_unrecognized(self):
assert "foo" not in challenges.Challenge.TYPES
@ -93,23 +93,23 @@ class AuthenticatorTest(unittest.TestCase):
from letsencrypt.plugins.standalone import Authenticator
self.config = mock.MagicMock(
dvsni_port=1234, http01_port=4321,
standalone_supported_challenges="dvsni,http-01")
standalone_supported_challenges="tls-sni-01,http-01")
self.auth = Authenticator(self.config, name="standalone")
def test_supported_challenges(self):
self.assertEqual(self.auth.supported_challenges,
set([challenges.DVSNI, challenges.HTTP01]))
set([challenges.TLSSNI01, 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.HTTP01]))
set([challenges.TLSSNI01, challenges.HTTP01]))
@mock.patch("letsencrypt.plugins.standalone.util")
def test_perform_alredy_listening(self, mock_util):
for chall, port in ((challenges.DVSNI.typ, 1234),
for chall, port in ((challenges.TLSSNI01.typ, 1234),
(challenges.HTTP01.typ, 4321)):
mock_util.already_listening.return_value = True
self.config.standalone_supported_challenges = chall
@ -155,8 +155,8 @@ class AuthenticatorTest(unittest.TestCase):
key = jose.JWK.load(test_util.load_vector('rsa512_key.pem'))
http_01 = 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)
tls_sni_01 = achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.TLSSNI01_P, domain=domain, account_key=key)
self.auth.servers = mock.MagicMock()
@ -164,19 +164,19 @@ class AuthenticatorTest(unittest.TestCase):
return "server{0}".format(port)
self.auth.servers.run.side_effect = _run
responses = self.auth.perform2([http_01, dvsni])
responses = self.auth.perform2([http_01, tls_sni_01])
self.assertTrue(isinstance(responses, list))
self.assertEqual(2, len(responses))
self.assertTrue(isinstance(responses[0], challenges.HTTP01Response))
self.assertTrue(isinstance(responses[1], challenges.DVSNIResponse))
self.assertTrue(isinstance(responses[1], challenges.TLSSNI01Response))
self.assertEqual(self.auth.servers.run.mock_calls, [
mock.call(4321, challenges.HTTP01),
mock.call(1234, challenges.DVSNI),
mock.call(1234, challenges.TLSSNI01),
])
self.assertEqual(self.auth.served, {
"server1234": set([dvsni]),
"server1234": set([tls_sni_01]),
"server4321": set([http_01]),
})
self.assertEqual(1, len(self.auth.http_01_resources))

View file

@ -27,7 +27,7 @@ common() {
"$@"
}
common --domains le1.wtf --standalone-supported-challenges dvsni auth
common --domains le1.wtf --standalone-supported-challenges tls-sni-01 auth
common --domains le2.wtf --standalone-supported-challenges http-01 run
common -a manual -d le.wtf auth