Merge pull request #528 from kuba/acme-spec-118-revoke

acme: up to date revoke
This commit is contained in:
James Kasten 2015-06-22 16:56:06 -04:00
commit cd7c6d8220
5 changed files with 37 additions and 49 deletions

View file

@ -539,21 +539,17 @@ class Client(object): # pylint: disable=too-many-instance-attributes
else:
return None
def revoke(self, certr, when=messages.Revocation.NOW):
def revoke(self, cert):
"""Revoke certificate.
:param certr: Certificate Resource
:type certr: `.CertificateResource`
:param when: When should the revocation take place? Takes
the same values as `.messages.Revocation.revoke`.
:param .ComparableX509 cert: `M2Crypto.X509.X509` wrapped in
`.ComparableX509`
:raises .ClientError: If revocation is unsuccessful.
"""
rev = messages.Revocation(revoke=when, authorizations=tuple(
authzr.uri for authzr in certr.authzrs))
response = self._post(certr.uri, rev)
response = self._post(messages.Revocation.url(self.new_reg_uri),
messages.Revocation(certificate=cert))
if response.status_code != httplib.OK:
raise errors.ClientError(
'Successful revocation must return HTTP OK status')

View file

@ -517,8 +517,9 @@ class ClientTest(unittest.TestCase):
def test_revoke(self):
self._mock_post_get()
self.net.revoke(self.certr, when=messages.Revocation.NOW)
self.post.assert_called_once_with(self.certr.uri, mock.ANY)
self.net.revoke(self.certr.body)
self.post.assert_called_once_with(messages.Revocation.url(
self.net.new_reg_uri), mock.ANY)
def test_revoke_bad_status_raises_error(self):
self.response.status_code = httplib.METHOD_NOT_ALLOWED

View file

@ -1,4 +1,6 @@
"""ACME protocol messages."""
import urlparse
from acme import challenges
from acme import fields
from acme import jose
@ -271,28 +273,22 @@ class CertificateResource(Resource):
class Revocation(jose.JSONObjectWithFields):
"""Revocation message.
:ivar revoke: Either a `datetime.datetime` or `Revocation.NOW`.
:ivar tuple authorizations: Same as `CertificateRequest.authorizations`
:ivar .ComparableX509 certificate: `M2Crypto.X509.X509` wrapped in
`.ComparableX509`
"""
certificate = jose.Field(
'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
NOW = 'now'
"""A possible value for `revoke`, denoting that certificate should
be revoked now."""
# TODO: acme-spec#138, this allows only one ACME server instance per domain
PATH = '/acme/revoke-cert'
"""Path to revocation URL, see `url`"""
revoke = jose.Field('revoke')
authorizations = CertificateRequest._fields['authorizations']
@classmethod
def url(cls, base):
"""Get revocation URL.
@revoke.decoder
def revoke(value): # pylint: disable=missing-docstring,no-self-argument
if value == Revocation.NOW:
return value
else:
return fields.RFC3339Field.default_decoder(value)
:param str base: New Registration Resource or server (root) URL.
@revoke.encoder
def revoke(value): # pylint: disable=missing-docstring,no-self-argument
if value == Revocation.NOW:
return value
else:
return fields.RFC3339Field.default_encoder(value)
"""
return urlparse.urljoin(base, cls.PATH)

View file

@ -1,11 +1,10 @@
"""Tests for acme.messages."""
import datetime
import os
import pkg_resources
import unittest
import M2Crypto.X509
import mock
import pytz
from Crypto.PublicKey import RSA
from acme import challenges
@ -14,6 +13,9 @@ from acme import jose
KEY = jose.util.HashableRSAKey(RSA.importKey(pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'rsa512_key.pem'))))
CERT = jose.ComparableX509(M2Crypto.X509.load_cert(
format=M2Crypto.X509.FORMAT_DER, file=pkg_resources.resource_filename(
'acme.jose', os.path.join('testdata', 'cert.der'))))
class ErrorTest(unittest.TestCase):
@ -223,27 +225,20 @@ class AuthorizationTest(unittest.TestCase):
class RevocationTest(unittest.TestCase):
"""Tests for acme.messages.RevocationTest."""
def test_url(self):
from acme.messages import Revocation
url = 'https://letsencrypt-demo.org/acme/revoke-cert'
self.assertEqual(url, Revocation.url('https://letsencrypt-demo.org'))
self.assertEqual(
url, Revocation.url('https://letsencrypt-demo.org/acme/new-reg'))
def setUp(self):
from acme.messages import Revocation
self.rev_now = Revocation(authorizations=(), revoke=Revocation.NOW)
self.rev_date = Revocation(authorizations=(), revoke=datetime.datetime(
2015, 3, 27, tzinfo=pytz.utc))
self.jobj_now = {'authorizations': (), 'revoke': Revocation.NOW}
self.jobj_date = {'authorizations': (),
'revoke': '2015-03-27T00:00:00Z'}
def test_revoke_decoder(self):
from acme.messages import Revocation
self.assertEqual(self.rev_now, Revocation.from_json(self.jobj_now))
self.assertEqual(self.rev_date, Revocation.from_json(self.jobj_date))
def test_revoke_encoder(self):
self.assertEqual(self.jobj_now, self.rev_now.to_partial_json())
self.assertEqual(self.jobj_date, self.rev_date.to_partial_json())
self.rev = Revocation(certificate=CERT)
def test_from_json_hashable(self):
from acme.messages import Revocation
hash(Revocation.from_json(self.rev_now.to_json()))
hash(Revocation.from_json(self.rev.to_json()))
if __name__ == '__main__':

View file

@ -253,7 +253,7 @@ class Revoker(object):
raise errors.LetsEncryptRevokerError(
"Corrupted backup key file: %s" % cert.backup_key_path)
return self.network.revoke(certr=None) # XXX
return self.network.revoke(cert=None) # XXX
def _remove_certs_keys(self, cert_list): # pylint: disable=no-self-use
"""Remove certificate and key.