mirror of
https://github.com/certbot/certbot.git
synced 2026-06-03 22:08:07 -04:00
Merge remote-tracking branch 'github/letsencrypt/master' into acme
Conflicts: letsencrypt/client/tests/auth_handler_test.py
This commit is contained in:
commit
ec4dc6905f
7 changed files with 156 additions and 57 deletions
17
docs/conf.py
17
docs/conf.py
|
|
@ -12,13 +12,22 @@
|
|||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# read version number (and other metadata) from package init
|
||||
init_fn = os.path.join(here, '..', 'letsencrypt', '__init__.py')
|
||||
with codecs.open(init_fn, encoding='utf8') as fd:
|
||||
meta = dict(re.findall(r"""__([a-z]+)__ = "([^"]+)""", fd.read()))
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(here, '..')))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
|
|
@ -58,9 +67,9 @@ copyright = u'2014, Let\'s Encrypt Project'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.1'
|
||||
version = '.'.join(meta['version'].split('.')[:2])
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.1'
|
||||
release = meta['version']
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
"""Let's Encrypt."""
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = "0.1"
|
||||
|
|
|
|||
|
|
@ -129,7 +129,12 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
|
|||
self._cleanup_challenges(domain)
|
||||
|
||||
def _satisfy_challenges(self):
|
||||
"""Attempt to satisfy all saved challenge messages."""
|
||||
"""Attempt to satisfy all saved challenge messages.
|
||||
|
||||
.. todo:: It might be worth it to try different challenges to
|
||||
find one that doesn't throw an exception
|
||||
|
||||
"""
|
||||
logging.info("Performing the following challenges:")
|
||||
for dom in self.domains:
|
||||
self.paths[dom] = gen_challenge_path(
|
||||
|
|
@ -149,8 +154,19 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
|
|||
flat_client.extend(ichall.chall for ichall in self.client_c[dom])
|
||||
flat_auth.extend(ichall.chall for ichall in self.dv_c[dom])
|
||||
|
||||
client_resp = self.client_auth.perform(flat_client)
|
||||
dv_resp = self.dv_auth.perform(flat_auth)
|
||||
try:
|
||||
client_resp = self.client_auth.perform(flat_client)
|
||||
dv_resp = self.dv_auth.perform(flat_auth)
|
||||
# This will catch both specific types of errors.
|
||||
except errors.LetsEncryptAuthHandlerError as err:
|
||||
logging.critical("Failure in setting up challenges:")
|
||||
logging.critical(str(err))
|
||||
logging.info("Attempting to clean up outstanding challenges...")
|
||||
for dom in self.domains:
|
||||
self._cleanup_challenges(dom)
|
||||
|
||||
raise errors.LetsEncryptAuthHandlerError(
|
||||
"Unable to perform challenges")
|
||||
|
||||
logging.info("Ready for verification...")
|
||||
|
||||
|
|
@ -197,8 +213,12 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
|
|||
|
||||
"""
|
||||
logging.info("Cleaning up challenges for %s", domain)
|
||||
self.dv_auth.cleanup(self.dv_c[domain])
|
||||
self.client_auth.cleanup(self.client_c[domain])
|
||||
# These are indexed challenges... give just the challenges to the auth
|
||||
# Chose to make these lists instead of a generator to make it easier to
|
||||
# work with...
|
||||
self.dv_auth.cleanup([ichall.chall for ichall in self.dv_c[domain]])
|
||||
self.client_auth.cleanup(
|
||||
[ichall.chall for ichall in self.client_c[domain]])
|
||||
|
||||
def _cleanup_state(self, delete_list):
|
||||
"""Cleanup state after an authorization is received.
|
||||
|
|
@ -285,8 +305,7 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
|
|||
|
||||
elif chall["type"] == "dns":
|
||||
logging.info(" DNS challenge for name %s.", domain)
|
||||
return challenge_util.DnsChall(
|
||||
domain, str(chall["token"]), self.authkey[domain])
|
||||
return challenge_util.DnsChall(domain, str(chall["token"]))
|
||||
|
||||
else:
|
||||
raise errors.LetsEncryptClientError(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from letsencrypt.client import crypto_util
|
|||
DvsniChall = collections.namedtuple("DvsniChall", "domain, r_b64, nonce, key")
|
||||
SimpleHttpsChall = collections.namedtuple(
|
||||
"SimpleHttpsChall", "domain, token, key")
|
||||
DnsChall = collections.namedtuple("DnsChall", "domain, token, key")
|
||||
DnsChall = collections.namedtuple("DnsChall", "domain, token")
|
||||
|
||||
# Client Challenges
|
||||
RecContactChall = collections.namedtuple(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class LetsEncryptReverterError(LetsEncryptClientError):
|
|||
"""Let's Encrypt Reverter error."""
|
||||
|
||||
|
||||
# Auth Handler Errors
|
||||
class LetsEncryptAuthHandlerError(LetsEncryptClientError):
|
||||
"""Let's Encrypt Auth Handler error."""
|
||||
|
||||
|
|
@ -17,6 +18,16 @@ class LetsEncryptClientAuthError(LetsEncryptAuthHandlerError):
|
|||
"""Let's Encrypt Client Authenticator error."""
|
||||
|
||||
|
||||
class LetsEncryptDvAuthError(LetsEncryptAuthHandlerError):
|
||||
"""Let's Encrypt DV Authenticator error."""
|
||||
|
||||
|
||||
# Authenticator - Challenge specific errors
|
||||
class LetsEncryptDvsniError(LetsEncryptDvAuthError):
|
||||
"""Let's Encrypt DVSNI error."""
|
||||
|
||||
|
||||
# Configurator Errors
|
||||
class LetsEncryptConfiguratorError(LetsEncryptClientError):
|
||||
"""Let's Encrypt Configurator error."""
|
||||
|
||||
|
|
@ -28,6 +39,3 @@ class LetsEncryptNoInstallationError(LetsEncryptConfiguratorError):
|
|||
class LetsEncryptMisconfigurationError(LetsEncryptConfiguratorError):
|
||||
"""Let's Encrypt Misconfiguration error."""
|
||||
|
||||
|
||||
class LetsEncryptDvsniError(LetsEncryptConfiguratorError):
|
||||
"""Let's Encrypt DVSNI error."""
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"""Tests for letsencrypt.client.auth_handler."""
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from letsencrypt import acme
|
||||
|
||||
from letsencrypt.client import challenge_util
|
||||
from letsencrypt.client import errors
|
||||
from letsencrypt.client.tests import acme_util
|
||||
|
||||
|
|
@ -38,6 +40,11 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
self.handler = AuthHandler(
|
||||
self.mock_dv_auth, self.mock_client_auth, None)
|
||||
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
def test_name1_dvsni1(self):
|
||||
dom = "0"
|
||||
challenge = [acme_util.CHALLENGES["dvsni"]]
|
||||
|
|
@ -58,7 +65,7 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
|
||||
def test_name5_dvsni5(self):
|
||||
challenge = [acme_util.CHALLENGES["dvsni"]]
|
||||
for i in range(5):
|
||||
for i in xrange(5):
|
||||
self.handler.add_chall_msg(
|
||||
str(i),
|
||||
acme.messages.Challenge(session_id=str(i), nonce="nonce%d" % i,
|
||||
|
|
@ -72,14 +79,14 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
self.assertEqual(len(self.handler.client_c), 5)
|
||||
# Each message contains 1 auth, 0 client
|
||||
|
||||
for i in range(5):
|
||||
for i in xrange(5):
|
||||
dom = str(i)
|
||||
self.assertEqual(len(self.handler.responses[dom]), 1)
|
||||
self.assertEqual(self.handler.responses[dom][0], "DvsniChall%d" % i)
|
||||
self.assertEqual(len(self.handler.dv_c[dom]), 1)
|
||||
self.assertEqual(len(self.handler.client_c[dom]), 0)
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c[dom][0].chall).__name__, "DvsniChall")
|
||||
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
|
||||
challenge_util.DvsniChall))
|
||||
|
||||
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
|
||||
def test_name1_auth(self, mock_chall_path):
|
||||
|
|
@ -108,8 +115,8 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(len(self.handler.dv_c[dom]), 1)
|
||||
self.assertEqual(len(self.handler.client_c[dom]), 0)
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c[dom][0].chall).__name__, "SimpleHttpsChall")
|
||||
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
|
||||
challenge_util.SimpleHttpsChall))
|
||||
|
||||
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
|
||||
def test_name1_all(self, mock_chall_path):
|
||||
|
|
@ -138,16 +145,16 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
self.assertEqual(
|
||||
self.handler.responses[dom],
|
||||
self._get_exp_response(dom, path, challenges))
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c[dom][0].chall).__name__, "SimpleHttpsChall")
|
||||
self.assertEqual(
|
||||
type(self.handler.client_c[dom][0].chall).__name__, "RecTokenChall")
|
||||
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
|
||||
challenge_util.SimpleHttpsChall))
|
||||
self.assertTrue(isinstance(self.handler.client_c[dom][0].chall,
|
||||
challenge_util.RecTokenChall))
|
||||
|
||||
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
|
||||
def test_name5_all(self, mock_chall_path):
|
||||
challenges = acme_util.get_challenges()
|
||||
combos = acme_util.gen_combos(challenges)
|
||||
for i in range(5):
|
||||
for i in xrange(5):
|
||||
self.handler.add_chall_msg(
|
||||
str(i),
|
||||
acme.messages.Challenge(
|
||||
|
|
@ -161,7 +168,7 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
self.handler._satisfy_challenges() # pylint: disable=protected-access
|
||||
|
||||
self.assertEqual(len(self.handler.responses), 5)
|
||||
for i in range(5):
|
||||
for i in xrange(5):
|
||||
self.assertEqual(
|
||||
len(self.handler.responses[str(i)]), len(challenges))
|
||||
self.assertEqual(len(self.handler.dv_c), 5)
|
||||
|
|
@ -175,11 +182,10 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
self.assertEqual(len(self.handler.dv_c[dom]), 1)
|
||||
self.assertEqual(len(self.handler.client_c[dom]), 1)
|
||||
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c[dom][0].chall).__name__, "DvsniChall")
|
||||
self.assertEqual(
|
||||
type(self.handler.client_c[dom][0].chall).__name__,
|
||||
"RecContactChall")
|
||||
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
|
||||
challenge_util.DvsniChall))
|
||||
self.assertTrue(isinstance(self.handler.client_c[dom][0].chall,
|
||||
challenge_util.RecContactChall))
|
||||
|
||||
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
|
||||
def test_name5_mix(self, mock_chall_path):
|
||||
|
|
@ -196,7 +202,7 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
acme_util.get_challenges()]
|
||||
|
||||
# Combos doesn't matter since I am overriding the gen_path function
|
||||
for i in range(5):
|
||||
for i in xrange(5):
|
||||
dom = str(i)
|
||||
paths.append(gen_path(chosen_chall[i], challenge_list[i]))
|
||||
self.handler.add_chall_msg(
|
||||
|
|
@ -214,7 +220,7 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
self.assertEqual(len(self.handler.dv_c), 5)
|
||||
self.assertEqual(len(self.handler.client_c), 5)
|
||||
|
||||
for i in range(5):
|
||||
for i in xrange(5):
|
||||
dom = str(i)
|
||||
resp = self._get_exp_response(i, paths[i], challenge_list[i])
|
||||
self.assertEqual(self.handler.responses[dom], resp)
|
||||
|
|
@ -222,21 +228,67 @@ class SatisfyChallengesTest(unittest.TestCase):
|
|||
self.assertEqual(
|
||||
len(self.handler.client_c[dom]), len(chosen_chall[i]) - 1)
|
||||
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c["0"][0].chall).__name__, "DnsChall")
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c["1"][0].chall).__name__, "DvsniChall")
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c["2"][0].chall).__name__, "SimpleHttpsChall")
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c["3"][0].chall).__name__, "SimpleHttpsChall")
|
||||
self.assertEqual(
|
||||
type(self.handler.dv_c["4"][0].chall).__name__, "DnsChall")
|
||||
self.assertTrue(isinstance(self.handler.dv_c["0"][0].chall,
|
||||
challenge_util.DnsChall))
|
||||
self.assertTrue(isinstance(self.handler.dv_c["1"][0].chall,
|
||||
challenge_util.DvsniChall))
|
||||
self.assertTrue(isinstance(self.handler.dv_c["2"][0].chall,
|
||||
challenge_util.SimpleHttpsChall))
|
||||
self.assertTrue(isinstance(self.handler.dv_c["3"][0].chall,
|
||||
challenge_util.SimpleHttpsChall))
|
||||
self.assertTrue(isinstance(self.handler.dv_c["4"][0].chall,
|
||||
challenge_util.DnsChall))
|
||||
|
||||
self.assertTrue(isinstance(self.handler.client_c["2"][0].chall,
|
||||
challenge_util.PopChall))
|
||||
self.assertTrue(isinstance(self.handler.client_c["4"][0].chall,
|
||||
challenge_util.RecTokenChall))
|
||||
|
||||
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
|
||||
def test_perform_exception_cleanup(self, mock_chall_path):
|
||||
"""3 Challenge messages... fail perform... clean up."""
|
||||
# pylint: disable=protected-access
|
||||
self.mock_dv_auth.perform.side_effect = errors.LetsEncryptDvsniError
|
||||
|
||||
challenges = acme_util.get_challenges()
|
||||
combos = acme_util.gen_combos(challenges)
|
||||
|
||||
for i in xrange(3):
|
||||
self.handler.add_chall_msg(
|
||||
str(i),
|
||||
acme.messages.Challenge(
|
||||
session_id=str(i), nonce="nonce%d" % i,
|
||||
challenges=challenges, combinations=combos),
|
||||
"dummy_key")
|
||||
|
||||
mock_chall_path.return_value = gen_path(
|
||||
["dvsni", "proofOfPossession"], challenges)
|
||||
|
||||
# This may change in the future... but for now catch the error
|
||||
self.assertRaises(errors.LetsEncryptAuthHandlerError,
|
||||
self.handler._satisfy_challenges)
|
||||
|
||||
# Verify cleanup is actually run correctly
|
||||
self.assertEqual(self.mock_dv_auth.cleanup.call_count, 3)
|
||||
self.assertEqual(self.mock_client_auth.cleanup.call_count, 3)
|
||||
|
||||
# Check DV cleanup
|
||||
mock_cleanup_args = self.mock_dv_auth.cleanup.call_args_list
|
||||
for i in xrange(3):
|
||||
# Assert length of arg list was 1
|
||||
arg_chall_list = mock_cleanup_args[i][0][0]
|
||||
self.assertEqual(len(arg_chall_list), 1)
|
||||
self.assertTrue(isinstance(arg_chall_list[0],
|
||||
challenge_util.DvsniChall))
|
||||
|
||||
# Check Auth cleanup
|
||||
mock_cleanup_args = self.mock_client_auth.cleanup.call_args_list
|
||||
for i in xrange(3):
|
||||
arg_chall_list = mock_cleanup_args[i][0][0]
|
||||
self.assertEqual(len(arg_chall_list), 1)
|
||||
self.assertTrue(isinstance(arg_chall_list[0],
|
||||
challenge_util.PopChall))
|
||||
|
||||
self.assertEqual(
|
||||
type(self.handler.client_c["2"][0].chall).__name__, "PopChall")
|
||||
self.assertEqual(
|
||||
type(self.handler.client_c["4"][0].chall).__name__, "RecTokenChall")
|
||||
|
||||
def _get_exp_response(self, domain, path, challenges):
|
||||
# pylint: disable=no-self-use
|
||||
|
|
@ -269,7 +321,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
def test_solved3_at_once(self):
|
||||
# Set 3 DVSNI challenges
|
||||
challenge = [acme_util.CHALLENGES["dvsni"]]
|
||||
for i in range(3):
|
||||
for i in xrange(3):
|
||||
self.handler.add_chall_msg(
|
||||
str(i),
|
||||
acme.messages.Challenge(session_id=str(i), nonce="nonce%d" % i,
|
||||
|
|
@ -288,7 +340,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
self._test_finished()
|
||||
|
||||
def _sat_solved_at_once(self):
|
||||
for i in range(3):
|
||||
for i in xrange(3):
|
||||
dom = str(i)
|
||||
self.handler.responses[dom] = ["DvsniChall%d" % i]
|
||||
self.handler.paths[dom] = [0]
|
||||
|
|
@ -326,7 +378,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
challs = []
|
||||
challs.append(acme_util.get_challenges())
|
||||
challs.append(acme_util.get_dv_challenges())
|
||||
for i in range(2):
|
||||
for i in xrange(2):
|
||||
dom = str(i)
|
||||
self.handler.add_chall_msg(
|
||||
dom,
|
||||
|
|
@ -401,7 +453,7 @@ class PathSatisfiedTest(unittest.TestCase):
|
|||
self.handler.paths[dom[4]] = []
|
||||
self.handler.responses[dom[4]] = ["respond... sure"]
|
||||
|
||||
for i in range(5):
|
||||
for i in xrange(5):
|
||||
self.assertTrue(self.handler._path_satisfied(dom[i]))
|
||||
|
||||
def test_not_satisfied(self):
|
||||
|
|
@ -418,16 +470,25 @@ class PathSatisfiedTest(unittest.TestCase):
|
|||
self.handler.paths[dom[3]] = [0]
|
||||
self.handler.responses[dom[3]] = ["null"]
|
||||
|
||||
for i in range(4):
|
||||
for i in xrange(4):
|
||||
self.assertFalse(self.handler._path_satisfied(dom[i]))
|
||||
|
||||
|
||||
def gen_auth_resp(chall_list): # pylint: disable=missing-docstring
|
||||
def gen_auth_resp(chall_list):
|
||||
"""Generate a dummy authorization response."""
|
||||
return ["%s%s" % (type(chall).__name__, chall.domain)
|
||||
for chall in chall_list]
|
||||
|
||||
|
||||
def gen_path(str_list, challenges): # pylint: disable=missing-docstring
|
||||
def gen_path(str_list, challenges):
|
||||
"""Generate a path for challenge messages
|
||||
|
||||
:param list str_list: challenge message types (:class:`str`)
|
||||
:param dict challenges: ACME challenge messages
|
||||
|
||||
:return: :class:`list` of :class:`int`
|
||||
|
||||
"""
|
||||
path = []
|
||||
for i, chall in enumerate(challenges):
|
||||
for str_chall in str_list:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Tests for letsencrypt.client.le_util."""
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
|
@ -33,11 +34,11 @@ class MakeOrVerifyDirTest(unittest.TestCase):
|
|||
path = os.path.join(self.root_path, 'bar')
|
||||
self._call(path, 0o650)
|
||||
self.assertTrue(os.path.isdir(path))
|
||||
# TODO: check mode
|
||||
self.assertEqual(stat.S_IMODE(os.stat(path).st_mode), 0o650)
|
||||
|
||||
def test_existing_correct_mode_does_not_fail(self):
|
||||
self._call(self.path, 0o400)
|
||||
# TODO: check mode
|
||||
self.assertEqual(stat.S_IMODE(os.stat(self.path).st_mode), 0o400)
|
||||
|
||||
def test_existing_wrong_mode_fails(self):
|
||||
self.assertRaises(Exception, self._call, self.path, 0o600)
|
||||
|
|
|
|||
Loading…
Reference in a new issue