1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-23 07:20:55 +03:00

Enforce "resource" field in request objects.

Corresponds to:
- https://github.com/letsencrypt/boulder/pull/442
- https://github.com/letsencrypt/acme-spec/pull/156
This commit is contained in:
Jakub Warmuz
2015-07-09 13:28:57 +00:00
parent d850be2d73
commit 35c21d4cf4
5 changed files with 59 additions and 15 deletions

View File

@@ -7,6 +7,7 @@ import os
import requests
from acme import interfaces
from acme import jose
from acme import other
@@ -31,10 +32,17 @@ class DVChallenge(Challenge): # pylint: disable=abstract-method
"""Domain validation challenges."""
class ChallengeResponse(jose.TypedJSONObjectWithFields):
class ChallengeResponse(interfaces.ClientRequestableResource,
jose.TypedJSONObjectWithFields):
# _fields_to_partial_json | pylint: disable=abstract-method
"""ACME challenge response."""
"""ACME challenge response.
:ivar str mitm_resource: ACME resource identifier used in client
HTTPS requests in order to protect against MITM.
"""
TYPES = {}
resource_type = 'challenge'
@classmethod
def from_json(cls, jobj):

View File

@@ -2,6 +2,7 @@
import datetime
import heapq
import httplib
import json
import logging
import time
@@ -81,6 +82,8 @@ class Client(object): # pylint: disable=too-many-instance-attributes
response = self.net.post(self.new_reg_uri, new_reg)
assert response.status_code == httplib.CREATED # TODO: handle errors
# "Instance of 'Field' has no key/contact member" bug:
# pylint: disable=no-member
regr = self._regr_from_response(response)
if (regr.body.key != self.key.public_key() or
regr.body.contact != new_reg.contact):
@@ -443,11 +446,13 @@ class ClientNetwork(object):
.. todo:: Implement ``acmePath``.
:param JSONDeSerializable obj:
:param .ClientRequestableResource obj:
:rtype: `.JWS`
"""
dumps = obj.json_dumps()
jobj = obj.to_json()
jobj['resource'] = obj.resource_type
dumps = json.dumps(jobj)
logger.debug('Serialized JSON: %s', dumps)
return jws.JWS.sign(
payload=dumps, key=self.key, alg=self.alg, nonce=nonce).json_dumps()

View File

@@ -1,6 +1,7 @@
"""Tests for acme.client."""
import datetime
import httplib
import json
import os
import pkg_resources
import unittest
@@ -74,6 +75,8 @@ class ClientTest(unittest.TestCase):
cert_chain_uri='https://www.letsencrypt-demo.org/ca')
def test_register(self):
# "Instance of 'Field' has no to_json/update member" bug:
# pylint: disable=no-member
self.response.status_code = httplib.CREATED
self.response.json.return_value = self.regr.body.to_json()
self.response.headers['Location'] = self.regr.uri
@@ -97,6 +100,8 @@ class ClientTest(unittest.TestCase):
errors.ClientError, self.client.register, self.regr.body)
def test_update_registration(self):
# "Instance of 'Field' has no to_json/update member" bug:
# pylint: disable=no-member
self.response.headers['Location'] = self.regr.uri
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr, self.client.update_registration(self.regr))
@@ -367,20 +372,22 @@ class ClientNetworkTest(unittest.TestCase):
self.assertTrue(self.net.verify_ssl is self.verify_ssl)
def test_wrap_in_jws(self):
class MockJSONDeSerializable(jose.JSONDeSerializable):
class MockClientRequestableResource(jose.JSONDeSerializable):
# pylint: disable=missing-docstring
resource_type = 'mock'
def __init__(self, value):
self.value = value
def to_partial_json(self):
return self.value
return {'foo': self.value}
@classmethod
def from_json(cls, value):
pass # pragma: no cover
# pylint: disable=protected-access
jws_dump = self.net._wrap_in_jws(
MockJSONDeSerializable('foo'), nonce='Tg')
MockClientRequestableResource('foo'), nonce='Tg')
jws = acme_jws.JWS.json_loads(jws_dump)
self.assertEqual(jws.payload, '"foo"')
self.assertEqual(json.loads(jws.payload),
{'foo': 'foo', 'resource': 'mock'})
self.assertEqual(jws.signature.combined.nonce, 'Tg')
# TODO: check that nonce is in protected header

13
acme/interfaces.py Normal file
View File

@@ -0,0 +1,13 @@
"""ACME interfaces."""
from acme import jose
class ClientRequestableResource(jose.JSONDeSerializable):
"""Resource that can be requested by client.
:ivar str resource_type: ACME resource identifier used in client
HTTPS requests in order to protect against MITM.
"""
# pylint: disable=abstract-method
resource_type = NotImplemented

View File

@@ -3,6 +3,7 @@ import urlparse
from acme import challenges
from acme import fields
from acme import interfaces
from acme import jose
@@ -117,7 +118,6 @@ class Identifier(jose.JSONObjectWithFields):
class Resource(jose.JSONObjectWithFields):
"""ACME Resource.
:ivar str uri: Location of the resource.
:ivar acme.messages.ResourceBody body: Resource body.
"""
@@ -137,13 +137,15 @@ class ResourceBody(jose.JSONObjectWithFields):
"""ACME Resource Body."""
class Registration(ResourceBody):
class Registration(interfaces.ClientRequestableResource, ResourceBody):
"""Registration Resource Body.
:ivar acme.jose.jwk.JWK key: Public key.
:ivar tuple contact: Contact information following ACME spec
"""
resource_type = 'new-regr'
# on new-reg key server ignores 'key' and populates it based on
# JWS.signature.combined.jwk
key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)
@@ -205,7 +207,8 @@ class Registration(ResourceBody):
return None
class RegistrationResource(ResourceWithURI):
class RegistrationResource(interfaces.ClientRequestableResource,
ResourceWithURI):
"""Registration Resource.
:ivar acme.messages.Registration body:
@@ -213,6 +216,7 @@ class RegistrationResource(ResourceWithURI):
:ivar str terms_of_service: URL for the CA TOS.
"""
resource_type = 'reg'
body = jose.Field('body', decoder=Registration.from_json)
new_authzr_uri = jose.Field('new_authzr_uri')
terms_of_service = jose.Field('terms_of_service', omitempty=True)
@@ -274,7 +278,7 @@ class ChallengeResource(Resource):
return self.body.uri # pylint: disable=no-member
class Authorization(ResourceBody):
class Authorization(interfaces.ClientRequestableResource, ResourceBody):
"""Authorization Resource Body.
:ivar acme.messages.Identifier identifier:
@@ -287,6 +291,7 @@ class Authorization(ResourceBody):
:ivar datetime.datetime expires:
"""
resource_type = 'new-authz'
identifier = jose.Field('identifier', decoder=Identifier.from_json)
challenges = jose.Field('challenges', omitempty=True)
combinations = jose.Field('combinations', omitempty=True)
@@ -320,7 +325,8 @@ class AuthorizationResource(ResourceWithURI):
new_cert_uri = jose.Field('new_cert_uri')
class CertificateRequest(jose.JSONObjectWithFields):
class CertificateRequest(interfaces.ClientRequestableResource,
jose.JSONObjectWithFields):
"""ACME new-cert request.
:ivar acme.jose.util.ComparableX509 csr:
@@ -328,11 +334,13 @@ class CertificateRequest(jose.JSONObjectWithFields):
:ivar tuple authorizations: `tuple` of URIs (`str`)
"""
resource_type = 'new-cert'
csr = jose.Field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
authorizations = jose.Field('authorizations', decoder=tuple)
class CertificateResource(ResourceWithURI):
class CertificateResource(interfaces.ClientRequestableResource,
ResourceWithURI):
"""Certificate Resource.
:ivar acme.jose.util.ComparableX509 body:
@@ -341,17 +349,20 @@ class CertificateResource(ResourceWithURI):
:ivar tuple authzrs: `tuple` of `AuthorizationResource`.
"""
resource_type = 'cert'
cert_chain_uri = jose.Field('cert_chain_uri')
authzrs = jose.Field('authzrs')
class Revocation(jose.JSONObjectWithFields):
class Revocation(interfaces.ClientRequestableResource,
jose.JSONObjectWithFields):
"""Revocation message.
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
`.ComparableX509`
"""
resource_type = 'revoke-cert'
certificate = jose.Field(
'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)