Merge pull request #2587 from letsencrypt/continuing-continuity-error

More progress on #1304
This commit is contained in:
Peter Eckersley 2016-03-10 13:37:18 -08:00
commit 876fecbb83
6 changed files with 23 additions and 134 deletions

View file

@ -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

View file

@ -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
@ -320,12 +313,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)

View file

@ -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`.

View file

@ -48,19 +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."""
# Authenticator - Challenge specific errors
class TLSSNI01Error(DvAuthError):
"""Let's Encrypt TLSSNI01 error."""
# Plugin Errors
class PluginError(Error):
"""Let's Encrypt Plugin error."""

View file

@ -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):

View file

@ -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),)