certbot/letsencrypt/tests/auth_handler_test.py

423 lines
16 KiB
Python
Raw Normal View History

2015-05-10 08:25:29 -04:00
"""Tests for letsencrypt.auth_handler."""
2015-04-22 19:27:54 -04:00
import functools
2015-01-29 23:07:29 -05:00
import logging
2015-01-12 08:44:06 -05:00
import unittest
2015-01-24 08:12:45 -05:00
2015-01-12 08:44:06 -05:00
import mock
2015-05-10 07:26:21 -04:00
from acme import challenges
from acme import client as acme_client
2015-06-11 11:00:18 -04:00
from acme import messages
2015-10-28 16:42:27 -04:00
from letsencrypt import achallenges
2015-05-10 08:25:29 -04:00
from letsencrypt import errors
from letsencrypt import le_util
2015-01-12 08:44:06 -05:00
2015-05-10 08:25:29 -04:00
from letsencrypt.tests import acme_util
2015-01-12 08:44:06 -05:00
2015-04-22 19:27:54 -04:00
class ChallengeFactoryTest(unittest.TestCase):
# pylint: disable=protected-access
def setUp(self):
2015-05-10 08:25:29 -04:00
from letsencrypt.auth_handler import AuthHandler
2015-04-22 19:27:54 -04:00
# Account is mocked...
2016-02-26 16:01:58 -05:00
self.handler = AuthHandler(None, None, mock.Mock(key="mock_key"))
2015-04-22 19:27:54 -04:00
self.dom = "test"
self.handler.authzr[self.dom] = acme_util.gen_authzr(
2015-06-11 11:00:18 -04:00
messages.STATUS_PENDING, self.dom, acme_util.CHALLENGES,
2015-09-06 05:20:11 -04:00
[messages.STATUS_PENDING] * 6, False)
2015-04-22 19:27:54 -04:00
def test_all(self):
achalls = self.handler._challenge_factory(
2015-07-25 07:53:37 -04:00
self.dom, range(0, len(acme_util.CHALLENGES)))
2015-04-22 19:27:54 -04:00
self.assertEqual(
[achall.chall for achall in achalls], acme_util.CHALLENGES)
2015-04-22 19:27:54 -04:00
def test_one_tls_sni(self):
achalls = self.handler._challenge_factory(self.dom, [1])
2015-04-22 19:27:54 -04:00
self.assertEqual(
[achall.chall for achall in achalls], [acme_util.TLSSNI01])
2015-04-22 19:27:54 -04:00
def test_unrecognized(self):
self.handler.authzr["failure.com"] = acme_util.gen_authzr(
2015-06-11 11:00:18 -04:00
messages.STATUS_PENDING, "failure.com",
[mock.Mock(chall="chall", typ="unrecognized")],
2015-06-11 11:00:18 -04:00
[messages.STATUS_PENDING])
2015-04-22 19:27:54 -04:00
2015-06-12 10:45:28 -04:00
self.assertRaises(
errors.Error, self.handler._challenge_factory, "failure.com", [0])
2015-04-22 19:27:54 -04:00
class GetAuthorizationsTest(unittest.TestCase):
"""get_authorizations test.
This tests everything except for all functions under _poll_challenges.
"""
2015-01-24 08:12:45 -05:00
2015-01-12 08:44:06 -05:00
def setUp(self):
2015-05-10 08:25:29 -04:00
from letsencrypt.auth_handler import AuthHandler
2015-01-12 08:44:06 -05:00
self.mock_auth = mock.MagicMock(name="ApacheConfigurator")
2015-01-12 08:44:06 -05:00
self.mock_auth.get_chall_pref.return_value = [challenges.TLSSNI01]
2015-01-12 08:44:06 -05:00
self.mock_auth.perform.side_effect = gen_auth_resp
2015-01-12 08:44:06 -05:00
2015-04-22 19:27:54 -04:00
self.mock_account = mock.Mock(key=le_util.Key("file_path", "PEM"))
self.mock_net = mock.MagicMock(spec=acme_client.Client)
2015-04-19 23:10:40 -04:00
2015-01-12 08:44:06 -05:00
self.handler = AuthHandler(
self.mock_auth, self.mock_net, self.mock_account)
2015-01-12 08:44:06 -05:00
2015-01-29 23:07:29 -05:00
logging.disable(logging.CRITICAL)
def tearDown(self):
logging.disable(logging.NOTSET)
2015-05-10 08:25:29 -04:00
@mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges")
2015-11-07 09:03:58 -05:00
def test_name1_tls_sni_01_1(self, mock_poll):
2015-04-22 19:27:54 -04:00
self.mock_net.request_domain_challenges.side_effect = functools.partial(
gen_dom_authzr, challs=acme_util.CHALLENGES)
2015-04-22 19:27:54 -04:00
mock_poll.side_effect = self._validate_all
authzr = self.handler.get_authorizations(["0"])
2015-04-22 19:27:54 -04:00
self.assertEqual(self.mock_net.answer_challenge.call_count, 1)
2015-04-22 19:27:54 -04:00
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_auth.cleanup.call_count, 1)
2015-11-07 09:03:58 -05:00
# Test if list first element is TLSSNI01, use typ because it is an achall
2015-01-12 08:44:06 -05:00
self.assertEqual(
self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
2015-01-12 08:44:06 -05:00
2015-04-22 19:27:54 -04:00
self.assertEqual(len(authzr), 1)
2015-01-12 08:44:06 -05:00
2015-05-10 08:25:29 -04:00
@mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges")
2016-02-26 18:41:56 -05:00
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.CHALLENGES, combos=False)
2016-02-26 18:41:56 -05:00
mock_poll.side_effect = self._validate_all
self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01)
self.mock_auth.get_chall_pref.return_value.append(challenges.DNS)
2016-02-26 18:41:56 -05:00
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_auth.cleanup.call_count, 1)
2016-02-26 18:41:56 -05:00
# Test if list first element is TLSSNI01, use typ because it is an achall
for achall in self.mock_auth.cleanup.call_args[0][0]:
2016-02-26 18:41:56 -05:00
self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns"])
2016-03-14 09:11:52 -04:00
# Length of authorizations list
self.assertEqual(len(authzr), 1)
2016-02-26 18:41:56 -05:00
2015-05-10 08:25:29 -04:00
@mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges")
2016-02-26 16:01:58 -05:00
def test_name3_tls_sni_01_3(self, mock_poll):
2015-04-22 19:27:54 -04:00
self.mock_net.request_domain_challenges.side_effect = functools.partial(
gen_dom_authzr, challs=acme_util.CHALLENGES)
2015-01-12 08:44:06 -05:00
2015-04-22 19:27:54 -04:00
mock_poll.side_effect = self._validate_all
2015-01-12 08:44:06 -05:00
authzr = self.handler.get_authorizations(["0", "1", "2"])
2015-01-12 08:44:06 -05:00
2016-02-26 16:01:58 -05:00
self.assertEqual(self.mock_net.answer_challenge.call_count, 3)
2015-01-12 08:44:06 -05:00
2015-04-22 19:27:54 -04:00
# 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())
2016-02-26 16:01:58 -05:00
self.assertEqual(len(chall_update["0"]), 1)
2015-04-22 19:27:54 -04:00
self.assertTrue("1" in chall_update.keys())
2016-02-26 16:01:58 -05:00
self.assertEqual(len(chall_update["1"]), 1)
2015-04-22 19:27:54 -04:00
self.assertTrue("2" in chall_update.keys())
2016-02-26 16:01:58 -05:00
self.assertEqual(len(chall_update["2"]), 1)
2015-01-12 08:44:06 -05:00
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
2015-01-12 08:44:06 -05:00
2015-04-22 19:27:54 -04:00
self.assertEqual(len(authzr), 3)
2015-01-12 08:44:06 -05:00
def test_perform_failure(self):
self.mock_net.request_domain_challenges.side_effect = functools.partial(
gen_dom_authzr, challs=acme_util.CHALLENGES)
self.mock_auth.perform.side_effect = errors.AuthorizationError
2015-06-12 10:45:28 -04:00
self.assertRaises(
errors.AuthorizationError, self.handler.get_authorizations, ["0"])
2016-03-20 19:12:59 -04:00
def test_no_domains(self):
self.assertRaises(errors.AuthorizationError, self.handler.get_authorizations, [])
2015-04-23 02:17:53 -04:00
def _validate_all(self, unused_1, unused_2):
2015-04-22 19:27:54 -04:00
for dom in self.handler.authzr.keys():
azr = self.handler.authzr[dom]
self.handler.authzr[dom] = acme_util.gen_authzr(
2015-06-11 11:00:18 -04:00
messages.STATUS_VALID,
dom,
[challb.chall for challb in azr.body.challenges],
2015-09-06 05:20:11 -04:00
[messages.STATUS_VALID] * len(azr.body.challenges),
azr.body.combinations)
2015-01-12 08:44:06 -05:00
2015-01-13 19:48:18 -05:00
2015-04-23 02:17:53 -04:00
class PollChallengesTest(unittest.TestCase):
# pylint: disable=protected-access
2015-04-23 02:17:53 -04:00
"""Test poll challenges."""
def setUp(self):
2015-05-10 08:25:29 -04:00
from letsencrypt.auth_handler import challb_to_achall
from letsencrypt.auth_handler import AuthHandler
# Account and network are mocked...
self.mock_net = mock.MagicMock()
2015-04-23 02:17:53 -04:00
self.handler = AuthHandler(
2016-02-26 16:01:58 -05:00
None, self.mock_net, mock.Mock(key="mock_key"))
2015-04-23 02:17:53 -04:00
self.doms = ["0", "1", "2"]
self.handler.authzr[self.doms[0]] = acme_util.gen_authzr(
2015-06-11 11:00:18 -04:00
messages.STATUS_PENDING, self.doms[0],
[acme_util.HTTP01, acme_util.TLSSNI01],
[messages.STATUS_PENDING] * 2, False)
2015-04-23 02:17:53 -04:00
self.handler.authzr[self.doms[1]] = acme_util.gen_authzr(
2015-06-11 11:00:18 -04:00
messages.STATUS_PENDING, self.doms[1],
acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False)
2015-04-23 02:17:53 -04:00
self.handler.authzr[self.doms[2]] = acme_util.gen_authzr(
2015-06-11 11:00:18 -04:00
messages.STATUS_PENDING, self.doms[2],
acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False)
self.chall_update = {}
for dom in self.doms:
self.chall_update[dom] = [
challb_to_achall(challb, mock.Mock(key="dummy_key"), dom)
for challb in self.handler.authzr[dom].body.challenges]
2015-05-10 08:25:29 -04:00
@mock.patch("letsencrypt.auth_handler.time")
def test_poll_challenges(self, unused_mock_time):
self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid
self.handler._poll_challenges(self.chall_update, False)
for authzr in self.handler.authzr.values():
2015-06-11 11:00:18 -04:00
self.assertEqual(authzr.body.status, messages.STATUS_VALID)
2015-05-10 08:25:29 -04:00
@mock.patch("letsencrypt.auth_handler.time")
def test_poll_challenges_failure_best_effort(self, unused_mock_time):
self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid
self.handler._poll_challenges(self.chall_update, True)
for authzr in self.handler.authzr.values():
2015-06-11 11:00:18 -04:00
self.assertEqual(authzr.body.status, messages.STATUS_PENDING)
2015-05-10 08:25:29 -04:00
@mock.patch("letsencrypt.auth_handler.time")
2015-06-24 18:27:34 -04:00
@mock.patch("letsencrypt.auth_handler.zope.component.getUtility")
def test_poll_challenges_failure(self, unused_mock_time, unused_mock_zope):
self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid
2015-06-12 10:45:28 -04:00
self.assertRaises(
errors.AuthorizationError, self.handler._poll_challenges,
self.chall_update, False)
2015-05-10 08:25:29 -04:00
@mock.patch("letsencrypt.auth_handler.time")
def test_unable_to_find_challenge_status(self, unused_mock_time):
2015-05-10 08:25:29 -04:00
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.DNS_P, "key", self.doms[0]))
self.assertRaises(
2015-06-12 10:45:28 -04:00
errors.AuthorizationError, self.handler._poll_challenges,
self.chall_update, False)
def test_verify_authzr_failure(self):
self.assertRaises(
errors.AuthorizationError, self.handler.verify_authzr_complete)
def _mock_poll_solve_one_valid(self, authzr):
# Pending here because my dummy script won't change the full status.
# Basically it didn't raise an error and it stopped earlier than
# Making all challenges invalid which would make mock_poll_solve_one
# change authzr to invalid
2015-06-11 11:00:18 -04:00
return self._mock_poll_solve_one_chall(authzr, messages.STATUS_VALID)
def _mock_poll_solve_one_invalid(self, authzr):
2015-06-11 11:00:18 -04:00
return self._mock_poll_solve_one_chall(authzr, messages.STATUS_INVALID)
def _mock_poll_solve_one_chall(self, authzr, desired_status):
# pylint: disable=no-self-use
"""Dummy method that solves one chall at a time to desired_status.
When all are solved.. it changes authzr.status to desired_status
"""
new_challbs = authzr.body.challenges
for challb in authzr.body.challenges:
if challb.status != desired_status:
new_challbs = tuple(
challb_temp if challb_temp != challb
else acme_util.chall_to_challb(challb.chall, desired_status)
for challb_temp in authzr.body.challenges
)
break
if all(test_challb.status == desired_status
for test_challb in new_challbs):
status_ = desired_status
else:
status_ = authzr.body.status
2015-06-11 11:00:18 -04:00
new_authzr = messages.AuthorizationResource(
uri=authzr.uri,
new_cert_uri=authzr.new_cert_uri,
2015-06-11 11:00:18 -04:00
body=messages.Authorization(
identifier=authzr.body.identifier,
challenges=new_challbs,
combinations=authzr.body.combinations,
status=status_,
),
)
return (new_authzr, "response")
2015-04-23 02:17:53 -04:00
2015-09-06 05:20:11 -04:00
2015-10-28 16:42:27 -04:00
class ChallbToAchallTest(unittest.TestCase):
"""Tests for letsencrypt.auth_handler.challb_to_achall."""
def _call(self, challb):
from letsencrypt.auth_handler import challb_to_achall
return challb_to_achall(challb, "account_key", "domain")
def test_it(self):
self.assertEqual(
self._call(acme_util.HTTP01_P),
achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.HTTP01_P, account_key="account_key",
domain="domain"),
)
2015-03-28 00:08:14 -04:00
class GenChallengePathTest(unittest.TestCase):
2015-05-10 08:25:29 -04:00
"""Tests for letsencrypt.auth_handler.gen_challenge_path.
2015-03-28 00:08:14 -04:00
.. todo:: Add more tests for dumb_path... depending on what we want to do.
"""
def setUp(self):
logging.disable(logging.fatal)
def tearDown(self):
logging.disable(logging.NOTSET)
@classmethod
2015-04-22 19:27:54 -04:00
def _call(cls, challbs, preferences, combinations):
2015-05-10 08:25:29 -04:00
from letsencrypt.auth_handler import gen_challenge_path
2015-04-22 19:27:54 -04:00
return gen_challenge_path(challbs, preferences, combinations)
2015-03-28 00:08:14 -04:00
def test_common_case(self):
2015-11-07 09:03:58 -05:00
"""Given TLSSNI01 and HTTP01 with appropriate combos."""
challbs = (acme_util.TLSSNI01_P, acme_util.HTTP01_P)
2016-02-26 18:32:11 -05:00
prefs = [challenges.TLSSNI01, challenges.HTTP01]
2015-03-28 00:08:14 -04:00
combos = ((0,), (1,))
# Smart then trivial dumb path test
2015-04-22 19:27:54 -04:00
self.assertEqual(self._call(challbs, prefs, combos), (0,))
self.assertTrue(self._call(challbs, prefs, None))
2015-03-28 00:08:14 -04:00
# Rearrange order...
2015-04-22 19:27:54 -04:00
self.assertEqual(self._call(challbs[::-1], prefs, combos), (1,))
self.assertTrue(self._call(challbs[::-1], prefs, None))
2015-03-28 00:08:14 -04:00
def test_not_supported(self):
challbs = (acme_util.DNS_P, acme_util.TLSSNI01_P)
2015-11-07 09:03:58 -05:00
prefs = [challenges.TLSSNI01]
2015-03-28 00:08:14 -04:00
combos = ((0, 1),)
2016-02-26 18:36:22 -05:00
# smart path fails because no challs in perfs satisfies combos
2015-06-12 10:45:28 -04:00
self.assertRaises(
errors.AuthorizationError, self._call, challbs, prefs, combos)
2016-02-26 18:36:22 -05:00
# dumb path fails because all challbs are not supported
self.assertRaises(
errors.AuthorizationError, self._call, challbs, prefs, None)
2015-02-13 17:37:45 -05:00
2015-06-24 18:27:34 -04:00
class ReportFailedChallsTest(unittest.TestCase):
"""Tests for letsencrypt.auth_handler._report_failed_challs."""
# pylint: disable=protected-access
def setUp(self):
kwargs = {
2015-10-31 18:23:57 -04:00
"chall": acme_util.HTTP01,
2015-06-24 18:27:34 -04:00
"uri": "uri",
"status": messages.STATUS_INVALID,
2016-02-08 16:35:54 -05:00
"error": messages.Error(typ="urn:acme:error:tls", detail="detail"),
2015-06-24 18:27:34 -04:00
}
2016-02-08 16:35:54 -05:00
# Prevent future regressions if the error type changes
self.assertTrue(kwargs["error"].description is not None)
2015-10-31 18:23:57 -04:00
self.http01 = achallenges.KeyAuthorizationAnnotatedChallenge(
2015-09-06 05:20:11 -04:00
# pylint: disable=star-args
challb=messages.ChallengeBody(**kwargs),
2015-06-24 18:27:34 -04:00
domain="example.com",
account_key="key")
2015-06-24 18:27:34 -04:00
2015-11-07 09:03:58 -05:00
kwargs["chall"] = acme_util.TLSSNI01
self.tls_sni_same = achallenges.KeyAuthorizationAnnotatedChallenge(
2015-09-06 05:20:11 -04:00
# pylint: disable=star-args
challb=messages.ChallengeBody(**kwargs),
2015-06-24 18:27:34 -04:00
domain="example.com",
account_key="key")
2015-06-24 18:27:34 -04:00
kwargs["error"] = messages.Error(typ="dnssec", detail="detail")
2015-11-07 09:03:58 -05:00
self.tls_sni_diff = achallenges.KeyAuthorizationAnnotatedChallenge(
2015-09-06 05:20:11 -04:00
# pylint: disable=star-args
challb=messages.ChallengeBody(**kwargs),
2015-06-24 18:27:34 -04:00
domain="foo.bar",
account_key="key")
2015-06-24 18:27:34 -04:00
@mock.patch("letsencrypt.auth_handler.zope.component.getUtility")
def test_same_error_and_domain(self, mock_zope):
from letsencrypt import auth_handler
2015-11-07 09:03:58 -05:00
auth_handler._report_failed_challs([self.http01, self.tls_sni_same])
2015-06-24 18:27:34 -04:00
call_list = mock_zope().add_message.call_args_list
self.assertTrue(len(call_list) == 1)
2016-01-05 14:02:03 -05:00
self.assertTrue("Domain: example.com\nType: tls\nDetail: detail" in call_list[0][0][0])
2015-06-24 18:27:34 -04:00
@mock.patch("letsencrypt.auth_handler.zope.component.getUtility")
def test_different_errors_and_domains(self, mock_zope):
from letsencrypt import auth_handler
2015-11-07 09:03:58 -05:00
auth_handler._report_failed_challs([self.http01, self.tls_sni_diff])
2015-06-24 18:27:34 -04:00
self.assertTrue(mock_zope().add_message.call_count == 2)
2015-01-29 23:07:29 -05:00
def gen_auth_resp(chall_list):
"""Generate a dummy authorization response."""
2015-02-02 15:35:04 -05:00
return ["%s%s" % (chall.__class__.__name__, chall.domain)
2015-01-12 08:44:06 -05:00
for chall in chall_list]
2015-01-13 19:48:18 -05:00
2016-02-26 18:41:56 -05:00
def gen_dom_authzr(domain, unused_new_authzr_uri, challs, combos=True):
2015-04-22 19:27:54 -04:00
"""Generates new authzr for domains."""
return acme_util.gen_authzr(
2015-06-11 11:00:18 -04:00
messages.STATUS_PENDING, domain, challs,
2016-02-26 18:41:56 -05:00
[messages.STATUS_PENDING] * len(challs), combos)
2015-04-22 19:27:54 -04:00
2015-02-12 04:40:40 -05:00
if __name__ == "__main__":
unittest.main() # pragma: no cover