mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 08:08:00 +02:00
Merge pull request #594 from kuba/acme-resource
Enforce "resource" field in request objects.
This commit is contained in:
+10
-2
@@ -7,6 +7,7 @@ import os
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from acme import interfaces
|
||||||
from acme import jose
|
from acme import jose
|
||||||
from acme import other
|
from acme import other
|
||||||
|
|
||||||
@@ -31,10 +32,17 @@ class DVChallenge(Challenge): # pylint: disable=abstract-method
|
|||||||
"""Domain validation challenges."""
|
"""Domain validation challenges."""
|
||||||
|
|
||||||
|
|
||||||
class ChallengeResponse(jose.TypedJSONObjectWithFields):
|
class ChallengeResponse(interfaces.ClientRequestableResource,
|
||||||
|
jose.TypedJSONObjectWithFields):
|
||||||
# _fields_to_partial_json | pylint: disable=abstract-method
|
# _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 = {}
|
TYPES = {}
|
||||||
|
resource_type = 'challenge'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, jobj):
|
def from_json(cls, jobj):
|
||||||
|
|||||||
+7
-2
@@ -2,6 +2,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import heapq
|
import heapq
|
||||||
import httplib
|
import httplib
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
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)
|
response = self.net.post(self.new_reg_uri, new_reg)
|
||||||
assert response.status_code == httplib.CREATED # TODO: handle errors
|
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)
|
regr = self._regr_from_response(response)
|
||||||
if (regr.body.key != self.key.public_key() or
|
if (regr.body.key != self.key.public_key() or
|
||||||
regr.body.contact != new_reg.contact):
|
regr.body.contact != new_reg.contact):
|
||||||
@@ -443,11 +446,13 @@ class ClientNetwork(object):
|
|||||||
|
|
||||||
.. todo:: Implement ``acmePath``.
|
.. todo:: Implement ``acmePath``.
|
||||||
|
|
||||||
:param JSONDeSerializable obj:
|
:param .ClientRequestableResource obj:
|
||||||
:rtype: `.JWS`
|
: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)
|
logger.debug('Serialized JSON: %s', dumps)
|
||||||
return jws.JWS.sign(
|
return jws.JWS.sign(
|
||||||
payload=dumps, key=self.key, alg=self.alg, nonce=nonce).json_dumps()
|
payload=dumps, key=self.key, alg=self.alg, nonce=nonce).json_dumps()
|
||||||
|
|||||||
+11
-4
@@ -1,6 +1,7 @@
|
|||||||
"""Tests for acme.client."""
|
"""Tests for acme.client."""
|
||||||
import datetime
|
import datetime
|
||||||
import httplib
|
import httplib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import unittest
|
import unittest
|
||||||
@@ -74,6 +75,8 @@ class ClientTest(unittest.TestCase):
|
|||||||
cert_chain_uri='https://www.letsencrypt-demo.org/ca')
|
cert_chain_uri='https://www.letsencrypt-demo.org/ca')
|
||||||
|
|
||||||
def test_register(self):
|
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.status_code = httplib.CREATED
|
||||||
self.response.json.return_value = self.regr.body.to_json()
|
self.response.json.return_value = self.regr.body.to_json()
|
||||||
self.response.headers['Location'] = self.regr.uri
|
self.response.headers['Location'] = self.regr.uri
|
||||||
@@ -97,6 +100,8 @@ class ClientTest(unittest.TestCase):
|
|||||||
errors.ClientError, self.client.register, self.regr.body)
|
errors.ClientError, self.client.register, self.regr.body)
|
||||||
|
|
||||||
def test_update_registration(self):
|
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.headers['Location'] = self.regr.uri
|
||||||
self.response.json.return_value = self.regr.body.to_json()
|
self.response.json.return_value = self.regr.body.to_json()
|
||||||
self.assertEqual(self.regr, self.client.update_registration(self.regr))
|
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)
|
self.assertTrue(self.net.verify_ssl is self.verify_ssl)
|
||||||
|
|
||||||
def test_wrap_in_jws(self):
|
def test_wrap_in_jws(self):
|
||||||
class MockJSONDeSerializable(jose.JSONDeSerializable):
|
class MockClientRequestableResource(jose.JSONDeSerializable):
|
||||||
# pylint: disable=missing-docstring
|
# pylint: disable=missing-docstring
|
||||||
|
resource_type = 'mock'
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
self.value = value
|
self.value = value
|
||||||
def to_partial_json(self):
|
def to_partial_json(self):
|
||||||
return self.value
|
return {'foo': self.value}
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, value):
|
def from_json(cls, value):
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
jws_dump = self.net._wrap_in_jws(
|
jws_dump = self.net._wrap_in_jws(
|
||||||
MockJSONDeSerializable('foo'), nonce='Tg')
|
MockClientRequestableResource('foo'), nonce='Tg')
|
||||||
jws = acme_jws.JWS.json_loads(jws_dump)
|
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')
|
self.assertEqual(jws.signature.combined.nonce, 'Tg')
|
||||||
# TODO: check that nonce is in protected header
|
# TODO: check that nonce is in protected header
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
+18
-7
@@ -3,6 +3,7 @@ import urlparse
|
|||||||
|
|
||||||
from acme import challenges
|
from acme import challenges
|
||||||
from acme import fields
|
from acme import fields
|
||||||
|
from acme import interfaces
|
||||||
from acme import jose
|
from acme import jose
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +118,6 @@ class Identifier(jose.JSONObjectWithFields):
|
|||||||
class Resource(jose.JSONObjectWithFields):
|
class Resource(jose.JSONObjectWithFields):
|
||||||
"""ACME Resource.
|
"""ACME Resource.
|
||||||
|
|
||||||
:ivar str uri: Location of the resource.
|
|
||||||
:ivar acme.messages.ResourceBody body: Resource body.
|
:ivar acme.messages.ResourceBody body: Resource body.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -137,13 +137,15 @@ class ResourceBody(jose.JSONObjectWithFields):
|
|||||||
"""ACME Resource Body."""
|
"""ACME Resource Body."""
|
||||||
|
|
||||||
|
|
||||||
class Registration(ResourceBody):
|
class Registration(interfaces.ClientRequestableResource, ResourceBody):
|
||||||
"""Registration Resource Body.
|
"""Registration Resource Body.
|
||||||
|
|
||||||
:ivar acme.jose.jwk.JWK key: Public key.
|
:ivar acme.jose.jwk.JWK key: Public key.
|
||||||
:ivar tuple contact: Contact information following ACME spec
|
:ivar tuple contact: Contact information following ACME spec
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
resource_type = 'new-regr'
|
||||||
|
|
||||||
# on new-reg key server ignores 'key' and populates it based on
|
# on new-reg key server ignores 'key' and populates it based on
|
||||||
# JWS.signature.combined.jwk
|
# JWS.signature.combined.jwk
|
||||||
key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)
|
key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)
|
||||||
@@ -205,7 +207,8 @@ class Registration(ResourceBody):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class RegistrationResource(ResourceWithURI):
|
class RegistrationResource(interfaces.ClientRequestableResource,
|
||||||
|
ResourceWithURI):
|
||||||
"""Registration Resource.
|
"""Registration Resource.
|
||||||
|
|
||||||
:ivar acme.messages.Registration body:
|
:ivar acme.messages.Registration body:
|
||||||
@@ -213,6 +216,7 @@ class RegistrationResource(ResourceWithURI):
|
|||||||
:ivar str terms_of_service: URL for the CA TOS.
|
:ivar str terms_of_service: URL for the CA TOS.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
resource_type = 'reg'
|
||||||
body = jose.Field('body', decoder=Registration.from_json)
|
body = jose.Field('body', decoder=Registration.from_json)
|
||||||
new_authzr_uri = jose.Field('new_authzr_uri')
|
new_authzr_uri = jose.Field('new_authzr_uri')
|
||||||
terms_of_service = jose.Field('terms_of_service', omitempty=True)
|
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
|
return self.body.uri # pylint: disable=no-member
|
||||||
|
|
||||||
|
|
||||||
class Authorization(ResourceBody):
|
class Authorization(interfaces.ClientRequestableResource, ResourceBody):
|
||||||
"""Authorization Resource Body.
|
"""Authorization Resource Body.
|
||||||
|
|
||||||
:ivar acme.messages.Identifier identifier:
|
:ivar acme.messages.Identifier identifier:
|
||||||
@@ -287,6 +291,7 @@ class Authorization(ResourceBody):
|
|||||||
:ivar datetime.datetime expires:
|
:ivar datetime.datetime expires:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
resource_type = 'new-authz'
|
||||||
identifier = jose.Field('identifier', decoder=Identifier.from_json)
|
identifier = jose.Field('identifier', decoder=Identifier.from_json)
|
||||||
challenges = jose.Field('challenges', omitempty=True)
|
challenges = jose.Field('challenges', omitempty=True)
|
||||||
combinations = jose.Field('combinations', omitempty=True)
|
combinations = jose.Field('combinations', omitempty=True)
|
||||||
@@ -320,7 +325,8 @@ class AuthorizationResource(ResourceWithURI):
|
|||||||
new_cert_uri = jose.Field('new_cert_uri')
|
new_cert_uri = jose.Field('new_cert_uri')
|
||||||
|
|
||||||
|
|
||||||
class CertificateRequest(jose.JSONObjectWithFields):
|
class CertificateRequest(interfaces.ClientRequestableResource,
|
||||||
|
jose.JSONObjectWithFields):
|
||||||
"""ACME new-cert request.
|
"""ACME new-cert request.
|
||||||
|
|
||||||
:ivar acme.jose.util.ComparableX509 csr:
|
:ivar acme.jose.util.ComparableX509 csr:
|
||||||
@@ -328,11 +334,13 @@ class CertificateRequest(jose.JSONObjectWithFields):
|
|||||||
:ivar tuple authorizations: `tuple` of URIs (`str`)
|
:ivar tuple authorizations: `tuple` of URIs (`str`)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
resource_type = 'new-cert'
|
||||||
csr = jose.Field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
|
csr = jose.Field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
|
||||||
authorizations = jose.Field('authorizations', decoder=tuple)
|
authorizations = jose.Field('authorizations', decoder=tuple)
|
||||||
|
|
||||||
|
|
||||||
class CertificateResource(ResourceWithURI):
|
class CertificateResource(interfaces.ClientRequestableResource,
|
||||||
|
ResourceWithURI):
|
||||||
"""Certificate Resource.
|
"""Certificate Resource.
|
||||||
|
|
||||||
:ivar acme.jose.util.ComparableX509 body:
|
:ivar acme.jose.util.ComparableX509 body:
|
||||||
@@ -341,17 +349,20 @@ class CertificateResource(ResourceWithURI):
|
|||||||
:ivar tuple authzrs: `tuple` of `AuthorizationResource`.
|
:ivar tuple authzrs: `tuple` of `AuthorizationResource`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
resource_type = 'cert'
|
||||||
cert_chain_uri = jose.Field('cert_chain_uri')
|
cert_chain_uri = jose.Field('cert_chain_uri')
|
||||||
authzrs = jose.Field('authzrs')
|
authzrs = jose.Field('authzrs')
|
||||||
|
|
||||||
|
|
||||||
class Revocation(jose.JSONObjectWithFields):
|
class Revocation(interfaces.ClientRequestableResource,
|
||||||
|
jose.JSONObjectWithFields):
|
||||||
"""Revocation message.
|
"""Revocation message.
|
||||||
|
|
||||||
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
|
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
|
||||||
`.ComparableX509`
|
`.ComparableX509`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
resource_type = 'revoke-cert'
|
||||||
certificate = jose.Field(
|
certificate = jose.Field(
|
||||||
'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
|
'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user