mirror of
https://github.com/certbot/certbot.git
synced 2026-07-16 23:11:32 -04:00
Merge pull request #2587 from letsencrypt/continuing-continuity-error
More progress on #1304
This commit is contained in:
commit
876fecbb83
6 changed files with 23 additions and 134 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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),)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue