Use new framework for ACME messages

This commit is contained in:
Jakub Warmuz
2015-02-06 16:38:35 +00:00
parent fe98a4ca48
commit 753b9ca15c
7 changed files with 281 additions and 292 deletions
+89 -185
View File
@@ -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"])
+161 -88
View File
@@ -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<?\xc8W\x94\x94cj(\xe7\xaa$'
'\x92\xe9\x96\x11\xc2\xefx\x0bR',
nonce='\xab?\x08o\xe6\x81$\x9f\xa1\xc9\x025\x1c\x1b\xa5+')
jmsg = msg._fields_to_json() # pylint: disable=protected-access
jmsg.pop('signature')
self.assertEqual(jmsg, {
from letsencrypt.acme.messages import AuthorizationRequest
self.msg = AuthorizationRequest(
session_id='aefoGaavieG9Wihuk2aufai3aeZ5EeW4',
nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9',
responses=self.responses,
signature=signature,
contact=[],
)
self.jmsg = {
'type': 'authorizationRequest',
'sessionID': 'aefoGaavieG9Wihuk2aufai3aeZ5EeW4',
'nonce': 'Y3pwc3JGMEtNSDZkZ2FqaWczVEdIdw',
'responses': responses,
})
'nonce': '7Nbyb1lI6xPVI3Hg3aKSqQ',
'responses': self.responses,
'signature': signature,
}
def test_create(self):
from letsencrypt.acme.messages import AuthorizationRequest
self.assertEqual(self.msg, AuthorizationRequest.create(
name='example.com', key=KEY, responses=self.responses,
nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9',
session_id='aefoGaavieG9Wihuk2aufai3aeZ5EeW4',
sig_nonce='\xab?\x08o\xe6\x81$\x9f\xa1\xc9\x025\x1c\x1b\xa5+'))
def test_verify(self):
self.assertTrue(self.msg.verify('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 AuthorizationRequest
self.jmsg['signature'] = self.jmsg['signature'].to_json()
self.jmsg['signature']['jwk'] = self.jmsg['signature']['jwk'].to_json()
self.assertEqual(self.msg, AuthorizationRequest.from_json(self.jmsg))
class CertificateRequestTest(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?'
signature = other.Signature(
alg='RS256', jwk=jose.JWK(key=KEY.publickey()),
sig='\x1cD\x157\x83\x14\xd7 \xeb\x02\xb3\xf6O\xb5\x99C]\x97'
'\x94p\xa7\xe48\x13>\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__':
+6 -5
View File
@@ -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
+4 -2
View File
@@ -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):
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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(
+18 -9
View File
@@ -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