From 26a25a705382f5de40d390e15929ed5f6b0b9b96 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Wed, 17 Feb 2016 18:34:37 -0800 Subject: [PATCH 01/29] allow users to choose how many config changes are shown --- letsencrypt/cli.py | 5 ++++- letsencrypt/client.py | 4 ++-- letsencrypt/reverter.py | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 855c7a467..30654ea06 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1059,7 +1059,7 @@ def config_changes(config, unused_plugins): View checkpoints and associated configuration changes. """ - client.view_config_changes(config) + client.view_config_changes(config, num=config.num) def plugins_cmd(config, plugins): # TODO: Use IDisplay rather than print @@ -1633,6 +1633,9 @@ def _create_subparsers(helpful): helpful.add_group("revoke", description="Options for revocation of certs") helpful.add_group("rollback", description="Options for reverting config changes") helpful.add_group("plugins", description="Plugin options") + helpful.add_group("config_changes", description="Options for showing a history of config changes") + helpful.add("config_changes", "--num", type=int, + help="How many past revisions you want to be displayed") helpful.add( None, "--user-agent", default=None, help="Set a custom user agent string for the client. User agent strings allow " diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 9dfa70e8d..149fdbbe9 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -543,7 +543,7 @@ def rollback(default_installer, checkpoints, config, plugins): installer.restart() -def view_config_changes(config): +def view_config_changes(config, num=None): """View checkpoints and associated configuration changes. .. note:: This assumes that the installation is using a Reverter object. @@ -554,7 +554,7 @@ def view_config_changes(config): """ rev = reverter.Reverter(config) rev.recovery_routine() - rev.view_config_changes() + rev.view_config_changes(num) def _save_chain(chain_pem, chain_path): diff --git a/letsencrypt/reverter.py b/letsencrypt/reverter.py index 863074374..ea54a91ee 100644 --- a/letsencrypt/reverter.py +++ b/letsencrypt/reverter.py @@ -94,7 +94,7 @@ class Reverter(object): "Unable to load checkpoint during rollback") rollback -= 1 - def view_config_changes(self, for_logging=False): + def view_config_changes(self, for_logging=False, num=None): """Displays all saved checkpoints. All checkpoints are printed by @@ -107,7 +107,8 @@ class Reverter(object): """ backups = os.listdir(self.config.backup_dir) backups.sort(reverse=True) - + if num: + backups = backups[:num] if not backups: logger.info("The Let's Encrypt client has not saved any backups " "of your configuration") From 6a7c3ada654bee5401ae268f8e3160e8e3ccc0a1 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 18 Feb 2016 14:49:57 -0800 Subject: [PATCH 02/29] lint fix --- letsencrypt/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 30654ea06..0422a8c6c 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1633,7 +1633,8 @@ def _create_subparsers(helpful): helpful.add_group("revoke", description="Options for revocation of certs") helpful.add_group("rollback", description="Options for reverting config changes") helpful.add_group("plugins", description="Plugin options") - helpful.add_group("config_changes", description="Options for showing a history of config changes") + helpful.add_group("config_changes", + description="Options for showing a history of config changes") helpful.add("config_changes", "--num", type=int, help="How many past revisions you want to be displayed") helpful.add( From a2e8c5bde8194f846020360b07781d3b392612d6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Feb 2016 13:01:58 -0800 Subject: [PATCH 03/29] Remove cont_auth from auth_handler --- letsencrypt/auth_handler.py | 52 ++++++-------------------- letsencrypt/client.py | 5 +-- letsencrypt/tests/auth_handler_test.py | 36 ++++++------------ 3 files changed, 25 insertions(+), 68 deletions(-) diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index ffbd70ced..f142346cc 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -25,10 +25,6 @@ class AuthHandler(object): :class:`~acme.challenges.DVChallenge` types :type dv_auth: :class:`letsencrypt.interfaces.IAuthenticator` - :ivar cont_auth: Authenticator capable of solving - :class:`~acme.challenges.ContinuityChallenge` types - :type cont_auth: :class:`letsencrypt.interfaces.IAuthenticator` - :ivar acme.client.Client acme: ACME client API. :ivar account: Client's Account @@ -38,13 +34,10 @@ class AuthHandler(object): and values are :class:`acme.messages.AuthorizationResource` :ivar list dv_c: DV challenges in the form of :class:`letsencrypt.achallenges.AnnotatedChallenge` - :ivar list cont_c: Continuity challenges in the - form of :class:`letsencrypt.achallenges.AnnotatedChallenge` """ - def __init__(self, dv_auth, cont_auth, acme, account): + def __init__(self, dv_auth, acme, account): self.dv_auth = dv_auth - self.cont_auth = cont_auth self.acme = acme self.account = account @@ -52,7 +45,6 @@ class AuthHandler(object): # List must be used to keep responses straight. self.dv_c = [] - self.cont_c = [] def get_authorizations(self, domains, best_effort=False): """Retrieve all authorizations for challenges. @@ -76,12 +68,12 @@ class AuthHandler(object): self._choose_challenges(domains) # While there are still challenges remaining... - while self.dv_c or self.cont_c: - cont_resp, dv_resp = self._solve_challenges() + while self.dv_c: + dv_resp = self._solve_challenges() logger.info("Waiting for verification...") - # Send all Responses - this modifies dv_c and cont_c - self._respond(cont_resp, dv_resp, best_effort) + # Send all Responses - this modifies dv_c + self._respond(dv_resp, best_effort) # Just make sure all decisions are complete. self.verify_authzr_complete() @@ -98,19 +90,15 @@ class AuthHandler(object): self._get_chall_pref(dom), self.authzr[dom].body.combinations) - dom_cont_c, dom_dv_c = self._challenge_factory( + dom_dv_c = self._challenge_factory( dom, path) self.dv_c.extend(dom_dv_c) - self.cont_c.extend(dom_cont_c) def _solve_challenges(self): """Get Responses for challenges from authenticators.""" - cont_resp = [] dv_resp = [] with error_handler.ErrorHandler(self._cleanup_challenges): try: - if self.cont_c: - cont_resp = self.cont_auth.perform(self.cont_c) if self.dv_c: dv_resp = self.dv_auth.perform(self.dv_c) except errors.AuthorizationError: @@ -118,12 +106,11 @@ class AuthHandler(object): logger.info("Attempting to clean up outstanding challenges...") raise - assert len(cont_resp) == len(self.cont_c) assert len(dv_resp) == len(self.dv_c) - return cont_resp, dv_resp + return dv_resp - def _respond(self, cont_resp, dv_resp, best_effort): + def _respond(self, dv_resp, best_effort): """Send/Receive confirmation of all challenges. .. note:: This method also cleans up the auth_handler state. @@ -134,14 +121,12 @@ class AuthHandler(object): active_achalls = [] active_achalls.extend( self._send_responses(self.dv_c, dv_resp, chall_update)) - active_achalls.extend( - self._send_responses(self.cont_c, cont_resp, chall_update)) # Check for updated status... try: self._poll_challenges(chall_update, best_effort) finally: - # This removes challenges from self.dv_c and self.cont_c + # This removes challenges from self.dv_c self._cleanup_challenges(active_achalls) def _send_responses(self, achalls, resps, chall_update): @@ -255,7 +240,6 @@ class AuthHandler(object): """ # Make sure to make a copy... chall_prefs = [] - chall_prefs.extend(self.cont_auth.get_chall_pref(domain)) chall_prefs.extend(self.dv_auth.get_chall_pref(domain)) return chall_prefs @@ -269,21 +253,14 @@ class AuthHandler(object): if achall_list is None: dv_c = self.dv_c - cont_c = self.cont_c else: dv_c = [achall for achall in achall_list if isinstance(achall.chall, challenges.DVChallenge)] - cont_c = [achall for achall in achall_list if isinstance( - achall.chall, challenges.ContinuityChallenge)] if dv_c: self.dv_auth.cleanup(dv_c) for achall in dv_c: self.dv_c.remove(achall) - if cont_c: - self.cont_auth.cleanup(cont_c) - for achall in cont_c: - self.cont_c.remove(achall) def verify_authzr_complete(self): """Verifies that all authorizations have been decided. @@ -306,15 +283,12 @@ class AuthHandler(object): :returns: dv_chall, list of DVChallenge type :class:`letsencrypt.achallenges.Indexed` - cont_chall, list of ContinuityChallenge type - :class:`letsencrypt.achallenges.Indexed` - :rtype: tuple + :rtype: list :raises .errors.Error: if challenge type is not recognized """ dv_chall = [] - cont_chall = [] for index in path: challb = self.authzr[domain].body.challenges[index] @@ -322,12 +296,10 @@ class AuthHandler(object): achall = challb_to_achall(challb, self.account.key, domain) - if isinstance(chall, challenges.ContinuityChallenge): - cont_chall.append(achall) - elif isinstance(chall, challenges.DVChallenge): + if isinstance(chall, challenges.DVChallenge): dv_chall.append(achall) - return cont_chall, dv_chall + return dv_chall def challb_to_achall(challb, account_key, domain): diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 9dfa70e8d..d1d1c3547 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -17,7 +17,6 @@ from letsencrypt import account from letsencrypt import auth_handler from letsencrypt import configuration from letsencrypt import constants -from letsencrypt import continuity_auth from letsencrypt import crypto_util from letsencrypt import errors from letsencrypt import error_handler @@ -188,10 +187,8 @@ class Client(object): # standalone (then default is False, otherwise default is True) if dv_auth is not None: - cont_auth = continuity_auth.ContinuityAuthenticator(config, - installer) self.auth_handler = auth_handler.AuthHandler( - dv_auth, cont_auth, self.acme, self.account) + dv_auth, self.acme, self.account) else: self.auth_handler = None diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index 5a6199ca3..6eefa6577 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -23,8 +23,7 @@ class ChallengeFactoryTest(unittest.TestCase): from letsencrypt.auth_handler import AuthHandler # Account is mocked... - self.handler = AuthHandler( - None, None, None, mock.Mock(key="mock_key")) + self.handler = AuthHandler(None, None, mock.Mock(key="mock_key")) self.dom = "test" self.handler.authzr[self.dom] = acme_util.gen_authzr( @@ -32,19 +31,15 @@ class ChallengeFactoryTest(unittest.TestCase): [messages.STATUS_PENDING] * 6, False) def test_all(self): - cont_c, dv_c = self.handler._challenge_factory( + dv_c = self.handler._challenge_factory( self.dom, range(0, len(acme_util.CHALLENGES))) - self.assertEqual( - [achall.chall for achall in cont_c], acme_util.CONT_CHALLENGES) self.assertEqual( [achall.chall for achall in dv_c], acme_util.DV_CHALLENGES) - def test_one_dv_one_cont(self): - cont_c, dv_c = self.handler._challenge_factory(self.dom, [1, 3]) + def test_one_dv(self): + dv_c = self.handler._challenge_factory(self.dom, [1, 3]) - self.assertEqual( - [achall.chall for achall in cont_c], [acme_util.RECOVERY_CONTACT]) self.assertEqual([achall.chall for achall in dv_c], [acme_util.TLSSNI01]) def test_unrecognized(self): @@ -68,21 +63,16 @@ class GetAuthorizationsTest(unittest.TestCase): from letsencrypt.auth_handler import AuthHandler self.mock_dv_auth = mock.MagicMock(name="ApacheConfigurator") - self.mock_cont_auth = mock.MagicMock(name="ContinuityAuthenticator") self.mock_dv_auth.get_chall_pref.return_value = [challenges.TLSSNI01] - self.mock_cont_auth.get_chall_pref.return_value = [ - challenges.RecoveryContact] - self.mock_cont_auth.perform.side_effect = gen_auth_resp self.mock_dv_auth.perform.side_effect = gen_auth_resp self.mock_account = mock.Mock(key=le_util.Key("file_path", "PEM")) self.mock_net = mock.MagicMock(spec=acme_client.Client) self.handler = AuthHandler( - self.mock_dv_auth, self.mock_cont_auth, - self.mock_net, self.mock_account) + self.mock_dv_auth, self.mock_net, self.mock_account) logging.disable(logging.CRITICAL) @@ -106,7 +96,6 @@ class GetAuthorizationsTest(unittest.TestCase): self.assertEqual(len(chall_update.values()), 1) self.assertEqual(self.mock_dv_auth.cleanup.call_count, 1) - self.assertEqual(self.mock_cont_auth.cleanup.call_count, 0) # Test if list first element is TLSSNI01, use typ because it is an achall self.assertEqual( self.mock_dv_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") @@ -114,29 +103,28 @@ class GetAuthorizationsTest(unittest.TestCase): self.assertEqual(len(authzr), 1) @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") - def test_name3_tls_sni_01_3_rectok_3(self, mock_poll): + def test_name3_tls_sni_01_3(self, mock_poll): self.mock_net.request_domain_challenges.side_effect = functools.partial( - gen_dom_authzr, challs=acme_util.CHALLENGES) + gen_dom_authzr, challs=acme_util.DV_CHALLENGES) mock_poll.side_effect = self._validate_all authzr = self.handler.get_authorizations(["0", "1", "2"]) - self.assertEqual(self.mock_net.answer_challenge.call_count, 6) + self.assertEqual(self.mock_net.answer_challenge.call_count, 3) # Check poll call self.assertEqual(mock_poll.call_count, 1) chall_update = mock_poll.call_args[0][0] self.assertEqual(len(chall_update.keys()), 3) self.assertTrue("0" in chall_update.keys()) - self.assertEqual(len(chall_update["0"]), 2) + self.assertEqual(len(chall_update["0"]), 1) self.assertTrue("1" in chall_update.keys()) - self.assertEqual(len(chall_update["1"]), 2) + self.assertEqual(len(chall_update["1"]), 1) self.assertTrue("2" in chall_update.keys()) - self.assertEqual(len(chall_update["2"]), 2) + self.assertEqual(len(chall_update["2"]), 1) self.assertEqual(self.mock_dv_auth.cleanup.call_count, 1) - self.assertEqual(self.mock_cont_auth.cleanup.call_count, 1) self.assertEqual(len(authzr), 3) @@ -170,7 +158,7 @@ class PollChallengesTest(unittest.TestCase): # Account and network are mocked... self.mock_net = mock.MagicMock() self.handler = AuthHandler( - None, None, self.mock_net, mock.Mock(key="mock_key")) + None, self.mock_net, mock.Mock(key="mock_key")) self.doms = ["0", "1", "2"] self.handler.authzr[self.doms[0]] = acme_util.gen_authzr( From 6c4e29fb49e995f915a884574a510da60d6c5e83 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Feb 2016 13:09:00 -0800 Subject: [PATCH 04/29] fix test_perform_failure --- letsencrypt/tests/auth_handler_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index 6eefa6577..e0b7958f4 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -130,7 +130,7 @@ class GetAuthorizationsTest(unittest.TestCase): def test_perform_failure(self): self.mock_net.request_domain_challenges.side_effect = functools.partial( - gen_dom_authzr, challs=acme_util.CHALLENGES) + gen_dom_authzr, challs=acme_util.DV_CHALLENGES) self.mock_dv_auth.perform.side_effect = errors.AuthorizationError self.assertRaises( From f1eacc74615d3e3f00837dd54066dbeb1ab02226 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Feb 2016 15:32:11 -0800 Subject: [PATCH 05/29] simplify code when no combos in authzr --- letsencrypt/auth_handler.py | 59 ++++++-------------- letsencrypt/constants.py | 5 -- letsencrypt/tests/acme_util.py | 11 +++- letsencrypt/tests/auth_handler_test.py | 76 +------------------------- 4 files changed, 27 insertions(+), 124 deletions(-) diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index f142346cc..bf37c3c92 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -9,7 +9,6 @@ from acme import challenges from acme import messages from letsencrypt import achallenges -from letsencrypt import constants from letsencrypt import errors from letsencrypt import error_handler from letsencrypt import interfaces @@ -396,10 +395,7 @@ def _find_smart_path(challbs, preferences, combinations): combo_total = 0 if not best_combo: - msg = ("Client does not support any combination of challenges that " - "will satisfy the CA.") - logger.fatal(msg) - raise errors.AuthorizationError(msg) + _report_no_chall_path() return best_combo @@ -408,48 +404,29 @@ def _find_dumb_path(challbs, preferences): """Find challenge path without server hints. Should be called if the combinations hint is not included by the - server. This function returns the best path that does not contain - multiple mutually exclusive challenges. + server. This function either returns a path containing all + challenges provided by the CA or raises an exception. """ - assert len(preferences) == len(set(preferences)) - path = [] - satisfied = set() - for pref_c in preferences: - for i, offered_challb in enumerate(challbs): - if (isinstance(offered_challb.chall, pref_c) and - is_preferred(offered_challb, satisfied)): - path.append(i) - satisfied.add(offered_challb) + for i, challb in enumerate(challbs): + # supported is set to True if the challenge type is supported + supported = next((True for pref_c in preferences + if isinstance(challb.chall, pref_c)), False) + if supported: + path.append(i) + else: + _report_no_chall_path() + return path -def mutually_exclusive(obj1, obj2, groups, different=False): - """Are two objects mutually exclusive?""" - for group in groups: - obj1_present = False - obj2_present = False - - for obj_cls in group: - obj1_present |= isinstance(obj1, obj_cls) - obj2_present |= isinstance(obj2, obj_cls) - - if obj1_present and obj2_present and ( - not different or not isinstance(obj1, obj2.__class__)): - return False - return True - - -def is_preferred(offered_challb, satisfied, - exclusive_groups=constants.EXCLUSIVE_CHALLENGES): - """Return whether or not the challenge is preferred in path.""" - for challb in satisfied: - if not mutually_exclusive( - offered_challb.chall, challb.chall, exclusive_groups, - different=True): - return False - return True +def _report_no_chall_path(): + """Logs and raises an error that no satisfiable chall path exists.""" + msg = ("Client with the currently selected authenticator does not support " + "any combination of challenges that will satisfy the CA.") + logger.fatal(msg) + raise errors.AuthorizationError(msg) _ACME_PREFIX = "urn:acme:error:" diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 402f5e9a1..f8ef1e845 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -44,11 +44,6 @@ RENEWER_DEFAULTS = dict( """Defaults for renewer script.""" -EXCLUSIVE_CHALLENGES = frozenset([frozenset([ - challenges.TLSSNI01, challenges.HTTP01])]) -"""Mutually exclusive challenges.""" - - ENHANCEMENTS = ["redirect", "http-header", "ocsp-stapling", "spdy"] """List of possible :class:`letsencrypt.interfaces.IInstaller` enhancements. diff --git a/letsencrypt/tests/acme_util.py b/letsencrypt/tests/acme_util.py index 6b07b840f..71ebb471d 100644 --- a/letsencrypt/tests/acme_util.py +++ b/letsencrypt/tests/acme_util.py @@ -59,9 +59,14 @@ def gen_combos(challbs): else: cont_chall.append(i) - # Gen combos for 1 of each type, lowest index first (makes testing easier) - return tuple((i, j) if i < j else (j, i) - for i in dv_chall for j in cont_chall) + if cont_chall: + # Gen combos for 1 of each type, lowest + # index included first (makes testing easier) + return tuple((i, j) if i < j else (j, i) + for i in dv_chall for j in cont_chall) + else: + # completing a single DV chall satisfies the CA + return tuple((i,) for i in dv_chall) def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index e0b7958f4..f4c09a5ba 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -299,7 +299,7 @@ class GenChallengePathTest(unittest.TestCase): def test_common_case(self): """Given TLSSNI01 and HTTP01 with appropriate combos.""" challbs = (acme_util.TLSSNI01_P, acme_util.HTTP01_P) - prefs = [challenges.TLSSNI01] + prefs = [challenges.TLSSNI01, challenges.HTTP01] combos = ((0,), (1,)) # Smart then trivial dumb path test @@ -318,9 +318,6 @@ class GenChallengePathTest(unittest.TestCase): combos = acme_util.gen_combos(challbs) self.assertEqual(self._call(challbs, prefs, combos), (0, 2)) - # dumb_path() trivial test - self.assertTrue(self._call(challbs, prefs, None)) - def test_full_cont_server(self): challbs = (acme_util.RECOVERY_CONTACT_P, acme_util.POP_P, @@ -336,9 +333,6 @@ class GenChallengePathTest(unittest.TestCase): combos = acme_util.gen_combos(challbs) self.assertEqual(self._call(challbs, prefs, combos), (1, 3)) - # Dumb path trivial test - self.assertTrue(self._call(challbs, prefs, None)) - def test_not_supported(self): challbs = (acme_util.POP_P, acme_util.TLSSNI01_P) prefs = [challenges.TLSSNI01] @@ -348,74 +342,6 @@ class GenChallengePathTest(unittest.TestCase): errors.AuthorizationError, self._call, challbs, prefs, combos) -class MutuallyExclusiveTest(unittest.TestCase): - """Tests for letsencrypt.auth_handler.mutually_exclusive.""" - - # pylint: disable=missing-docstring,too-few-public-methods - class A(object): - pass - - class B(object): - pass - - class C(object): - pass - - class D(C): - pass - - @classmethod - def _call(cls, chall1, chall2, different=False): - from letsencrypt.auth_handler import mutually_exclusive - return mutually_exclusive(chall1, chall2, groups=frozenset([ - frozenset([cls.A, cls.B]), frozenset([cls.A, cls.C]), - ]), different=different) - - def test_group_members(self): - self.assertFalse(self._call(self.A(), self.B())) - self.assertFalse(self._call(self.A(), self.C())) - - def test_cross_group(self): - self.assertTrue(self._call(self.B(), self.C())) - - def test_same_type(self): - self.assertFalse(self._call(self.A(), self.A(), different=False)) - self.assertTrue(self._call(self.A(), self.A(), different=True)) - - # in particular... - obj = self.A() - self.assertFalse(self._call(obj, obj, different=False)) - self.assertTrue(self._call(obj, obj, different=True)) - - def test_subclass(self): - self.assertFalse(self._call(self.A(), self.D())) - self.assertFalse(self._call(self.D(), self.A())) - - -class IsPreferredTest(unittest.TestCase): - """Tests for letsencrypt.auth_handler.is_preferred.""" - - @classmethod - def _call(cls, chall, satisfied): - from letsencrypt.auth_handler import is_preferred - return is_preferred(chall, satisfied, exclusive_groups=frozenset([ - frozenset([challenges.TLSSNI01, challenges.HTTP01]), - frozenset([challenges.DNS, challenges.HTTP01]), - ])) - - def test_empty_satisfied(self): - self.assertTrue(self._call(acme_util.DNS_P, frozenset())) - - def test_mutually_exclusvie(self): - self.assertFalse( - self._call( - acme_util.TLSSNI01_P, frozenset([acme_util.HTTP01_P]))) - - def test_mutually_exclusive_same_type(self): - self.assertTrue( - self._call(acme_util.TLSSNI01_P, frozenset([acme_util.TLSSNI01_P]))) - - class ReportFailedChallsTest(unittest.TestCase): """Tests for letsencrypt.auth_handler._report_failed_challs.""" # pylint: disable=protected-access From 50267acb272d1bcaa2e32adef0067709e5b028de Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Feb 2016 15:36:22 -0800 Subject: [PATCH 06/29] test dumb path failure --- letsencrypt/tests/auth_handler_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index f4c09a5ba..6f73c35d7 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -338,8 +338,12 @@ class GenChallengePathTest(unittest.TestCase): prefs = [challenges.TLSSNI01] combos = ((0, 1),) + # smart path fails because no challs in perfs satisfies combos self.assertRaises( errors.AuthorizationError, self._call, challbs, prefs, combos) + # dumb path fails because all challbs are not supported + self.assertRaises( + errors.AuthorizationError, self._call, challbs, prefs, None) class ReportFailedChallsTest(unittest.TestCase): From bc4f01cb6e49fb7b4c4fbb4e08a43789aaf5d75a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Feb 2016 15:41:56 -0800 Subject: [PATCH 07/29] test successful dumb path use --- letsencrypt/tests/auth_handler_test.py | 29 ++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index 6f73c35d7..6448324b5 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -102,6 +102,31 @@ class GetAuthorizationsTest(unittest.TestCase): self.assertEqual(len(authzr), 1) + @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") + def test_name1_tls_sni_01_1_http_01_1_dns_1(self, mock_poll): + self.mock_net.request_domain_challenges.side_effect = functools.partial( + gen_dom_authzr, challs=acme_util.DV_CHALLENGES, combos=False) + + mock_poll.side_effect = self._validate_all + self.mock_dv_auth.get_chall_pref.return_value.append(challenges.HTTP01) + self.mock_dv_auth.get_chall_pref.return_value.append(challenges.DNS) + + authzr = self.handler.get_authorizations(["0"]) + + self.assertEqual(self.mock_net.answer_challenge.call_count, 3) + + self.assertEqual(mock_poll.call_count, 1) + chall_update = mock_poll.call_args[0][0] + self.assertEqual(chall_update.keys(), ["0"]) + self.assertEqual(len(chall_update.values()), 1) + + self.assertEqual(self.mock_dv_auth.cleanup.call_count, 1) + # Test if list first element is TLSSNI01, use typ because it is an achall + for achall in self.mock_dv_auth.cleanup.call_args[0][0]: + self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns"]) + + self.assertEqual(len(authzr), 1) + @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") def test_name3_tls_sni_01_3(self, mock_poll): self.mock_net.request_domain_challenges.side_effect = functools.partial( @@ -404,11 +429,11 @@ def gen_auth_resp(chall_list): for chall in chall_list] -def gen_dom_authzr(domain, unused_new_authzr_uri, challs): +def gen_dom_authzr(domain, unused_new_authzr_uri, challs, combos=True): """Generates new authzr for domains.""" return acme_util.gen_authzr( messages.STATUS_PENDING, domain, challs, - [messages.STATUS_PENDING] * len(challs)) + [messages.STATUS_PENDING] * len(challs), combos) if __name__ == "__main__": From 7e6002a13f404c33449c805a078310e9abf6ec41 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Feb 2016 15:43:58 -0800 Subject: [PATCH 08/29] Remove continuity authenticator --- letsencrypt/continuity_auth.py | 54 ------------------ letsencrypt/tests/continuity_auth_test.py | 67 ----------------------- 2 files changed, 121 deletions(-) delete mode 100644 letsencrypt/continuity_auth.py delete mode 100644 letsencrypt/tests/continuity_auth_test.py diff --git a/letsencrypt/continuity_auth.py b/letsencrypt/continuity_auth.py deleted file mode 100644 index 28612bb17..000000000 --- a/letsencrypt/continuity_auth.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Continuity Authenticator""" -import zope.interface - -from acme import challenges - -from letsencrypt import achallenges -from letsencrypt import errors -from letsencrypt import interfaces -from letsencrypt import proof_of_possession - - -@zope.interface.implementer(interfaces.IAuthenticator) -class ContinuityAuthenticator(object): - """IAuthenticator for - :const:`~acme.challenges.ContinuityChallenge` class challenges. - - :ivar proof_of_pos: Performs "proofOfPossession" challenges. - :type proof_of_pos: - :class:`letsencrypt.proof_of_possession.Proof_of_Possession` - - """ - - # This will have an installer soon for get_key/cert purposes - def __init__(self, config, installer): # pylint: disable=unused-argument - """Initialize Client Authenticator. - - :param config: Configuration. - :type config: :class:`letsencrypt.interfaces.IConfig` - - :param installer: Let's Encrypt Installer. - :type installer: :class:`letsencrypt.interfaces.IInstaller` - - """ - self.proof_of_pos = proof_of_possession.ProofOfPossession(installer) - - def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use - """Return list of challenge preferences.""" - return [challenges.ProofOfPossession] - - def perform(self, achalls): - """Perform client specific challenges for IAuthenticator""" - responses = [] - for achall in achalls: - if isinstance(achall, achallenges.ProofOfPossession): - responses.append(self.proof_of_pos.perform(achall)) - else: - raise errors.ContAuthError("Unexpected Challenge") - return responses - - def cleanup(self, achalls): # pylint: disable=no-self-use - """Cleanup call for IAuthenticator.""" - for achall in achalls: - if not isinstance(achall, achallenges.ProofOfPossession): - raise errors.ContAuthError("Unexpected Challenge") diff --git a/letsencrypt/tests/continuity_auth_test.py b/letsencrypt/tests/continuity_auth_test.py deleted file mode 100644 index 70287bd01..000000000 --- a/letsencrypt/tests/continuity_auth_test.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Test for letsencrypt.continuity_auth.""" -import unittest - -import mock - -from acme import challenges - -from letsencrypt import achallenges -from letsencrypt import errors - - -class PerformTest(unittest.TestCase): - """Test client perform function.""" - - def setUp(self): - from letsencrypt.continuity_auth import ContinuityAuthenticator - - self.auth = ContinuityAuthenticator( - mock.MagicMock(server="demo_server.org"), None) - self.auth.proof_of_pos.perform = mock.MagicMock( - name="proof_of_pos_perform", side_effect=gen_client_resp) - - def test_pop(self): - achalls = [] - for i in xrange(4): - achalls.append(achallenges.ProofOfPossession( - challb=None, domain=str(i))) - responses = self.auth.perform(achalls) - - self.assertEqual(len(responses), 4) - for i in xrange(4): - self.assertEqual(responses[i], "ProofOfPossession%d" % i) - - def test_unexpected(self): - self.assertRaises( - errors.ContAuthError, self.auth.perform, [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=None, domain="0", account_key="invalid_key")]) - - def test_chall_pref(self): - self.assertEqual( - self.auth.get_chall_pref("example.com"), - [challenges.ProofOfPossession]) - - -class CleanupTest(unittest.TestCase): - """Test the Authenticator cleanup function.""" - - def setUp(self): - from letsencrypt.continuity_auth import ContinuityAuthenticator - - self.auth = ContinuityAuthenticator( - mock.MagicMock(server="demo_server.org"), None) - - def test_unexpected(self): - unexpected = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=None, domain="0", account_key="dummy_key") - self.assertRaises(errors.ContAuthError, self.auth.cleanup, [unexpected]) - - -def gen_client_resp(chall): - """Generate a dummy response.""" - return "%s%s" % (chall.__class__.__name__, chall.domain) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover From cf12f8702761a14ce55d89a2f75c23a996032c83 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 13:16:19 -0800 Subject: [PATCH 09/29] Remove SIGFILEBALL after creating sig --- tools/offline-sigrequest.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/offline-sigrequest.sh b/tools/offline-sigrequest.sh index 7706796ef..08a5c4c05 100755 --- a/tools/offline-sigrequest.sh +++ b/tools/offline-sigrequest.sh @@ -42,6 +42,7 @@ function oncesigned { # $1 <-- INPFILE ; $2 <--SIGFILE echo `file $2` exit 1 fi + rm $SIGFILEBALL } HERE=`dirname $0` From d3cc2b187c1931f4aa42cc4d02445234376f16e1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 14:49:51 -0800 Subject: [PATCH 10/29] remove outdated comment --- letsencrypt/client.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index d1d1c3547..bda63f6b7 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -182,10 +182,6 @@ class Client(object): acme = acme_from_config_key(config, self.account.key) self.acme = acme - # TODO: Check if self.config.enroll_autorenew is None. If - # so, set it based to the default: figure out if dv_auth is - # standalone (then default is False, otherwise default is True) - if dv_auth is not None: self.auth_handler = auth_handler.AuthHandler( dv_auth, self.acme, self.account) From 32c4f801174e6977b9d081333449b4a4128ef5ed Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 14:53:10 -0800 Subject: [PATCH 11/29] remove distinction between dv_auth and auth --- letsencrypt/client.py | 10 +++++----- letsencrypt/tests/client_test.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index bda63f6b7..99f211beb 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -162,7 +162,7 @@ class Client(object): :ivar .AuthHandler auth_handler: Authorizations handler that will dispatch DV and Continuity challenges to appropriate authenticators (providing `.IAuthenticator` interface). - :ivar .IAuthenticator dv_auth: Prepared (`.IAuthenticator.prepare`) + :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`) authenticator that can solve the `.constants.DV_CHALLENGES`. :ivar .IInstaller installer: Installer. :ivar acme.client.Client acme: Optional ACME client API handle. @@ -170,11 +170,11 @@ class Client(object): """ - def __init__(self, config, account_, dv_auth, installer, acme=None): + def __init__(self, config, account_, auth, installer, acme=None): """Initialize a client.""" self.config = config self.account = account_ - self.dv_auth = dv_auth + self.auth = auth self.installer = installer # Initialize ACME if account is provided @@ -182,9 +182,9 @@ class Client(object): acme = acme_from_config_key(config, self.account.key) self.acme = acme - if dv_auth is not None: + if auth is not None: self.auth_handler = auth_handler.AuthHandler( - dv_auth, self.acme, self.account) + auth, self.acme, self.account) else: self.auth_handler = None diff --git a/letsencrypt/tests/client_test.py b/letsencrypt/tests/client_test.py index b1a3fc779..7dd513e18 100644 --- a/letsencrypt/tests/client_test.py +++ b/letsencrypt/tests/client_test.py @@ -107,7 +107,7 @@ class ClientTest(unittest.TestCase): self.acme = acme.return_value = mock.MagicMock() self.client = Client( config=self.config, account_=self.account, - dv_auth=None, installer=None) + auth=None, installer=None) def test_init_acme_verify_ssl(self): net = self.acme_client.call_args[1]["net"] From 8266dde2bbe7ebbe61cdb65d0739c3d5a98ebaa8 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 15:13:58 -0800 Subject: [PATCH 12/29] clean up references to dv_challs and dv_auth --- letsencrypt/auth_handler.py | 64 +++++++++++++------------- letsencrypt/tests/auth_handler_test.py | 35 +++++++------- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index bf37c3c92..a324dd4b7 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -20,9 +20,9 @@ logger = logging.getLogger(__name__) class AuthHandler(object): """ACME Authorization Handler for a client. - :ivar dv_auth: Authenticator capable of solving + :ivar auth: Authenticator capable of solving :class:`~acme.challenges.DVChallenge` types - :type dv_auth: :class:`letsencrypt.interfaces.IAuthenticator` + :type auth: :class:`letsencrypt.interfaces.IAuthenticator` :ivar acme.client.Client acme: ACME client API. @@ -31,19 +31,19 @@ class AuthHandler(object): :ivar dict authzr: ACME Authorization Resource dict where keys are domains and values are :class:`acme.messages.AuthorizationResource` - :ivar list dv_c: DV challenges in the form of + :ivar list achalls: DV challenges in the form of :class:`letsencrypt.achallenges.AnnotatedChallenge` """ - def __init__(self, dv_auth, acme, account): - self.dv_auth = dv_auth + def __init__(self, auth, acme, account): + self.auth = auth self.acme = acme self.account = account self.authzr = dict() # List must be used to keep responses straight. - self.dv_c = [] + self.achalls = [] def get_authorizations(self, domains, best_effort=False): """Retrieve all authorizations for challenges. @@ -67,12 +67,12 @@ class AuthHandler(object): self._choose_challenges(domains) # While there are still challenges remaining... - while self.dv_c: - dv_resp = self._solve_challenges() + while self.achalls: + resp = self._solve_challenges() logger.info("Waiting for verification...") - # Send all Responses - this modifies dv_c - self._respond(dv_resp, best_effort) + # Send all Responses - this modifies achalls + self._respond(resp, best_effort) # Just make sure all decisions are complete. self.verify_authzr_complete() @@ -89,27 +89,27 @@ class AuthHandler(object): self._get_chall_pref(dom), self.authzr[dom].body.combinations) - dom_dv_c = self._challenge_factory( + dom_achalls = self._challenge_factory( dom, path) - self.dv_c.extend(dom_dv_c) + self.achalls.extend(dom_achalls) def _solve_challenges(self): """Get Responses for challenges from authenticators.""" - dv_resp = [] + resp = [] with error_handler.ErrorHandler(self._cleanup_challenges): try: - if self.dv_c: - dv_resp = self.dv_auth.perform(self.dv_c) + if self.achalls: + resp = self.auth.perform(self.achalls) except errors.AuthorizationError: logger.critical("Failure in setting up challenges.") logger.info("Attempting to clean up outstanding challenges...") raise - assert len(dv_resp) == len(self.dv_c) + assert len(resp) == len(self.achalls) - return dv_resp + return resp - def _respond(self, dv_resp, best_effort): + def _respond(self, resp, best_effort): """Send/Receive confirmation of all challenges. .. note:: This method also cleans up the auth_handler state. @@ -119,13 +119,13 @@ class AuthHandler(object): chall_update = dict() active_achalls = [] active_achalls.extend( - self._send_responses(self.dv_c, dv_resp, chall_update)) + self._send_responses(self.achalls, resp, chall_update)) # Check for updated status... try: self._poll_challenges(chall_update, best_effort) finally: - # This removes challenges from self.dv_c + # This removes challenges from self.achalls self._cleanup_challenges(active_achalls) def _send_responses(self, achalls, resps, chall_update): @@ -239,7 +239,7 @@ class AuthHandler(object): """ # Make sure to make a copy... chall_prefs = [] - chall_prefs.extend(self.dv_auth.get_chall_pref(domain)) + chall_prefs.extend(self.auth.get_chall_pref(domain)) return chall_prefs def _cleanup_challenges(self, achall_list=None): @@ -251,15 +251,15 @@ class AuthHandler(object): logger.info("Cleaning up challenges") if achall_list is None: - dv_c = self.dv_c + achalls = self.achalls else: - dv_c = [achall for achall in achall_list - if isinstance(achall.chall, challenges.DVChallenge)] + achalls = [achall for achall in achall_list + if isinstance(achall.chall, challenges.DVChallenge)] - if dv_c: - self.dv_auth.cleanup(dv_c) - for achall in dv_c: - self.dv_c.remove(achall) + if achalls: + self.auth.cleanup(achalls) + for achall in achalls: + self.achalls.remove(achall) def verify_authzr_complete(self): """Verifies that all authorizations have been decided. @@ -280,14 +280,14 @@ class AuthHandler(object): :param list path: List of indices from `challenges`. - :returns: dv_chall, list of DVChallenge type + :returns: achalls, list of DVChallenge type :class:`letsencrypt.achallenges.Indexed` :rtype: list :raises .errors.Error: if challenge type is not recognized """ - dv_chall = [] + achalls = [] for index in path: challb = self.authzr[domain].body.challenges[index] @@ -296,9 +296,9 @@ class AuthHandler(object): achall = challb_to_achall(challb, self.account.key, domain) if isinstance(chall, challenges.DVChallenge): - dv_chall.append(achall) + achalls.append(achall) - return dv_chall + return achalls def challb_to_achall(challb, account_key, domain): diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index 6448324b5..dcf8a5ab3 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -31,16 +31,17 @@ class ChallengeFactoryTest(unittest.TestCase): [messages.STATUS_PENDING] * 6, False) def test_all(self): - dv_c = self.handler._challenge_factory( + achalls = self.handler._challenge_factory( self.dom, range(0, len(acme_util.CHALLENGES))) self.assertEqual( - [achall.chall for achall in dv_c], acme_util.DV_CHALLENGES) + [achall.chall for achall in achalls], acme_util.DV_CHALLENGES) - def test_one_dv(self): - dv_c = self.handler._challenge_factory(self.dom, [1, 3]) + def test_one_tls_sni(self): + achalls = self.handler._challenge_factory(self.dom, [1, 3]) - self.assertEqual([achall.chall for achall in dv_c], [acme_util.TLSSNI01]) + self.assertEqual( + [achall.chall for achall in achalls], [acme_util.TLSSNI01]) def test_unrecognized(self): self.handler.authzr["failure.com"] = acme_util.gen_authzr( @@ -62,17 +63,17 @@ class GetAuthorizationsTest(unittest.TestCase): def setUp(self): from letsencrypt.auth_handler import AuthHandler - self.mock_dv_auth = mock.MagicMock(name="ApacheConfigurator") + self.mock_auth = mock.MagicMock(name="ApacheConfigurator") - self.mock_dv_auth.get_chall_pref.return_value = [challenges.TLSSNI01] + self.mock_auth.get_chall_pref.return_value = [challenges.TLSSNI01] - self.mock_dv_auth.perform.side_effect = gen_auth_resp + self.mock_auth.perform.side_effect = gen_auth_resp self.mock_account = mock.Mock(key=le_util.Key("file_path", "PEM")) self.mock_net = mock.MagicMock(spec=acme_client.Client) self.handler = AuthHandler( - self.mock_dv_auth, self.mock_net, self.mock_account) + self.mock_auth, self.mock_net, self.mock_account) logging.disable(logging.CRITICAL) @@ -95,10 +96,10 @@ class GetAuthorizationsTest(unittest.TestCase): self.assertEqual(chall_update.keys(), ["0"]) self.assertEqual(len(chall_update.values()), 1) - self.assertEqual(self.mock_dv_auth.cleanup.call_count, 1) + self.assertEqual(self.mock_auth.cleanup.call_count, 1) # Test if list first element is TLSSNI01, use typ because it is an achall self.assertEqual( - self.mock_dv_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") self.assertEqual(len(authzr), 1) @@ -108,8 +109,8 @@ class GetAuthorizationsTest(unittest.TestCase): gen_dom_authzr, challs=acme_util.DV_CHALLENGES, combos=False) mock_poll.side_effect = self._validate_all - self.mock_dv_auth.get_chall_pref.return_value.append(challenges.HTTP01) - self.mock_dv_auth.get_chall_pref.return_value.append(challenges.DNS) + self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) + self.mock_auth.get_chall_pref.return_value.append(challenges.DNS) authzr = self.handler.get_authorizations(["0"]) @@ -120,9 +121,9 @@ class GetAuthorizationsTest(unittest.TestCase): self.assertEqual(chall_update.keys(), ["0"]) self.assertEqual(len(chall_update.values()), 1) - self.assertEqual(self.mock_dv_auth.cleanup.call_count, 1) + self.assertEqual(self.mock_auth.cleanup.call_count, 1) # Test if list first element is TLSSNI01, use typ because it is an achall - for achall in self.mock_dv_auth.cleanup.call_args[0][0]: + for achall in self.mock_auth.cleanup.call_args[0][0]: self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns"]) self.assertEqual(len(authzr), 1) @@ -149,14 +150,14 @@ class GetAuthorizationsTest(unittest.TestCase): self.assertTrue("2" in chall_update.keys()) self.assertEqual(len(chall_update["2"]), 1) - self.assertEqual(self.mock_dv_auth.cleanup.call_count, 1) + self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual(len(authzr), 3) def test_perform_failure(self): self.mock_net.request_domain_challenges.side_effect = functools.partial( gen_dom_authzr, challs=acme_util.DV_CHALLENGES) - self.mock_dv_auth.perform.side_effect = errors.AuthorizationError + self.mock_auth.perform.side_effect = errors.AuthorizationError self.assertRaises( errors.AuthorizationError, self.handler.get_authorizations, ["0"]) From d10aa9faa365649531b01adc2e7698131b2f8750 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 15:24:43 -0800 Subject: [PATCH 13/29] remove reference to continuity challenges --- letsencrypt/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 99f211beb..d0bdb1f85 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -160,8 +160,8 @@ class Client(object): :ivar .IConfig config: Client configuration. :ivar .Account account: Account registered with `register`. :ivar .AuthHandler auth_handler: Authorizations handler that will - dispatch DV and Continuity challenges to appropriate - authenticators (providing `.IAuthenticator` interface). + dispatch DV challenges to appropriate authenticators + (providing `.IAuthenticator` interface). :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`) authenticator that can solve the `.constants.DV_CHALLENGES`. :ivar .IInstaller installer: Installer. From 4f98fe963005cf47965edb089d76fc06edbc8c19 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 15:33:28 -0800 Subject: [PATCH 14/29] Remove PoP --- letsencrypt/proof_of_possession.py | 99 ------------------- letsencrypt/tests/proof_of_possession_test.py | 83 ---------------- 2 files changed, 182 deletions(-) delete mode 100644 letsencrypt/proof_of_possession.py delete mode 100644 letsencrypt/tests/proof_of_possession_test.py diff --git a/letsencrypt/proof_of_possession.py b/letsencrypt/proof_of_possession.py deleted file mode 100644 index 7928c60e7..000000000 --- a/letsencrypt/proof_of_possession.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Proof of Possession Identifier Validation Challenge.""" -import logging -import os - -from cryptography import x509 -from cryptography.hazmat.backends import default_backend -import zope.component - -from acme import challenges -from acme import jose -from acme import other - -from letsencrypt import interfaces -from letsencrypt.display import util as display_util - - -logger = logging.getLogger(__name__) - - -class ProofOfPossession(object): # pylint: disable=too-few-public-methods - """Proof of Possession Identifier Validation Challenge. - - Based on draft-barnes-acme, section 6.5. - - :ivar installer: Installer object - :type installer: :class:`~letsencrypt.interfaces.IInstaller` - - """ - def __init__(self, installer): - self.installer = installer - - def perform(self, achall): - """Perform the Proof of Possession Challenge. - - :param achall: Proof of Possession Challenge - :type achall: :class:`letsencrypt.achallenges.ProofOfPossession` - - :returns: Response or None/False if the challenge cannot be completed - :rtype: :class:`acme.challenges.ProofOfPossessionResponse` - or False - - """ - if (achall.alg in [jose.HS256, jose.HS384, jose.HS512] or - not isinstance(achall.hints.jwk, achall.alg.kty)): - return None - - for cert, key, _ in self.installer.get_all_certs_keys(): - with open(cert) as cert_file: - cert_data = cert_file.read() - try: - cert_obj = x509.load_pem_x509_certificate( - cert_data, default_backend()) - except ValueError: - try: - cert_obj = x509.load_der_x509_certificate( - cert_data, default_backend()) - except ValueError: - logger.warn("Certificate is neither PER nor DER: %s", cert) - - cert_key = achall.alg.kty(key=cert_obj.public_key()) - if cert_key == achall.hints.jwk: - return self._gen_response(achall, key) - - # Is there are different prompt we should give the user? - code, key = zope.component.getUtility( - interfaces.IDisplay).input( - "Path to private key for identifier: %s " % achall.domain) - if code != display_util.CANCEL: - return self._gen_response(achall, key) - - # If we get here, the key wasn't found - return False - - def _gen_response(self, achall, key_path): # pylint: disable=no-self-use - """Create the response to the Proof of Possession Challenge. - - :param achall: Proof of Possession Challenge - :type achall: :class:`letsencrypt.achallenges.ProofOfPossession` - - :param str key_path: Path to the key corresponding to the hinted to - public key. - - :returns: Response or False if the challenge cannot be completed - :rtype: :class:`acme.challenges.ProofOfPossessionResponse` - or False - - """ - if os.path.isfile(key_path): - with open(key_path, 'rb') as key: - try: - # Needs to be changed if JWKES doesn't have a key attribute - jwk = achall.alg.kty.load(key.read()) - sig = other.Signature.from_msg(achall.nonce, jwk.key, - alg=achall.alg) - except (IndexError, ValueError, TypeError, jose.errors.Error): - return False - return challenges.ProofOfPossessionResponse(nonce=achall.nonce, - signature=sig) - return False diff --git a/letsencrypt/tests/proof_of_possession_test.py b/letsencrypt/tests/proof_of_possession_test.py deleted file mode 100644 index f2e7b2021..000000000 --- a/letsencrypt/tests/proof_of_possession_test.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Tests for letsencrypt.proof_of_possession.""" -import os -import tempfile -import unittest - -import mock - -from acme import challenges -from acme import jose -from acme import messages - -from letsencrypt import achallenges -from letsencrypt import proof_of_possession -from letsencrypt.display import util as display_util - -from letsencrypt.tests import test_util - - -CERT0_PATH = test_util.vector_path("cert.der") -CERT2_PATH = test_util.vector_path("dsa_cert.pem") -CERT2_KEY_PATH = test_util.vector_path("dsa512_key.pem") -CERT3_PATH = test_util.vector_path("matching_cert.pem") -CERT3_KEY_PATH = test_util.vector_path("rsa512_key_2.pem") -CERT3_KEY = test_util.load_rsa_private_key("rsa512_key_2.pem").public_key() - - -class ProofOfPossessionTest(unittest.TestCase): - def setUp(self): - self.installer = mock.MagicMock() - self.cert1_path = tempfile.mkstemp()[1] - certs = [CERT0_PATH, self.cert1_path, CERT2_PATH, CERT3_PATH] - keys = [None, None, CERT2_KEY_PATH, CERT3_KEY_PATH] - self.installer.get_all_certs_keys.return_value = zip( - certs, keys, 4 * [None]) - self.proof_of_pos = proof_of_possession.ProofOfPossession( - self.installer) - - hints = challenges.ProofOfPossession.Hints( - jwk=jose.JWKRSA(key=CERT3_KEY), cert_fingerprints=(), - certs=(), serial_numbers=(), subject_key_identifiers=(), - issuers=(), authorized_for=()) - chall = challenges.ProofOfPossession( - alg=jose.RS256, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) - challb = messages.ChallengeBody( - chall=chall, uri="http://example", status=messages.STATUS_PENDING) - self.achall = achallenges.ProofOfPossession( - challb=challb, domain="example.com") - - def tearDown(self): - os.remove(self.cert1_path) - - def test_perform_bad_challenge(self): - hints = challenges.ProofOfPossession.Hints( - jwk=jose.jwk.JWKOct(key="foo"), cert_fingerprints=(), - certs=(), serial_numbers=(), subject_key_identifiers=(), - issuers=(), authorized_for=()) - chall = challenges.ProofOfPossession( - alg=jose.HS512, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) - challb = messages.ChallengeBody( - chall=chall, uri="http://example", status=messages.STATUS_PENDING) - self.achall = achallenges.ProofOfPossession( - challb=challb, domain="example.com") - self.assertEqual(self.proof_of_pos.perform(self.achall), None) - - def test_perform_no_input(self): - self.assertTrue(self.proof_of_pos.perform(self.achall).verify()) - - @mock.patch("letsencrypt.proof_of_possession.zope.component.getUtility") - def test_perform_with_input(self, mock_input): - # Remove the matching certificate - self.installer.get_all_certs_keys.return_value.pop() - mock_input().input.side_effect = [(display_util.CANCEL, ""), - (display_util.OK, CERT0_PATH), - (display_util.OK, "imaginary_file"), - (display_util.OK, CERT3_KEY_PATH)] - self.assertFalse(self.proof_of_pos.perform(self.achall)) - self.assertFalse(self.proof_of_pos.perform(self.achall)) - self.assertFalse(self.proof_of_pos.perform(self.achall)) - self.assertTrue(self.proof_of_pos.perform(self.achall).verify()) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover From 33b851b6c5d5e802c1b1404ecf615d06fb3d0402 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:22:12 -0800 Subject: [PATCH 15/29] remove continuity challenges from acme_util --- letsencrypt/tests/acme_util.py | 58 ++------------------------ letsencrypt/tests/auth_handler_test.py | 47 ++++++--------------- 2 files changed, 16 insertions(+), 89 deletions(-) diff --git a/letsencrypt/tests/acme_util.py b/letsencrypt/tests/acme_util.py index 71ebb471d..ea5438923 100644 --- a/letsencrypt/tests/acme_util.py +++ b/letsencrypt/tests/acme_util.py @@ -17,56 +17,14 @@ HTTP01 = challenges.HTTP01( TLSSNI01 = challenges.TLSSNI01( token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA")) DNS = challenges.DNS(token="17817c66b60ce2e4012dfad92657527a") -RECOVERY_CONTACT = challenges.RecoveryContact( - activation_url="https://example.ca/sendrecovery/a5bd99383fb0", - success_url="https://example.ca/confirmrecovery/bb1b9928932", - contact="c********n@example.com") -POP = challenges.ProofOfPossession( - alg="RS256", nonce=jose.b64decode("eET5udtV7aoX8Xl8gYiZIA"), - hints=challenges.ProofOfPossession.Hints( - jwk=jose.JWKRSA(key=KEY.public_key()), - cert_fingerprints=( - "93416768eb85e33adc4277f4c9acd63e7418fcfe", - "16d95b7b63f1972b980b14c20291f3c0d1855d95", - "48b46570d9fc6358108af43ad1649484def0debf" - ), - certs=(), # TODO - subject_key_identifiers=("d0083162dcc4c8a23ecb8aecbd86120e56fd24e5"), - serial_numbers=(34234239832, 23993939911, 17), - issuers=( - "C=US, O=SuperT LLC, CN=SuperTrustworthy Public CA", - "O=LessTrustworthy CA Inc, CN=LessTrustworthy But StillSecure", - ), - authorized_for=("www.example.com", "example.net"), - ) -) -CHALLENGES = [HTTP01, TLSSNI01, DNS, RECOVERY_CONTACT, POP] -DV_CHALLENGES = [chall for chall in CHALLENGES - if isinstance(chall, challenges.DVChallenge)] -CONT_CHALLENGES = [chall for chall in CHALLENGES - if isinstance(chall, challenges.ContinuityChallenge)] +CHALLENGES = [HTTP01, TLSSNI01, DNS] def gen_combos(challbs): """Generate natural combinations for challbs.""" - dv_chall = [] - cont_chall = [] - - for i, challb in enumerate(challbs): # pylint: disable=redefined-outer-name - if isinstance(challb.chall, challenges.DVChallenge): - dv_chall.append(i) - else: - cont_chall.append(i) - - if cont_chall: - # Gen combos for 1 of each type, lowest - # index included first (makes testing easier) - return tuple((i, j) if i < j else (j, i) - for i in dv_chall for j in cont_chall) - else: - # completing a single DV chall satisfies the CA - return tuple((i,) for i in dv_chall) + # completing a single DV challenge satisfies the CA + return tuple((i,) for i, _ in enumerate(challbs)) def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name @@ -87,16 +45,8 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name TLSSNI01_P = chall_to_challb(TLSSNI01, messages.STATUS_PENDING) HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING) DNS_P = chall_to_challb(DNS, messages.STATUS_PENDING) -RECOVERY_CONTACT_P = chall_to_challb(RECOVERY_CONTACT, messages.STATUS_PENDING) -POP_P = chall_to_challb(POP, messages.STATUS_PENDING) -CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS_P, RECOVERY_CONTACT_P, POP_P] -DV_CHALLENGES_P = [challb for challb in CHALLENGES_P - if isinstance(challb.chall, challenges.DVChallenge)] -CONT_CHALLENGES_P = [ - challb for challb in CHALLENGES_P - if isinstance(challb.chall, challenges.ContinuityChallenge) -] +CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS_P] def gen_authzr(authz_status, domain, challs, statuses, combos=True): diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index dcf8a5ab3..426a269ac 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -35,10 +35,10 @@ class ChallengeFactoryTest(unittest.TestCase): self.dom, range(0, len(acme_util.CHALLENGES))) self.assertEqual( - [achall.chall for achall in achalls], acme_util.DV_CHALLENGES) + [achall.chall for achall in achalls], acme_util.CHALLENGES) def test_one_tls_sni(self): - achalls = self.handler._challenge_factory(self.dom, [1, 3]) + achalls = self.handler._challenge_factory(self.dom, [1]) self.assertEqual( [achall.chall for achall in achalls], [acme_util.TLSSNI01]) @@ -83,7 +83,7 @@ class GetAuthorizationsTest(unittest.TestCase): @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") def test_name1_tls_sni_01_1(self, mock_poll): self.mock_net.request_domain_challenges.side_effect = functools.partial( - gen_dom_authzr, challs=acme_util.DV_CHALLENGES) + gen_dom_authzr, challs=acme_util.CHALLENGES) mock_poll.side_effect = self._validate_all @@ -106,7 +106,7 @@ class GetAuthorizationsTest(unittest.TestCase): @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") def test_name1_tls_sni_01_1_http_01_1_dns_1(self, mock_poll): self.mock_net.request_domain_challenges.side_effect = functools.partial( - gen_dom_authzr, challs=acme_util.DV_CHALLENGES, combos=False) + gen_dom_authzr, challs=acme_util.CHALLENGES, combos=False) mock_poll.side_effect = self._validate_all self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) @@ -131,7 +131,7 @@ class GetAuthorizationsTest(unittest.TestCase): @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") def test_name3_tls_sni_01_3(self, mock_poll): self.mock_net.request_domain_challenges.side_effect = functools.partial( - gen_dom_authzr, challs=acme_util.DV_CHALLENGES) + gen_dom_authzr, challs=acme_util.CHALLENGES) mock_poll.side_effect = self._validate_all @@ -156,7 +156,7 @@ class GetAuthorizationsTest(unittest.TestCase): def test_perform_failure(self): self.mock_net.request_domain_challenges.side_effect = functools.partial( - gen_dom_authzr, challs=acme_util.DV_CHALLENGES) + gen_dom_authzr, challs=acme_util.CHALLENGES) self.mock_auth.perform.side_effect = errors.AuthorizationError self.assertRaises( @@ -189,15 +189,16 @@ class PollChallengesTest(unittest.TestCase): self.doms = ["0", "1", "2"] self.handler.authzr[self.doms[0]] = acme_util.gen_authzr( messages.STATUS_PENDING, self.doms[0], - acme_util.DV_CHALLENGES, [messages.STATUS_PENDING] * 3, False) + [acme_util.HTTP01, acme_util.TLSSNI01], + [messages.STATUS_PENDING] * 2, False) self.handler.authzr[self.doms[1]] = acme_util.gen_authzr( messages.STATUS_PENDING, self.doms[1], - acme_util.DV_CHALLENGES, [messages.STATUS_PENDING] * 3, False) + acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False) self.handler.authzr[self.doms[2]] = acme_util.gen_authzr( messages.STATUS_PENDING, self.doms[2], - acme_util.DV_CHALLENGES, [messages.STATUS_PENDING] * 3, False) + acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False) self.chall_update = {} for dom in self.doms: @@ -234,7 +235,7 @@ class PollChallengesTest(unittest.TestCase): from letsencrypt.auth_handler import challb_to_achall self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid self.chall_update[self.doms[0]].append( - challb_to_achall(acme_util.RECOVERY_CONTACT_P, "key", self.doms[0])) + challb_to_achall(acme_util.DNS_P, "key", self.doms[0])) self.assertRaises( errors.AuthorizationError, self.handler._poll_challenges, self.chall_update, False) @@ -335,32 +336,8 @@ class GenChallengePathTest(unittest.TestCase): self.assertEqual(self._call(challbs[::-1], prefs, combos), (1,)) self.assertTrue(self._call(challbs[::-1], prefs, None)) - def test_common_case_with_continuity(self): - challbs = (acme_util.POP_P, - acme_util.RECOVERY_CONTACT_P, - acme_util.TLSSNI01_P, - acme_util.HTTP01_P) - prefs = [challenges.ProofOfPossession, challenges.TLSSNI01] - combos = acme_util.gen_combos(challbs) - self.assertEqual(self._call(challbs, prefs, combos), (0, 2)) - - def test_full_cont_server(self): - challbs = (acme_util.RECOVERY_CONTACT_P, - acme_util.POP_P, - acme_util.TLSSNI01_P, - acme_util.HTTP01_P, - acme_util.DNS_P) - # Typical webserver client that can do everything except DNS - # Attempted to make the order realistic - prefs = [challenges.ProofOfPossession, - challenges.HTTP01, - challenges.TLSSNI01, - challenges.RecoveryContact] - combos = acme_util.gen_combos(challbs) - self.assertEqual(self._call(challbs, prefs, combos), (1, 3)) - def test_not_supported(self): - challbs = (acme_util.POP_P, acme_util.TLSSNI01_P) + challbs = (acme_util.DNS_P, acme_util.TLSSNI01_P) prefs = [challenges.TLSSNI01] combos = ((0, 1),) From b0280ac17e221fcaefb27753f6b5bc91f7ebcaa7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:25:05 -0800 Subject: [PATCH 16/29] no PoP or RC in auth_handler --- letsencrypt/auth_handler.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index a324dd4b7..659f4b721 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -320,12 +320,6 @@ def challb_to_achall(challb, account_key, domain): challb=challb, domain=domain, account_key=account_key) elif isinstance(chall, challenges.DNS): return achallenges.DNS(challb=challb, domain=domain) - elif isinstance(chall, challenges.RecoveryContact): - return achallenges.RecoveryContact( - challb=challb, domain=domain) - elif isinstance(chall, challenges.ProofOfPossession): - return achallenges.ProofOfPossession( - challb=challb, domain=domain) else: raise errors.Error( "Received unsupported challenge of type: %s", chall.typ) From eb71506be9e4aa5de78f9cc504165549be2feaa1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:27:56 -0800 Subject: [PATCH 17/29] remove PoP and RC achalls --- letsencrypt/achallenges.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/letsencrypt/achallenges.py b/letsencrypt/achallenges.py index 4d85f5d6a..0cdec06df 100644 --- a/letsencrypt/achallenges.py +++ b/letsencrypt/achallenges.py @@ -59,15 +59,3 @@ class DNS(AnnotatedChallenge): """Client annotated "dns" ACME challenge.""" __slots__ = ('challb', 'domain') acme_type = challenges.DNS - - -class RecoveryContact(AnnotatedChallenge): - """Client annotated "recoveryContact" ACME challenge.""" - __slots__ = ('challb', 'domain') - acme_type = challenges.RecoveryContact - - -class ProofOfPossession(AnnotatedChallenge): - """Client annotated "proofOfPossession" ACME challenge.""" - __slots__ = ('challb', 'domain') - acme_type = challenges.ProofOfPossession From f2e728cd4e7d94ffb17077c8895f2fcc02c543ff Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:29:08 -0800 Subject: [PATCH 18/29] Remove ContAuthError --- letsencrypt/errors.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/letsencrypt/errors.py b/letsencrypt/errors.py index 99bb29d9d..4f9e655d8 100644 --- a/letsencrypt/errors.py +++ b/letsencrypt/errors.py @@ -48,10 +48,6 @@ class FailedChallenges(AuthorizationError): for achall in self.failed_achalls if achall.error is not None)) -class ContAuthError(AuthorizationError): - """Let's Encrypt Continuity Authenticator error.""" - - class DvAuthError(AuthorizationError): """Let's Encrypt DV Authenticator error.""" From f1e3563f98a188324121b717818efc8af322b70b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:34:01 -0800 Subject: [PATCH 19/29] remove needlessly specific and unused challenge types --- letsencrypt/errors.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/letsencrypt/errors.py b/letsencrypt/errors.py index 4f9e655d8..eb9f8dd0e 100644 --- a/letsencrypt/errors.py +++ b/letsencrypt/errors.py @@ -48,15 +48,6 @@ class FailedChallenges(AuthorizationError): for achall in self.failed_achalls if achall.error is not None)) -class DvAuthError(AuthorizationError): - """Let's Encrypt DV Authenticator error.""" - - -# Authenticator - Challenge specific errors -class TLSSNI01Error(DvAuthError): - """Let's Encrypt TLSSNI01 error.""" - - # Plugin Errors class PluginError(Error): """Let's Encrypt Plugin error.""" From bb5d7b37e4cba4bfb29c1d7e7f73d236f44e6d6b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:36:51 -0800 Subject: [PATCH 20/29] remove error type for nonexistant revoker --- letsencrypt/errors.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/letsencrypt/errors.py b/letsencrypt/errors.py index 99bb29d9d..a5c3186a8 100644 --- a/letsencrypt/errors.py +++ b/letsencrypt/errors.py @@ -86,10 +86,6 @@ class NotSupportedError(PluginError): """Let's Encrypt Plugin function not supported error.""" -class RevokerError(Error): - """Let's Encrypt Revoker error.""" - - class StandaloneBindError(Error): """Standalone plugin bind error.""" From 4a208d4821d84cea1dde2193d4ad3327465c11d1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:53:59 -0800 Subject: [PATCH 21/29] remove stray references to DV challs in auth_handler --- letsencrypt/auth_handler.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index 659f4b721..285a1f3b7 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -21,7 +21,7 @@ class AuthHandler(object): """ACME Authorization Handler for a client. :ivar auth: Authenticator capable of solving - :class:`~acme.challenges.DVChallenge` types + :class:`~acme.challenges.Challenge` types :type auth: :class:`letsencrypt.interfaces.IAuthenticator` :ivar acme.client.Client acme: ACME client API. @@ -117,9 +117,8 @@ class AuthHandler(object): """ # TODO: chall_update is a dirty hack to get around acme-spec #105 chall_update = dict() - active_achalls = [] - active_achalls.extend( - self._send_responses(self.achalls, resp, chall_update)) + active_achalls = self._send_responses(self.achalls, + resp, chall_update) # Check for updated status... try: @@ -253,8 +252,7 @@ class AuthHandler(object): if achall_list is None: achalls = self.achalls else: - achalls = [achall for achall in achall_list - if isinstance(achall.chall, challenges.DVChallenge)] + achalls = achall_list if achalls: self.auth.cleanup(achalls) @@ -280,7 +278,7 @@ class AuthHandler(object): :param list path: List of indices from `challenges`. - :returns: achalls, list of DVChallenge type + :returns: achalls, list of challenge type :class:`letsencrypt.achallenges.Indexed` :rtype: list @@ -291,12 +289,7 @@ class AuthHandler(object): for index in path: challb = self.authzr[domain].body.challenges[index] - chall = challb.chall - - achall = challb_to_achall(challb, self.account.key, domain) - - if isinstance(chall, challenges.DVChallenge): - achalls.append(achall) + achalls.append(challb_to_achall(challb, self.account.key, domain)) return achalls From 26ffd8df3b5d12ea3707391caf70a32bc7999a2e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Mar 2016 17:56:23 -0800 Subject: [PATCH 22/29] constants.DV_CHALLENGES is not the constant you are looking for --- letsencrypt/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index d0bdb1f85..6134c4e6e 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -163,7 +163,7 @@ class Client(object): dispatch DV challenges to appropriate authenticators (providing `.IAuthenticator` interface). :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`) - authenticator that can solve the `.constants.DV_CHALLENGES`. + authenticator that can solve ACME challenges. :ivar .IInstaller installer: Installer. :ivar acme.client.Client acme: Optional ACME client API handle. You might already have one from `register`. From f7d862d0bf2020116db5cfd5e685e683f44d1cc2 Mon Sep 17 00:00:00 2001 From: Wang Yu Date: Sat, 5 Mar 2016 20:33:02 +0100 Subject: [PATCH 23/29] webroot configuration text--fix format --- docs/using.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/using.rst b/docs/using.rst index 37fca2c57..49d48a974 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -111,7 +111,9 @@ potentially be a separate directory for each domain. When requested a certificate for multiple domains, each domain will use the most recently specified ``--webroot-path``. So, for instance, -``letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net`` +:: + + letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net would obtain a single certificate for all of those names, using the ``/var/www/example`` webroot directory for the first two, and From b9496733f65c1e84bff2cb477753fbd62b2f508d Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 9 Mar 2016 15:29:14 -0800 Subject: [PATCH 24/29] Plugin doc cleanups --- docs/using.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/using.rst b/docs/using.rst index b2251d948..0e67c0271 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -71,11 +71,11 @@ Plugin Auth Inst Notes =========== ==== ==== =============================================================== apache_ Y Y Automates obtaining and installing a cert with Apache 2.4 on Debian-based distributions with ``libaugeas0`` 1.0+. -standalone_ Y N Uses a "standalone" webserver to obtain a cert. This is useful - on systems with no webserver, or when direct integration with - the local webserver is not supported or not desired. webroot_ Y N Obtains a cert by writing to the webroot directory of an already running webserver. +standalone_ Y N Uses a "standalone" webserver to obtain a cert. Requires + port 80 or 443 to be available. This is useful on systems + with no webserver, or when direct integration with the local webserver is not supported or not desired. manual_ Y N Helps you obtain a cert by giving you instructions to perform domain validation yourself. nginx_ Y Y Very experimental and not included in letsencrypt-auto_. @@ -87,15 +87,16 @@ There are also a number of third-party plugins for the client, provided by other Plugin Auth Inst Notes =========== ==== ==== =============================================================== plesk_ Y Y Integration with the Plesk web hosting tool - https://github.com/plesk/letsencrypt-plesk haproxy_ Y Y Inegration with the HAProxy load balancer - https://code.greenhost.net/open/letsencrypt-haproxy s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets - https://github.com/dlapiduz/letsencrypt-s3front gandi_ Y Y Integration with Gandi's hosting products and API - https://github.com/Gandi/letsencrypt-gandi =========== ==== ==== =============================================================== +.. _plesk: https://github.com/plesk/letsencrypt-plesk +.. _haproxy: https://code.greenhost.net/open/letsencrypt-haproxy +.. _s3front: https://github.com/dlapiduz/letsencrypt-s3front +.. _gandi: https://github.com/Gandi/letsencrypt-gandi + Future plugins for IMAP servers, SMTP servers, IRC servers, etc, are likely to be installers but not authenticators. From 6ba5f175ae8f12d3dcab832717900ac7bcd0e71d Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 9 Mar 2016 18:30:38 -0800 Subject: [PATCH 25/29] Prevent example command from overflowing margins --- docs/using.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/using.rst b/docs/using.rst index 0e67c0271..5c7137895 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -127,7 +127,8 @@ potentially be a separate directory for each domain. When requested a certificate for multiple domains, each domain will use the most recently specified ``--webroot-path``. So, for instance, -``letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net`` +``letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d`` +``example.com -w /var/www/eg -d other.example.net -d m.other.example.net`` would obtain a single certificate for all of those names, using the ``/var/www/example`` webroot directory for the first two, and From e203a6121cdef16ce3b3cfb0e73e31823c615f38 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 9 Mar 2016 18:38:03 -0800 Subject: [PATCH 26/29] weird spacing fix --- docs/using.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/using.rst b/docs/using.rst index 25046f0bb..2d389baf6 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -75,7 +75,8 @@ webroot_ Y N Obtains a cert by writing to the webroot directory of an already running webserver. standalone_ Y N Uses a "standalone" webserver to obtain a cert. Requires port 80 or 443 to be available. This is useful on systems - with no webserver, or when direct integration with the local webserver is not supported or not desired. + with no webserver, or when direct integration with the local + webserver is not supported or not desired. manual_ Y N Helps you obtain a cert by giving you instructions to perform domain validation yourself. nginx_ Y Y Very experimental and not included in letsencrypt-auto_. From 14f595d48ef4ad27560bb2f612b2709b82f57837 Mon Sep 17 00:00:00 2001 From: Wang Yu Date: Sat, 5 Mar 2016 20:33:02 +0100 Subject: [PATCH 27/29] webroot configuration text--fix format --- docs/using.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/using.rst b/docs/using.rst index 04b239958..488afa2e0 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -126,7 +126,9 @@ potentially be a separate directory for each domain. When requested a certificate for multiple domains, each domain will use the most recently specified ``--webroot-path``. So, for instance, -``letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net`` +:: + + letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net would obtain a single certificate for all of those names, using the ``/var/www/example`` webroot directory for the first two, and From 34480f9d0f45f0aa1d9e282b894d9a886b05b67e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 10 Mar 2016 13:27:47 -0800 Subject: [PATCH 28/29] Revert "Remove SIGFILEBALL after creating sig" This reverts commit cf12f8702761a14ce55d89a2f75c23a996032c83. --- tools/offline-sigrequest.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/offline-sigrequest.sh b/tools/offline-sigrequest.sh index 08a5c4c05..7706796ef 100755 --- a/tools/offline-sigrequest.sh +++ b/tools/offline-sigrequest.sh @@ -42,7 +42,6 @@ function oncesigned { # $1 <-- INPFILE ; $2 <--SIGFILE echo `file $2` exit 1 fi - rm $SIGFILEBALL } HERE=`dirname $0` From 4a17294654a4157cdfb87abb4bd2ab55b0af111a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 10 Mar 2016 13:35:06 -0800 Subject: [PATCH 29/29] Remove sigfileball and add it to gitignore --- .gitignore | 1 + letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64 | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64 diff --git a/.gitignore b/.gitignore index 38c95986c..8118edfd4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ dist*/ /.tox/ /releases/ letsencrypt.log +letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64 # coverage .coverage diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64 b/letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64 deleted file mode 100644 index 037f2f020..000000000 --- a/letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64 +++ /dev/null @@ -1,6 +0,0 @@ -XQAAAAT//////////wApLArrUzOk5bRHUk0UvMS4xjyZkm3U3qhnKvMbEan7rVeK6yBlbwGeeWFn -Sw4XT1raGAMNq7cwyJvT7ql93Df7TpuRnxNSbPx7q52GojYyb5Oj1IQ2Y22Mvq41Q4K3kCZcVv+1 -YVKW3OazUn+wCnaoGhDdMFmH0EKbEPSGibba6HJqUoFosaDE2hRZmjqYR/VwwPCtW820L0Qz9PZ7 -DEAZ5VdMmj1+u+bYjDEcZD5+DyWKoLWci8tBXcPGiSvPDdZax/IWmR0GGUOd13gC7uX/HM2dHgbM -Izh7Y3PPNEzM8Fu2wdXLoMCaYrQcrPAdKhsnyMCDbjxCVbD9LkS17xCq4LUMkcz/fMu3/CRSMMZ7 -gnn//jNQAA==