diff --git a/letsencrypt/acme/messages.py b/letsencrypt/acme/messages.py index 73602911f..df7e2c0cc 100644 --- a/letsencrypt/acme/messages.py +++ b/letsencrypt/acme/messages.py @@ -1,8 +1,4 @@ """ACME protocol messages.""" -import json -import pkg_resources - -import jsonschema import M2Crypto import zope.interface @@ -13,25 +9,7 @@ from letsencrypt.acme import other from letsencrypt.acme import util -SCHEMATA = dict([ - (schema, json.load(open(pkg_resources.resource_filename( - __name__, "schemata/%s.json" % schema)))) for schema in [ - "authorization", - "authorizationRequest", - "certificate", - "certificateRequest", - "challenge", - "challengeRequest", - "defer", - "error", - "revocation", - "revocationRequest", - "statusRequest", - ] -]) - - -class Message(object): +class Message(util.JSONDeSerializable, util.ImmutableMap): """ACME message. Messages are considered immutable. @@ -51,17 +29,6 @@ class Message(object): cls.TYPES[msg_cls.acme_type] = msg_cls return msg_cls - @classmethod - def schema(cls, schemata=None): - """Get JSON schema for this ACME message. - - :param dict schemata: Mapping from type name to JSON Schema - definition. Useful for testing. - - """ - schemata = SCHEMATA if schemata is None else schemata - return schemata[cls.acme_type] - def to_json(self): """Get JSON serializable object. @@ -85,34 +52,23 @@ class Message(object): :rtype: dict """ - raise NotImplementedError - - def json_dumps(self): - """Dump to JSON using proper serializer. - - :returns: JSON serialized string. - :rtype: str - - """ - return json.dumps(self, default=util.dump_ijsonserializable) + raise NotImplementedError() @classmethod - def validate(cls, jobj, schemata=None): - """Is JSON object a valid ACME message? + def from_json(cls, jobj, validate=True): + """Deserialize validated ACME message from JSON string. - :param str jobj: JSON object - - :param dict schemata: Mapping from type name to JSON Schema - definition. Useful for testing. - - :returns: ACME message class, subclassing :class:`Message`. + :param str jobj: JSON object. + :param bool validate: Validate against schema before deserializing. + Useful if :class:`JWK` is part of already validated json object. :raises letsencrypt.acme.errors.ValidationError: if validation was unsuccessful - """ - schemata = SCHEMATA if schemata is None else schemata + :returns: Valid ACME message. + :rtype: subclass of :class:`Message` + """ if not isinstance(jobj, dict): raise errors.ValidationError( "{0} is not a dictionary object".format(jobj)) @@ -122,64 +78,22 @@ class Message(object): raise errors.ValidationError("missing type field") try: - schema = schemata[msg_type] # pylint: disable=redefined-outer-name msg_cls = cls.TYPES[msg_type] except KeyError: raise errors.UnrecognnizedMessageTypeError(msg_type) - try: - jsonschema.validate(jobj, schema) - except jsonschema.ValidationError as error: - raise errors.SchemaValidationError(error) - - return msg_cls - - @classmethod - def from_json(cls, json_string, schemata=None): - """Deserialize validated ACME message from JSON string. - - :param str json_string: JSON serialize string. - :param dict schemata: Mapping from type name to JSON Schema - definition. Useful for testing. - - :raises letsencrypt.acme.errors.ValidationError: if validation - was unsuccessful - - :returns: Valid ACME message. - :rtype: subclass of :class:`Message` - - """ - jobj = json.loads(json_string) - msg_cls = cls.validate(jobj, schemata) + if validate: + msg_cls.validate_json(jobj) # pylint: disable=protected-access - return msg_cls._valid_from_json(jobj) - - @classmethod - def _valid_from_json(cls, jobj): - """Deserialize from valid ACME message JSON object. - - Subclasses must override. - - :param jobj: Schema validated ACME message JSON object. - :type jobj: dict - - :returns: Valid ACME message. - :rtype: subclass of :class:`Message` - - """ - raise NotImplementedError + return msg_cls._from_valid_json(jobj) @Message.register # pylint: disable=too-few-public-methods class Challenge(Message): """ACME "challenge" message.""" acme_type = "challenge" - - def __init__(self, session_id, nonce, challenges, combinations=None): - self.session_id = session_id - self.nonce = nonce - self.challenges = challenges - self.combinations = [] if combinations is None else combinations + schema = util.load_schema(acme_type) + __slots__ = ("session_id", "nonce", "challenges", "combinations") def _fields_to_json(self): fields = { @@ -192,9 +106,11 @@ class Challenge(Message): return fields @classmethod - def _valid_from_json(cls, jobj): - return cls(jobj["sessionID"], jose.b64decode(jobj["nonce"]), - jobj["challenges"], jobj.get("combinations")) + def _from_valid_json(cls, jobj): + return cls(session_id=jobj["sessionID"], + nonce=jose.b64decode(jobj["nonce"]), + challenges=jobj["challenges"], + combinations=jobj.get("combinations", [])) @Message.register # pylint: disable=too-few-public-methods @@ -205,9 +121,8 @@ class ChallengeRequest(Message): """ acme_type = "challengeRequest" - - def __init__(self, identifier): - self.identifier = identifier + schema = util.load_schema(acme_type) + __slots__ = ("identifier",) def _fields_to_json(self): return { @@ -215,19 +130,16 @@ class ChallengeRequest(Message): } @classmethod - def _valid_from_json(cls, json_string): - return cls(json_string["identifier"]) + def _from_valid_json(cls, jobj): + return cls(identifier=jobj["identifier"]) @Message.register # pylint: disable=too-few-public-methods class Authorization(Message): """ACME "authorization" message.""" acme_type = "authorization" - - def __init__(self, recovery_token=None, identifier=None, jwk=None): - self.recovery_token = recovery_token - self.identifier = identifier - self.jwk = jwk + schema = util.load_schema(acme_type) + __slots__ = ("recovery_token", "identifier", "jwk") def _fields_to_json(self): fields = {} @@ -240,11 +152,12 @@ class Authorization(Message): return fields @classmethod - def _valid_from_json(cls, jobj): + def _from_valid_json(cls, jobj): jwk = jobj.get("jwk") if jwk is not None: - jwk = jose.JWK.from_json(jwk) - return cls(jobj.get("recoveryToken"), jobj.get("identifier"), jwk) + jwk = jose.JWK.from_json(jwk, validate=False) + return cls(recovery_token=jobj.get("recoveryToken"), + identifier=jobj.get("identifier"), jwk=jwk) @Message.register @@ -259,19 +172,15 @@ class AuthorizationRequest(Message): """ acme_type = "authorizationRequest" - - def __init__(self, session_id, nonce, responses, signature, contact=None): - self.session_id = session_id - self.nonce = nonce - self.responses = responses - self.signature = signature - self.contact = [] if contact is None else contact + schema = util.load_schema(acme_type) + __slots__ = ("session_id", "nonce", "responses", "signature", "contact") @classmethod - def create(cls, session_id, nonce, responses, name, key, - sig_nonce=None, contact=None): + def create(cls, name, key, sig_nonce=None, **kwargs): """Create signed "authorizationRequest". + :param str name: TODO + :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` @@ -282,8 +191,10 @@ class AuthorizationRequest(Message): """ # pylint: disable=too-many-arguments - signature = other.Signature.from_msg(name + nonce, key, sig_nonce) - return cls(session_id, nonce, responses, signature, contact) + signature = other.Signature.from_msg( + name + kwargs["nonce"], key, sig_nonce) + return cls( + signature=signature, contact=kwargs.pop("contact", []), **kwargs) def verify(self, name): """Verify signature. @@ -310,11 +221,13 @@ class AuthorizationRequest(Message): return fields @classmethod - def _valid_from_json(cls, jobj): - return cls(jobj["sessionID"], jose.b64decode(jobj["nonce"]), - jobj["responses"], - other.Signature.from_json(jobj["signature"]), - jobj.get("contact")) + def _from_valid_json(cls, jobj): + return cls(session_id=jobj["sessionID"], + nonce=jose.b64decode(jobj["nonce"]), + responses=jobj["responses"], + signature=other.Signature.from_json( + jobj["signature"], validate=False), + contact=jobj.get("contact", [])) @Message.register # pylint: disable=too-few-public-methods @@ -326,11 +239,8 @@ class Certificate(Message): """ acme_type = "certificate" - - def __init__(self, certificate, chain=None, refresh=None): - self.certificate = certificate - self.chain = [] if chain is None else chain - self.refresh = refresh + schema = util.load_schema(acme_type) + __slots__ = ("certificate", "chain", "refresh") def _fields_to_json(self): fields = { @@ -342,10 +252,11 @@ class Certificate(Message): return fields @classmethod - def _valid_from_json(cls, jobj): + def _from_valid_json(cls, jobj): certificate = M2Crypto.X509.load_cert_der_string( jose.b64decode(jobj["certificate"])) - return cls(certificate, jobj.get("chain"), jobj.get("refresh")) + return cls(certificate=certificate, chain=jobj.get("chain", []), + refresh=jobj.get("refresh")) @Message.register @@ -358,25 +269,24 @@ class CertificateRequest(Message): """ acme_type = "certificateRequest" - - def __init__(self, csr, signature): - self.csr = csr - self.signature = signature + schema = util.load_schema(acme_type) + __slots__ = ("csr", "signature") @classmethod - def create(cls, csr, key, nonce=None): + def create(cls, key, sig_nonce=None, **kwargs): """Create signed "certificateRequest". :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` - :param str nonce: Nonce used for signature. Useful for testing. + :param str sig_nonce: Nonce used for signature. Useful for testing. :returns: Signed "certificateRequest" ACME message. :rtype: :class:`CertificateRequest` """ - return cls(csr, other.Signature.from_msg(csr, key, nonce)) + return cls(signature=other.Signature.from_msg( + kwargs["csr"], key, sig_nonce), **kwargs) def verify(self): """Verify signature. @@ -396,20 +306,18 @@ class CertificateRequest(Message): } @classmethod - def _valid_from_json(cls, jobj): - return cls(jose.b64decode(jobj["csr"]), - other.Signature.from_json(jobj["signature"])) + def _from_valid_json(cls, jobj): + return cls(csr=jose.b64decode(jobj["csr"]), + signature=other.Signature.from_json( + jobj["signature"], validate=False)) @Message.register # pylint: disable=too-few-public-methods class Defer(Message): """ACME "defer" message.""" acme_type = "defer" - - def __init__(self, token, interval=None, message=None): - self.token = token - self.interval = interval # TODO: int - self.message = message + schema = util.load_schema(acme_type) + __slots__ = ("token", "interval", "message") def _fields_to_json(self): fields = {"token": self.token} @@ -420,14 +328,17 @@ class Defer(Message): return fields @classmethod - def _valid_from_json(cls, jobj): - return cls(jobj["token"], jobj.get("interval"), jobj.get("message")) + def _from_valid_json(cls, jobj): + return cls(token=jobj["token"], interval=jobj.get("interval"), + message=jobj.get("message")) @Message.register # pylint: disable=too-few-public-methods class Error(Message): """ACME "error" message.""" acme_type = "error" + schema = util.load_schema(acme_type) + __slots__ = ("error", "message", "more_info") CODES = { "malformed": "The request message was malformed", @@ -438,12 +349,6 @@ class Error(Message): "badCSR": "The CSR is unacceptable (e.g., due to a short key)", } - def __init__(self, error, message=None, more_info=None): - assert error in self.CODES # TODO: already checked by schema validation - self.error = error - self.message = message - self.more_info = more_info - def _fields_to_json(self): fields = {"error": self.error} if self.message is not None: @@ -453,20 +358,23 @@ class Error(Message): return fields @classmethod - def _valid_from_json(cls, jobj): - return cls(jobj["error"], jobj.get("message"), jobj.get("more_info")) + def _from_valid_json(cls, jobj): + return cls(error=jobj["error"], message=jobj.get("message"), + more_info=jobj.get("more_info")) @Message.register # pylint: disable=too-few-public-methods class Revocation(Message): """ACME "revocation" message.""" acme_type = "revocation" + schema = util.load_schema(acme_type) + __slots__ = () def _fields_to_json(self): return {} @classmethod - def _valid_from_json(cls, jobj): + def _from_valid_json(cls, jobj): return cls() @@ -481,26 +389,24 @@ class RevocationRequest(Message): """ acme_type = "revocationRequest" - - def __init__(self, certificate, signature): - self.certificate = certificate - self.signature = signature + schema = util.load_schema(acme_type) + __slots__ = ("certificate", "signature") @classmethod - def create(cls, certificate, key, nonce=None): + def create(cls, key, sig_nonce=None, **kwargs): """Create signed "revocationRequest". :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` - :param str nonce: Nonce used for signature. Useful for testing. + :param str sig_nonce: Nonce used for signature. Useful for testing. :returns: Signed "revocationRequest" ACME message. :rtype: :class:`RevocationRequest` """ - return cls(certificate, - other.Signature.from_msg(certificate, key, nonce)) + return cls(signature=other.Signature.from_msg( + kwargs["certificate"], key, sig_nonce), **kwargs) def verify(self): """Verify signature. @@ -520,9 +426,10 @@ class RevocationRequest(Message): } @classmethod - def _valid_from_json(cls, json_string): - return cls(jose.b64decode(json_string["certificate"]), - other.Signature.from_json(json_string["signature"])) + def _from_valid_json(cls, jobj): + return cls(certificate=jose.b64decode(jobj["certificate"]), + signature=other.Signature.from_json( + jobj["signature"], validate=False)) @Message.register # pylint: disable=too-few-public-methods @@ -533,15 +440,12 @@ class StatusRequest(Message): """ acme_type = "statusRequest" - - def __init__(self, token): - self.token = token + schema = util.load_schema(acme_type) + __slots__ = ("token",) def _fields_to_json(self): - return { - "token": self.token, - } + return {"token": self.token} @classmethod - def _valid_from_json(cls, json_string): - return cls(json_string["token"]) + def _from_valid_json(cls, jobj): + return cls(token=jobj["token"]) diff --git a/letsencrypt/acme/messages_test.py b/letsencrypt/acme/messages_test.py index beb7a9fb5..9637fe852 100644 --- a/letsencrypt/acme/messages_test.py +++ b/letsencrypt/acme/messages_test.py @@ -6,6 +6,9 @@ import Crypto.PublicKey.RSA import mock from letsencrypt.acme import errors +from letsencrypt.acme import jose +from letsencrypt.acme import other + KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string( 'letsencrypt.client.tests', 'testdata/rsa256_key.pem')) @@ -15,158 +18,228 @@ class MessageTest(unittest.TestCase): """Tests for letsencrypt.acme.messages.Message.""" def setUp(self): - self.schemata = { - 'foo': { + # pylint: disable=missing-docstring,too-few-public-methods + from letsencrypt.acme.messages import Message + class TestMessage(Message): + acme_type = 'test' + schema = { 'type': 'object', 'properties': { 'price': {'type': 'number'}, 'name': {'type': 'string'}, }, - }, - } + } + @classmethod + def _from_valid_json(cls, jobj): + return jobj - def _validate(self, jobj): + def _fields_to_json(self): + pass + + self.msg_cls = TestMessage + + @classmethod + def _from_json(cls, jobj, validate=True): from letsencrypt.acme.messages import Message - return Message.validate(jobj, self.schemata) + return Message.from_json(jobj, validate) - def test_validate_non_dictionary_fails(self): - self.assertRaises(errors.ValidationError, self._validate, []) + def test_from_json_non_dict_fails(self): + self.assertRaises(errors.ValidationError, self._from_json, []) - def test_validate_dict_without_type_fails(self): - self.assertRaises(errors.ValidationError, self._validate, {}) + def test_from_json_dict_no_type_fails(self): + self.assertRaises(errors.ValidationError, self._from_json, {}) - def test_validate_unknown_type_fails(self): + def test_from_json_unknown_type_fails(self): self.assertRaises(errors.UnrecognnizedMessageTypeError, - self._validate, {'type': 'bar'}) - - def test_validate_unregistered_type_fails(self): - self.assertRaises(errors.UnrecognnizedMessageTypeError, - self._validate, {'type': 'foo'}) + self._from_json, {'type': 'bar'}) @mock.patch('letsencrypt.acme.messages.Message.TYPES') - def test_validate_invalid_fails(self, types): - types.__getitem__.side_effect = lambda x: {'foo': 'bar'}[x] + def test_from_json_validate_errors(self, types): + types.__getitem__.side_effect = lambda x: {'foo': self.msg_cls}[x] self.assertRaises(errors.SchemaValidationError, - self._validate, {'type': 'foo', 'price': 'asd'}) + self._from_json, {'type': 'foo', 'price': 'asd'}) @mock.patch('letsencrypt.acme.messages.Message.TYPES') - def test_validate_valid_returns_cls(self, types): - types.__getitem__.side_effect = lambda x: {'foo': 'bar'}[x] - self.assertEqual(self._validate({'type': 'foo'}), 'bar') + def test_from_json_valid_returns_cls(self, types): + types.__getitem__.side_effect = lambda x: {'foo': self.msg_cls}[x] + self.assertEqual(self._from_json({'type': 'foo'}, validate=False), + {'type': 'foo'}) class ChallengeRequestTest(unittest.TestCase): - # pylint: disable=too-few-public-methods - def test_it(self): + def setUp(self): from letsencrypt.acme.messages import ChallengeRequest - msg = ChallengeRequest('example.com') + self.msg = ChallengeRequest(identifier='example.com') - jmsg = msg._fields_to_json() # pylint: disable=protected-access - self.assertEqual(jmsg, { + self.jmsg = { + 'type': 'challengeRequest', 'identifier': 'example.com', - }) + } + + def test_to_json(self): + self.assertEqual(self.msg.to_json(), self.jmsg) + + def test_from_json(self): + from letsencrypt.acme.messages import ChallengeRequest + self.assertEqual(ChallengeRequest.from_json(self.jmsg), self.msg) class AuthorizationRequestTest(unittest.TestCase): def setUp(self): - self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' - self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' - self.csr = 'TODO: real DER CSR?' - - def test_authorization_request(self): - from letsencrypt.acme.messages import AuthorizationRequest - responses = [ - { - 'type': 'simpleHttps', - 'path': 'Hf5GrX4Q7EBax9hc2jJnfw', - }, + self.responses = [ + {'type': 'simpleHttps', 'path': 'Hf5GrX4Q7EBax9hc2jJnfw'}, None, # null - { - 'type': 'recoveryToken', - 'token': '23029d88d9e123e', - } + {'type': 'recoveryToken', 'token': '23029d88d9e123e'}, ] - msg = AuthorizationRequest.create( - 'aefoGaavieG9Wihuk2aufai3aeZ5EeW4', - 'czpsrF0KMH6dgajig3TGHw', - responses, - 'example.com', - KEY, - self.nonce, - ) - msg.verify('example.com') + signature = other.Signature( + alg='RS256', jwk=jose.JWK(key=KEY.publickey()), + sig='-v\xd8\xc2\xa3\xba0\xd6\x92\x16\xb5.\xbe\xa1[\x04\xbe' + '\x1b\xa1X\xd2)\x18\x94\x8f\xd7\xd0\xc0\xbbcI`W\xdf v' + '\xe4\xed\xe8\x03J\xe8\xc8\x06\xf9yd\xf9\xfe\xf8\xd1>\x9aKH' + '\xd7\xba\xb9a1\xf5!p\x1b\xd7}\xbaj\xa7\xe3\xd9\xd9\t%' + '\xbb\xba\xc9\x00\xdaW\x16\xe9', + nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9') - def test_it(self): from letsencrypt.acme.messages import CertificateRequest - msg = CertificateRequest.create(self.csr, KEY, self.nonce) - self.assertTrue(msg.verify()) + self.msg = CertificateRequest(csr=self.csr, signature=signature) - jmsg = msg._fields_to_json() # pylint: disable=protected-access - jmsg.pop('signature') - self.assertEqual(jmsg, { + self.jmsg = { + 'type': 'certificateRequest', 'csr': 'VE9ETzogcmVhbCBERVIgQ1NSPw', - }) + 'signature': signature, + } + + def test_create(self): + from letsencrypt.acme.messages import CertificateRequest + self.assertEqual(self.msg, CertificateRequest.create( + csr=self.csr, key=KEY, + sig_nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9')) + + def test_verify(self): + self.assertTrue(self.msg.verify()) + + def test_to_json(self): + self.assertEqual(self.msg.to_json(), self.jmsg) + + def test_from_json(self): + from letsencrypt.acme.messages import CertificateRequest + self.jmsg['signature'] = self.jmsg['signature'].to_json() + self.jmsg['signature']['jwk'] = self.jmsg['signature']['jwk'].to_json() + self.assertEqual(self.msg, CertificateRequest.from_json(self.jmsg)) class RevocationRequestTest(unittest.TestCase): def setUp(self): + self.sig_nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' + self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' - self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' self.certificate = 'TODO: real DER cert?' - def test_it(self): - from letsencrypt.acme.messages import RevocationRequest - msg = RevocationRequest.create(self.certificate, KEY, self.nonce) - self.assertTrue(msg.verify()) + signature = other.Signature( + alg='RS256', jwk=jose.JWK(key=KEY.publickey()), + sig='\x00\x15\xc0\xd4\x8b2M\xa9S\\\x8a#\xc6a\xa7!A\xb2d\x04' + '\xa6\xbe\xa1/M\x0f|\x8c\x9eJ\x16\xcd\x85N\xcc\x0b\x12k(' + '\xa8U\xdfS\xa9y\xfd\xfa.\xb3\xeblms\x9f,\xdf\xbb>7\xd9' + '\xe5u\x8f\xbe', + nonce=self.sig_nonce) - jmsg = msg._fields_to_json() # pylint: disable=protected-access - jmsg.pop('signature') - self.assertEqual(jmsg, { + from letsencrypt.acme.messages import RevocationRequest + self.msg = RevocationRequest( + certificate=self.certificate, signature=signature) + + self.jmsg = { + 'type': 'revocationRequest', 'certificate': 'VE9ETzogcmVhbCBERVIgY2VydD8', - }) + 'signature': signature, + } + + def test_create(self): + from letsencrypt.acme.messages import RevocationRequest + RevocationRequest.create( + certificate=self.certificate, key=KEY, sig_nonce=self.sig_nonce) + + def test_verify(self): + self.assertTrue(self.msg.verify()) + + def test_to_json(self): + self.assertEqual(self.msg.to_json(), self.jmsg) + + def test_from_json(self): + from letsencrypt.acme.messages import RevocationRequest + self.jmsg['signature'] = self.jmsg['signature'].to_json() + self.jmsg['signature']['jwk'] = self.jmsg['signature']['jwk'].to_json() + self.assertEqual(self.msg, RevocationRequest.from_json(self.jmsg)) class StatusRequestTest(unittest.TestCase): def setUp(self): from letsencrypt.acme.messages import StatusRequest - self.token = u'O7-s9MNq1siZHlgrMzi9_A' - self.msg = StatusRequest(self.token) + self.msg = StatusRequest(token=u'O7-s9MNq1siZHlgrMzi9_A') self.jmsg = { - 'token': self.token, + 'type': 'statusRequest', + 'token': u'O7-s9MNq1siZHlgrMzi9_A', } - def test_attributes(self): - self.assertEqual(self.msg.token, self.token) - - def test_json(self): - jmsg = self.msg._fields_to_json() # pylint: disable=protected-access - self.assertEqual(jmsg, self.jmsg) + def test_to_json(self): + self.assertEqual(self.msg.to_json(), self.jmsg) + def test_from_json(self): from letsencrypt.acme.messages import StatusRequest - # pylint: disable=protected-access - msg = StatusRequest._valid_from_json(self.jmsg) - self.assertEqual(msg.token, self.msg.token) + self.assertEqual(StatusRequest.from_json(self.jmsg), self.msg) if __name__ == '__main__': diff --git a/letsencrypt/client/auth_handler.py b/letsencrypt/client/auth_handler.py index 7b4b09eb1..80d08e5fc 100644 --- a/letsencrypt/client/auth_handler.py +++ b/letsencrypt/client/auth_handler.py @@ -111,11 +111,12 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes try: auth = self.network.send_and_receive_expected( acme.messages.AuthorizationRequest.create( - self.msgs[domain].session_id, - self.msgs[domain].nonce, - self.responses[domain], - domain, - Crypto.PublicKey.RSA.importKey(self.authkey[domain].pem)), + session_id=self.msgs[domain].session_id, + nonce=self.msgs[domain].nonce, + responses=self.responses[domain], + name=domain, + key=Crypto.PublicKey.RSA.importKey( + self.authkey[domain].pem)), acme.messages.Authorization) logging.info("Received Authorization for %s", domain) return auth diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index bfab53107..309e288c8 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -122,7 +122,8 @@ class Client(object): """ return self.network.send_and_receive_expected( - acme.messages.ChallengeRequest(domain), acme.messages.Challenge) + acme.messages.ChallengeRequest(identifier=domain), + acme.messages.Challenge) def acme_certificate(self, csr_der): """Handle ACME "certificate" phase. @@ -136,7 +137,8 @@ class Client(object): logging.info("Preparing and sending CSR...") return self.network.send_and_receive_expected( acme.messages.CertificateRequest.create( - csr_der, Crypto.PublicKey.RSA.importKey(self.authkey.pem)), + csr=csr_der, key=Crypto.PublicKey.RSA.importKey( + self.authkey.pem)), acme.messages.Certificate) def save_certificate(self, certificate_msg, cert_path, chain_path): diff --git a/letsencrypt/client/network.py b/letsencrypt/client/network.py index 164d0810b..390e0d922 100644 --- a/letsencrypt/client/network.py +++ b/letsencrypt/client/network.py @@ -53,7 +53,7 @@ class Network(object): raise errors.LetsEncryptClientError( 'Sending ACME message to server has failed: %s' % error) - return acme.messages.Message.from_json(response.json()) + return acme.messages.Message.from_json(response.json(), validate=True) def send_and_receive_expected(self, msg, expected): """Send ACME message to server and return expected message. @@ -101,7 +101,7 @@ class Network(object): logging.info("Waiting for %d seconds...", delay) time.sleep(delay) response = self.send( - acme.messages.StatusRequest(response.token)) + acme.messages.StatusRequest(token=response.token)) else: logging.fatal("Received unexpected message") logging.fatal("Expected: %s", expected) diff --git a/letsencrypt/client/revoker.py b/letsencrypt/client/revoker.py index bd7053789..04541230b 100644 --- a/letsencrypt/client/revoker.py +++ b/letsencrypt/client/revoker.py @@ -38,7 +38,7 @@ class Revoker(object): revocation = self.network.send_and_receive_expected( acme.messages.RevocationRequest.create( - cert_der, Crypto.PublicKey.RSA.importKey(key)), + certificate=cert_der, key=Crypto.PublicKey.RSA.importKey(key)), acme.messages.Revocation) zope.component.getUtility(interfaces.IDisplay).generic_notification( diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/client/tests/auth_handler_test.py index 69ca2bc25..28182c7e7 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/client/tests/auth_handler_test.py @@ -41,7 +41,8 @@ class SatisfyChallengesTest(unittest.TestCase): def test_name1_dvsni1(self): dom = "0" challenge = [acme_util.CHALLENGES["dvsni"]] - msg = acme.messages.Challenge(dom, "nonce0", challenge) + msg = acme.messages.Challenge(session_id=dom, nonce="nonce0", + challenges=challenge, combinations=[]) self.handler.add_chall_msg(dom, msg, "dummy_key") self.handler._satisfy_challenges() # pylint: disable=protected-access @@ -60,7 +61,8 @@ class SatisfyChallengesTest(unittest.TestCase): for i in range(5): self.handler.add_chall_msg( str(i), - acme.messages.Challenge(str(i), "nonce%d" % i, challenge), + acme.messages.Challenge(session_id=str(i), nonce="nonce%d" % i, + challenges=challenge, combinations=[]), "dummy_key") self.handler._satisfy_challenges() # pylint: disable=protected-access @@ -87,7 +89,8 @@ class SatisfyChallengesTest(unittest.TestCase): combos = acme_util.gen_combos(challenges) self.handler.add_chall_msg( dom, - acme.messages.Challenge("0", "nonce0", challenges, combos), + acme.messages.Challenge(session_id="0", nonce="nonce0", + challenges=challenges, combinations=combos), "dummy_key") path = gen_path(["simpleHttps"], challenges) @@ -116,7 +119,8 @@ class SatisfyChallengesTest(unittest.TestCase): combos = acme_util.gen_combos(challenges) self.handler.add_chall_msg( dom, - acme.messages.Challenge(dom, "nonce0", challenges, combos), + acme.messages.Challenge(session_id=dom, nonce="nonce0", + challenges=challenges, combinations=combos), "dummy_key") path = gen_path(["simpleHttps", "recoveryToken"], challenges) @@ -147,7 +151,8 @@ class SatisfyChallengesTest(unittest.TestCase): self.handler.add_chall_msg( str(i), acme.messages.Challenge( - str(i), "nonce%d" % i, challenges, combos), + session_id=str(i), nonce="nonce%d" % i, + challenges=challenges, combinations=combos), "dummy_key") path = gen_path(["dvsni", "recoveryContact"], challenges) @@ -197,7 +202,8 @@ class SatisfyChallengesTest(unittest.TestCase): self.handler.add_chall_msg( dom, acme.messages.Challenge( - dom, "nonce%d" % i, challenge_list[i]), + session_id=dom, nonce="nonce%d" % i, + challenges=challenge_list[i], combinations=[]), "dummy_key") mock_chall_path.side_effect = paths @@ -266,7 +272,8 @@ class GetAuthorizationsTest(unittest.TestCase): for i in range(3): self.handler.add_chall_msg( str(i), - acme.messages.Challenge(str(i), "nonce%d" % i, challenge), + acme.messages.Challenge(session_id=str(i), nonce="nonce%d" % i, + challenges=challenge, combinations=[]), "dummy_key") self.mock_sat_chall.side_effect = self._sat_solved_at_once @@ -294,7 +301,8 @@ class GetAuthorizationsTest(unittest.TestCase): challenges = acme_util.get_challenges() self.handler.add_chall_msg( "0", - acme.messages.Challenge("0", "nonce0", challenges), + acme.messages.Challenge(session_id="0", nonce="nonce0", + challenges=challenges, combinations=[]), "dummy_key") # Don't do anything to satisfy challenges @@ -322,7 +330,8 @@ class GetAuthorizationsTest(unittest.TestCase): dom = str(i) self.handler.add_chall_msg( dom, - acme.messages.Challenge(dom, "nonce%d" % i, challs[i]), + acme.messages.Challenge(session_id=dom, nonce="nonce%d" % i, + challenges=challs[i], combinations=[]), "dummy_key") self.mock_sat_chall.side_effect = self._sat_incremental