diff --git a/letsencrypt/acme/fields.py b/letsencrypt/acme/fields.py new file mode 100644 index 000000000..020f02bd3 --- /dev/null +++ b/letsencrypt/acme/fields.py @@ -0,0 +1,19 @@ +"""ACME JSON fields.""" +import pyrfc3339 + +from letsencrypt.acme import jose + + +class RFC3339Field(jose.Field): + """RFC3339 field encoder/decoder""" + + @classmethod + def default_encoder(self, value): + return pyrfc3339.generate(value) + + @classmethod + def default_decoder(cls, value): + try: + return pyrfc3339.parse(value) + except ValueError as error: + raise jose.DeserializationError(error) diff --git a/letsencrypt/acme/messages2.py b/letsencrypt/acme/messages2.py index 4b6ca98e5..8fe72e8fa 100644 --- a/letsencrypt/acme/messages2.py +++ b/letsencrypt/acme/messages2.py @@ -3,6 +3,7 @@ import jsonschema from letsencrypt.acme import challenges from letsencrypt.acme import errors +from letsencrypt.acme import fields from letsencrypt.acme import jose from letsencrypt.acme import other from letsencrypt.acme import util @@ -157,8 +158,7 @@ class Challenge(ResourceBody): __slots__ = ('chall',) uri = jose.Field('uri') status = jose.Field('status', decoder=Status.from_json) - # TODO: de/encode datetime - validated = jose.Field('validated', omitempty=True) + validated = fields.RFC3339Field('validated', omitempty=True) def to_json(self): jobj = super(Challenge, self).to_json() @@ -202,7 +202,7 @@ class Authorization(ResourceBody): # general, but for Key Authorization '[t]he "expires" field MUST # be absent'... then acme-spec gives example with 'expires' # present... That's confusing! - expires = jose.Field('expires', omitempty=True) # TODO: this is date + expires = fields.RFC3339Field('expires', omitempty=True) @challenges.decoder def challenges(value): # pylint: disable=missing-docstring,no-self-argument @@ -241,8 +241,21 @@ class CertificateResource(Resource): class Revocation(jose.JSONObjectWithFields): """Revocation message.""" - class When(object): # TODO: 'now' or datetime - pass + NOW = 'now' - revoke = jose.Field('revoke') # TODO: use When + revoke = jose.Field('revoke') authorizations = CertificateRequest._fields['authorizations'] + + @revoke.decoder + def revoke(value): + if jobj == NOW: + return jobj + else: + return RFC3339Field.default_decoder(value) + + @revoke.encoder + def revoke(value): + if jobj == NOW: + return value + else: + return RFC3339Field.default_encoder(value) diff --git a/letsencrypt/client/network2.py b/letsencrypt/client/network2.py index 610088972..e8beb7ee4 100644 --- a/letsencrypt/client/network2.py +++ b/letsencrypt/client/network2.py @@ -355,7 +355,7 @@ class Network(object): """ return self._get_cert(certr.cert_chain_uri) - def revoke(self, certr, when='now'): + def revoke(self, certr, when=messages2.Revocation.NOW): """Revoke certificate. :param when: When should the revocation take place. diff --git a/setup.py b/setup.py index 91e17b337..b25b7fdb4 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ install_requires = [ 'pyasn1', # urllib3 InsecurePlatformWarning (#304) 'pycrypto', 'PyOpenSSL', + 'pyrfc3339', 'python-augeas', 'python2-pythondialog', 'requests',