diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 13d19d3c4..0b15e7fb4 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -13,7 +13,6 @@ from acme import errors from acme import crypto_util from acme import fields from acme import jose -from acme import other logger = logging.getLogger(__name__) @@ -36,10 +35,6 @@ class Challenge(jose.TypedJSONObjectWithFields): return UnrecognizedChallenge.from_json(jobj) -class ContinuityChallenge(Challenge): # pylint: disable=abstract-method - """Client validation challenges.""" - - class DVChallenge(Challenge): # pylint: disable=abstract-method """Domain validation challenges.""" @@ -460,106 +455,6 @@ class TLSSNI01(KeyAuthorizationChallenge): 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 class DNS(_TokenDVChallenge): """ACME "dns" challenge.""" diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index ef78e1eba..04b7442b0 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -9,7 +9,6 @@ from six.moves.urllib import parse as urllib_parse # pylint: disable=import-err from acme import errors from acme import jose -from acme import other 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) -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): def setUp(self): diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py index 8e74826bf..fa558cf4a 100644 --- a/acme/acme/messages_test.py +++ b/acme/acme/messages_test.py @@ -271,10 +271,8 @@ class AuthorizationTest(unittest.TestCase): ChallengeBody(uri='http://challb2', status=STATUS_VALID, chall=challenges.DNS( 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 Identifier @@ -300,8 +298,8 @@ class AuthorizationTest(unittest.TestCase): def test_resolved_combinations(self): self.assertEqual(self.authz.resolved_combinations, ( - (self.challbs[0], self.challbs[2]), - (self.challbs[1], self.challbs[2]), + (self.challbs[0],), + (self.challbs[1],), ))