mirror of
https://github.com/certbot/certbot.git
synced 2026-07-30 16:14:44 +02:00
remove crufty continuity challenges
This commit is contained in:
@@ -13,7 +13,6 @@ from acme import errors
|
|||||||
from acme import crypto_util
|
from acme import crypto_util
|
||||||
from acme import fields
|
from acme import fields
|
||||||
from acme import jose
|
from acme import jose
|
||||||
from acme import other
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -36,10 +35,6 @@ class Challenge(jose.TypedJSONObjectWithFields):
|
|||||||
return UnrecognizedChallenge.from_json(jobj)
|
return UnrecognizedChallenge.from_json(jobj)
|
||||||
|
|
||||||
|
|
||||||
class ContinuityChallenge(Challenge): # pylint: disable=abstract-method
|
|
||||||
"""Client validation challenges."""
|
|
||||||
|
|
||||||
|
|
||||||
class DVChallenge(Challenge): # pylint: disable=abstract-method
|
class DVChallenge(Challenge): # pylint: disable=abstract-method
|
||||||
"""Domain validation challenges."""
|
"""Domain validation challenges."""
|
||||||
|
|
||||||
@@ -460,106 +455,6 @@ class TLSSNI01(KeyAuthorizationChallenge):
|
|||||||
return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))
|
return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))
|
||||||
|
|
||||||
|
|
||||||
@Challenge.register
|
|
||||||
class RecoveryContact(ContinuityChallenge):
|
|
||||||
"""ACME "recoveryContact" challenge.
|
|
||||||
|
|
||||||
:ivar unicode activation_url:
|
|
||||||
:ivar unicode success_url:
|
|
||||||
:ivar unicode contact:
|
|
||||||
|
|
||||||
"""
|
|
||||||
typ = "recoveryContact"
|
|
||||||
|
|
||||||
activation_url = jose.Field("activationURL", omitempty=True)
|
|
||||||
success_url = jose.Field("successURL", omitempty=True)
|
|
||||||
contact = jose.Field("contact", omitempty=True)
|
|
||||||
|
|
||||||
|
|
||||||
@ChallengeResponse.register
|
|
||||||
class RecoveryContactResponse(ChallengeResponse):
|
|
||||||
"""ACME "recoveryContact" challenge response.
|
|
||||||
|
|
||||||
:ivar unicode token:
|
|
||||||
|
|
||||||
"""
|
|
||||||
typ = "recoveryContact"
|
|
||||||
token = jose.Field("token", omitempty=True)
|
|
||||||
|
|
||||||
|
|
||||||
@Challenge.register
|
|
||||||
class ProofOfPossession(ContinuityChallenge):
|
|
||||||
"""ACME "proofOfPossession" challenge.
|
|
||||||
|
|
||||||
:ivar .JWAAlgorithm alg:
|
|
||||||
:ivar bytes nonce: Random data, **not** base64-encoded.
|
|
||||||
:ivar hints: Various clues for the client (:class:`Hints`).
|
|
||||||
|
|
||||||
"""
|
|
||||||
typ = "proofOfPossession"
|
|
||||||
|
|
||||||
NONCE_SIZE = 16
|
|
||||||
|
|
||||||
class Hints(jose.JSONObjectWithFields):
|
|
||||||
"""Hints for "proofOfPossession" challenge.
|
|
||||||
|
|
||||||
:ivar JWK jwk: JSON Web Key
|
|
||||||
:ivar tuple cert_fingerprints: `tuple` of `unicode`
|
|
||||||
:ivar tuple certs: Sequence of :class:`acme.jose.ComparableX509`
|
|
||||||
certificates.
|
|
||||||
:ivar tuple subject_key_identifiers: `tuple` of `unicode`
|
|
||||||
:ivar tuple issuers: `tuple` of `unicode`
|
|
||||||
:ivar tuple authorized_for: `tuple` of `unicode`
|
|
||||||
|
|
||||||
"""
|
|
||||||
jwk = jose.Field("jwk", decoder=jose.JWK.from_json)
|
|
||||||
cert_fingerprints = jose.Field(
|
|
||||||
"certFingerprints", omitempty=True, default=())
|
|
||||||
certs = jose.Field("certs", omitempty=True, default=())
|
|
||||||
subject_key_identifiers = jose.Field(
|
|
||||||
"subjectKeyIdentifiers", omitempty=True, default=())
|
|
||||||
serial_numbers = jose.Field("serialNumbers", omitempty=True, default=())
|
|
||||||
issuers = jose.Field("issuers", omitempty=True, default=())
|
|
||||||
authorized_for = jose.Field("authorizedFor", omitempty=True, default=())
|
|
||||||
|
|
||||||
@certs.encoder
|
|
||||||
def certs(value): # pylint: disable=missing-docstring,no-self-argument
|
|
||||||
return tuple(jose.encode_cert(cert) for cert in value)
|
|
||||||
|
|
||||||
@certs.decoder
|
|
||||||
def certs(value): # pylint: disable=missing-docstring,no-self-argument
|
|
||||||
return tuple(jose.decode_cert(cert) for cert in value)
|
|
||||||
|
|
||||||
alg = jose.Field("alg", decoder=jose.JWASignature.from_json)
|
|
||||||
nonce = jose.Field(
|
|
||||||
"nonce", encoder=jose.encode_b64jose, decoder=functools.partial(
|
|
||||||
jose.decode_b64jose, size=NONCE_SIZE))
|
|
||||||
hints = jose.Field("hints", decoder=Hints.from_json)
|
|
||||||
|
|
||||||
|
|
||||||
@ChallengeResponse.register
|
|
||||||
class ProofOfPossessionResponse(ChallengeResponse):
|
|
||||||
"""ACME "proofOfPossession" challenge response.
|
|
||||||
|
|
||||||
:ivar bytes nonce: Random data, **not** base64-encoded.
|
|
||||||
:ivar acme.other.Signature signature: Sugnature of this message.
|
|
||||||
|
|
||||||
"""
|
|
||||||
typ = "proofOfPossession"
|
|
||||||
|
|
||||||
NONCE_SIZE = ProofOfPossession.NONCE_SIZE
|
|
||||||
|
|
||||||
nonce = jose.Field(
|
|
||||||
"nonce", encoder=jose.encode_b64jose, decoder=functools.partial(
|
|
||||||
jose.decode_b64jose, size=NONCE_SIZE))
|
|
||||||
signature = jose.Field("signature", decoder=other.Signature.from_json)
|
|
||||||
|
|
||||||
def verify(self):
|
|
||||||
"""Verify the challenge."""
|
|
||||||
# self.signature is not Field | pylint: disable=no-member
|
|
||||||
return self.signature.verify(self.nonce)
|
|
||||||
|
|
||||||
|
|
||||||
@Challenge.register # pylint: disable=too-many-ancestors
|
@Challenge.register # pylint: disable=too-many-ancestors
|
||||||
class DNS(_TokenDVChallenge):
|
class DNS(_TokenDVChallenge):
|
||||||
"""ACME "dns" challenge."""
|
"""ACME "dns" challenge."""
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from six.moves.urllib import parse as urllib_parse # pylint: disable=import-err
|
|||||||
|
|
||||||
from acme import errors
|
from acme import errors
|
||||||
from acme import jose
|
from acme import jose
|
||||||
from acme import other
|
|
||||||
from acme import test_util
|
from acme import test_util
|
||||||
|
|
||||||
|
|
||||||
@@ -324,233 +323,6 @@ class TLSSNI01Test(unittest.TestCase):
|
|||||||
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key)
|
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key)
|
||||||
|
|
||||||
|
|
||||||
class RecoveryContactTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
from acme.challenges import RecoveryContact
|
|
||||||
self.msg = RecoveryContact(
|
|
||||||
activation_url='https://example.ca/sendrecovery/a5bd99383fb0',
|
|
||||||
success_url='https://example.ca/confirmrecovery/bb1b9928932',
|
|
||||||
contact='c********n@example.com')
|
|
||||||
self.jmsg = {
|
|
||||||
'type': 'recoveryContact',
|
|
||||||
'activationURL': 'https://example.ca/sendrecovery/a5bd99383fb0',
|
|
||||||
'successURL': 'https://example.ca/confirmrecovery/bb1b9928932',
|
|
||||||
'contact': 'c********n@example.com',
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
|
||||||
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
|
||||||
|
|
||||||
def test_from_json(self):
|
|
||||||
from acme.challenges import RecoveryContact
|
|
||||||
self.assertEqual(self.msg, RecoveryContact.from_json(self.jmsg))
|
|
||||||
|
|
||||||
def test_from_json_hashable(self):
|
|
||||||
from acme.challenges import RecoveryContact
|
|
||||||
hash(RecoveryContact.from_json(self.jmsg))
|
|
||||||
|
|
||||||
def test_json_without_optionals(self):
|
|
||||||
del self.jmsg['activationURL']
|
|
||||||
del self.jmsg['successURL']
|
|
||||||
del self.jmsg['contact']
|
|
||||||
|
|
||||||
from acme.challenges import RecoveryContact
|
|
||||||
msg = RecoveryContact.from_json(self.jmsg)
|
|
||||||
|
|
||||||
self.assertTrue(msg.activation_url is None)
|
|
||||||
self.assertTrue(msg.success_url is None)
|
|
||||||
self.assertTrue(msg.contact is None)
|
|
||||||
self.assertEqual(self.jmsg, msg.to_partial_json())
|
|
||||||
|
|
||||||
|
|
||||||
class RecoveryContactResponseTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
from acme.challenges import RecoveryContactResponse
|
|
||||||
self.msg = RecoveryContactResponse(token='23029d88d9e123e')
|
|
||||||
self.jmsg = {
|
|
||||||
'resource': 'challenge',
|
|
||||||
'type': 'recoveryContact',
|
|
||||||
'token': '23029d88d9e123e',
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
|
||||||
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
|
||||||
|
|
||||||
def test_from_json(self):
|
|
||||||
from acme.challenges import RecoveryContactResponse
|
|
||||||
self.assertEqual(
|
|
||||||
self.msg, RecoveryContactResponse.from_json(self.jmsg))
|
|
||||||
|
|
||||||
def test_from_json_hashable(self):
|
|
||||||
from acme.challenges import RecoveryContactResponse
|
|
||||||
hash(RecoveryContactResponse.from_json(self.jmsg))
|
|
||||||
|
|
||||||
def test_json_without_optionals(self):
|
|
||||||
del self.jmsg['token']
|
|
||||||
|
|
||||||
from acme.challenges import RecoveryContactResponse
|
|
||||||
msg = RecoveryContactResponse.from_json(self.jmsg)
|
|
||||||
|
|
||||||
self.assertTrue(msg.token is None)
|
|
||||||
self.assertEqual(self.jmsg, msg.to_partial_json())
|
|
||||||
|
|
||||||
|
|
||||||
class ProofOfPossessionHintsTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
jwk = KEY.public_key()
|
|
||||||
issuers = (
|
|
||||||
'C=US, O=SuperT LLC, CN=SuperTrustworthy Public CA',
|
|
||||||
'O=LessTrustworthy CA Inc, CN=LessTrustworthy But StillSecure',
|
|
||||||
)
|
|
||||||
cert_fingerprints = (
|
|
||||||
'93416768eb85e33adc4277f4c9acd63e7418fcfe',
|
|
||||||
'16d95b7b63f1972b980b14c20291f3c0d1855d95',
|
|
||||||
'48b46570d9fc6358108af43ad1649484def0debf',
|
|
||||||
)
|
|
||||||
subject_key_identifiers = ('d0083162dcc4c8a23ecb8aecbd86120e56fd24e5')
|
|
||||||
authorized_for = ('www.example.com', 'example.net')
|
|
||||||
serial_numbers = (34234239832, 23993939911, 17)
|
|
||||||
|
|
||||||
from acme.challenges import ProofOfPossession
|
|
||||||
self.msg = ProofOfPossession.Hints(
|
|
||||||
jwk=jwk, issuers=issuers, cert_fingerprints=cert_fingerprints,
|
|
||||||
certs=(CERT,), subject_key_identifiers=subject_key_identifiers,
|
|
||||||
authorized_for=authorized_for, serial_numbers=serial_numbers)
|
|
||||||
|
|
||||||
self.jmsg_to = {
|
|
||||||
'jwk': jwk,
|
|
||||||
'certFingerprints': cert_fingerprints,
|
|
||||||
'certs': (jose.encode_b64jose(OpenSSL.crypto.dump_certificate(
|
|
||||||
OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)),),
|
|
||||||
'subjectKeyIdentifiers': subject_key_identifiers,
|
|
||||||
'serialNumbers': serial_numbers,
|
|
||||||
'issuers': issuers,
|
|
||||||
'authorizedFor': authorized_for,
|
|
||||||
}
|
|
||||||
self.jmsg_from = self.jmsg_to.copy()
|
|
||||||
self.jmsg_from.update({'jwk': jwk.to_json()})
|
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
|
||||||
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
|
|
||||||
|
|
||||||
def test_from_json(self):
|
|
||||||
from acme.challenges import ProofOfPossession
|
|
||||||
self.assertEqual(
|
|
||||||
self.msg, ProofOfPossession.Hints.from_json(self.jmsg_from))
|
|
||||||
|
|
||||||
def test_from_json_hashable(self):
|
|
||||||
from acme.challenges import ProofOfPossession
|
|
||||||
hash(ProofOfPossession.Hints.from_json(self.jmsg_from))
|
|
||||||
|
|
||||||
def test_json_without_optionals(self):
|
|
||||||
for optional in ['certFingerprints', 'certs', 'subjectKeyIdentifiers',
|
|
||||||
'serialNumbers', 'issuers', 'authorizedFor']:
|
|
||||||
del self.jmsg_from[optional]
|
|
||||||
del self.jmsg_to[optional]
|
|
||||||
|
|
||||||
from acme.challenges import ProofOfPossession
|
|
||||||
msg = ProofOfPossession.Hints.from_json(self.jmsg_from)
|
|
||||||
|
|
||||||
self.assertEqual(msg.cert_fingerprints, ())
|
|
||||||
self.assertEqual(msg.certs, ())
|
|
||||||
self.assertEqual(msg.subject_key_identifiers, ())
|
|
||||||
self.assertEqual(msg.serial_numbers, ())
|
|
||||||
self.assertEqual(msg.issuers, ())
|
|
||||||
self.assertEqual(msg.authorized_for, ())
|
|
||||||
|
|
||||||
self.assertEqual(self.jmsg_to, msg.to_partial_json())
|
|
||||||
|
|
||||||
|
|
||||||
class ProofOfPossessionTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
from acme.challenges import ProofOfPossession
|
|
||||||
hints = ProofOfPossession.Hints(
|
|
||||||
jwk=KEY.public_key(), cert_fingerprints=(),
|
|
||||||
certs=(), serial_numbers=(), subject_key_identifiers=(),
|
|
||||||
issuers=(), authorized_for=())
|
|
||||||
self.msg = ProofOfPossession(
|
|
||||||
alg=jose.RS256, hints=hints,
|
|
||||||
nonce=b'xD\xf9\xb9\xdbU\xed\xaa\x17\xf1y|\x81\x88\x99 ')
|
|
||||||
|
|
||||||
self.jmsg_to = {
|
|
||||||
'type': 'proofOfPossession',
|
|
||||||
'alg': jose.RS256,
|
|
||||||
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
|
|
||||||
'hints': hints,
|
|
||||||
}
|
|
||||||
self.jmsg_from = {
|
|
||||||
'type': 'proofOfPossession',
|
|
||||||
'alg': jose.RS256.to_json(),
|
|
||||||
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
|
|
||||||
'hints': hints.to_json(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
|
||||||
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
|
|
||||||
|
|
||||||
def test_from_json(self):
|
|
||||||
from acme.challenges import ProofOfPossession
|
|
||||||
self.assertEqual(
|
|
||||||
self.msg, ProofOfPossession.from_json(self.jmsg_from))
|
|
||||||
|
|
||||||
def test_from_json_hashable(self):
|
|
||||||
from acme.challenges import ProofOfPossession
|
|
||||||
hash(ProofOfPossession.from_json(self.jmsg_from))
|
|
||||||
|
|
||||||
|
|
||||||
class ProofOfPossessionResponseTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
# acme-spec uses a confusing example in which both signature
|
|
||||||
# nonce and challenge nonce are the same, don't make the same
|
|
||||||
# mistake here...
|
|
||||||
signature = other.Signature(
|
|
||||||
alg=jose.RS256, jwk=KEY.public_key(),
|
|
||||||
sig=b'\xa7\xc1\xe7\xe82o\xbc\xcd\xd0\x1e\x010#Z|\xaf\x15\x83'
|
|
||||||
b'\x94\x8f#\x9b\nQo(\x80\x15,\x08\xfcz\x1d\xfd\xfd.\xaap'
|
|
||||||
b'\xfa\x06\xd1\xa2f\x8d8X2>%d\xbd%\xe1T\xdd\xaa0\x18\xde'
|
|
||||||
b'\x99\x08\xf0\x0e{',
|
|
||||||
nonce=b'\x99\xc7Q\xb3f2\xbc\xdci\xfe\xd6\x98k\xc67\xdf',
|
|
||||||
)
|
|
||||||
|
|
||||||
from acme.challenges import ProofOfPossessionResponse
|
|
||||||
self.msg = ProofOfPossessionResponse(
|
|
||||||
nonce=b'xD\xf9\xb9\xdbU\xed\xaa\x17\xf1y|\x81\x88\x99 ',
|
|
||||||
signature=signature)
|
|
||||||
|
|
||||||
self.jmsg_to = {
|
|
||||||
'resource': 'challenge',
|
|
||||||
'type': 'proofOfPossession',
|
|
||||||
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
|
|
||||||
'signature': signature,
|
|
||||||
}
|
|
||||||
self.jmsg_from = {
|
|
||||||
'resource': 'challenge',
|
|
||||||
'type': 'proofOfPossession',
|
|
||||||
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
|
|
||||||
'signature': signature.to_json(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_verify(self):
|
|
||||||
self.assertTrue(self.msg.verify())
|
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
|
||||||
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
|
|
||||||
|
|
||||||
def test_from_json(self):
|
|
||||||
from acme.challenges import ProofOfPossessionResponse
|
|
||||||
self.assertEqual(
|
|
||||||
self.msg, ProofOfPossessionResponse.from_json(self.jmsg_from))
|
|
||||||
|
|
||||||
def test_from_json_hashable(self):
|
|
||||||
from acme.challenges import ProofOfPossessionResponse
|
|
||||||
hash(ProofOfPossessionResponse.from_json(self.jmsg_from))
|
|
||||||
|
|
||||||
|
|
||||||
class DNSTest(unittest.TestCase):
|
class DNSTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -271,10 +271,8 @@ class AuthorizationTest(unittest.TestCase):
|
|||||||
ChallengeBody(uri='http://challb2', status=STATUS_VALID,
|
ChallengeBody(uri='http://challb2', status=STATUS_VALID,
|
||||||
chall=challenges.DNS(
|
chall=challenges.DNS(
|
||||||
token=b'DGyRejmCefe7v4NfDGDKfA')),
|
token=b'DGyRejmCefe7v4NfDGDKfA')),
|
||||||
ChallengeBody(uri='http://challb3', status=STATUS_VALID,
|
|
||||||
chall=challenges.RecoveryContact()),
|
|
||||||
)
|
)
|
||||||
combinations = ((0, 2), (1, 2))
|
combinations = ((0,), (1,))
|
||||||
|
|
||||||
from acme.messages import Authorization
|
from acme.messages import Authorization
|
||||||
from acme.messages import Identifier
|
from acme.messages import Identifier
|
||||||
@@ -300,8 +298,8 @@ class AuthorizationTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_resolved_combinations(self):
|
def test_resolved_combinations(self):
|
||||||
self.assertEqual(self.authz.resolved_combinations, (
|
self.assertEqual(self.authz.resolved_combinations, (
|
||||||
(self.challbs[0], self.challbs[2]),
|
(self.challbs[0],),
|
||||||
(self.challbs[1], self.challbs[2]),
|
(self.challbs[1],),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user