acme: simplehttp v04

This commit is contained in:
Jakub Warmuz 2015-07-31 21:30:08 +00:00
parent ceed8a71c1
commit 57110f4c18
No known key found for this signature in database
GPG key ID: 2A7BAD3A489B52EA
2 changed files with 36 additions and 43 deletions

View file

@ -54,46 +54,42 @@ class SimpleHTTP(DVChallenge):
TOKEN_SIZE = 128 / 8 # Based on the entropy value from the spec
"""Minimum size of the :attr:`token` in bytes."""
# TODO: acme-spec doesn't specify token as base64-encoded value
token = jose.Field(
"token", encoder=jose.encode_b64jose, decoder=functools.partial(
jose.decode_b64jose, size=TOKEN_SIZE, minimum=True))
@property
def good_token(self): # XXX: @token.decoder
"""Is `token` good?
.. todo:: acme-spec wants "It MUST NOT contain any non-ASCII
characters", but it should also warrant that it doesn't
contain ".." or "/"...
"""
# TODO: check that path combined with uri does not go above
# URI_ROOT_PATH!
return '..' not in self.token and '/' not in self.token
@ChallengeResponse.register
class SimpleHTTPResponse(ChallengeResponse):
"""ACME "simpleHttp" challenge response.
:ivar unicode path:
:ivar unicode tls:
:ivar bool tls:
"""
typ = "simpleHttp"
path = jose.Field("path")
tls = jose.Field("tls", default=True, omitempty=True)
URI_ROOT_PATH = ".well-known/acme-challenge"
"""URI root path for the server provisioned resource."""
_URI_TEMPLATE = "{scheme}://{domain}/" + URI_ROOT_PATH + "/{path}"
MAX_PATH_LEN = 25
"""Maximum allowed `path` length."""
_URI_TEMPLATE = "{scheme}://{domain}/" + URI_ROOT_PATH + "/{token}"
CONTENT_TYPE = "application/jose+json"
@property
def good_path(self):
"""Is `path` good?
.. todo:: acme-spec: "The value MUST be comprised entirely of
characters from the URL-safe alphabet for Base64 encoding
[RFC4648]", base64.b64decode ignores those characters
"""
# TODO: check that path combined with uri does not go above
# URI_ROOT_PATH!
return len(self.path) <= 25
@property
def scheme(self):
"""URL scheme for the provisioned resource."""
@ -104,17 +100,18 @@ class SimpleHTTPResponse(ChallengeResponse):
"""Port that the ACME client should be listening for validation."""
return 443 if self.tls else 80
def uri(self, domain):
def uri(self, domain, chall):
"""Create an URI to the provisioned resource.
Forms an URI to the HTTPS server provisioned resource
(containing :attr:`~SimpleHTTP.token`).
:param unicode domain: Domain name being verified.
:param challenges.SimpleHTTP chall:
"""
return self._URI_TEMPLATE.format(
scheme=self.scheme, domain=domain, path=self.path)
scheme=self.scheme, domain=domain, token=chall.encode("token"))
def gen_resource(self, chall):
"""Generate provisioned resource.
@ -123,8 +120,7 @@ class SimpleHTTPResponse(ChallengeResponse):
:rtype: SimpleHTTPProvisionedResource
"""
return SimpleHTTPProvisionedResource(
token=chall.token, path=self.path, tls=self.tls)
return SimpleHTTPProvisionedResource(token=chall.token, tls=self.tls)
def gen_validation(self, chall, account_key, alg=jose.RS256, **kwargs):
"""Generate validation.
@ -167,9 +163,7 @@ class SimpleHTTPResponse(ChallengeResponse):
logger.debug(error)
return False
return (resource.token == chall.token and
resource.path == self.path and
resource.tls == self.tls)
return resource.token == chall.token and resource.tls == self.tls
def simple_verify(self, chall, domain, account_public_key, port=None):
"""Simple verify.
@ -205,15 +199,15 @@ class SimpleHTTPResponse(ChallengeResponse):
"Using non-standard port for SimpleHTTP verification: %s", port)
domain += ":{0}".format(port)
uri = self.uri(domain)
uri = self.uri(domain, chall)
logger.debug("Verifying %s at %s...", chall.typ, uri)
try:
http_response = requests.get(uri, verify=False)
except requests.exceptions.RequestException as error:
logger.error("Unable to reach %s: %s", uri, error)
return False
logger.debug(
"Received %s. Headers: %s", http_response, http_response.headers)
logger.debug("Received %s: %s. Headers: %s", http_response,
http_response.text, http_response.headers)
if self.CONTENT_TYPE != http_response.headers.get(
"Content-Type", self.CONTENT_TYPE):
@ -232,8 +226,9 @@ class SimpleHTTPProvisionedResource(jose.JSONObjectWithFields):
"""SimpleHTTP provisioned resource."""
typ = fields.Fixed("type", SimpleHTTP.typ)
token = SimpleHTTP._fields["token"]
path = SimpleHTTPResponse._fields["path"]
tls = SimpleHTTPResponse._fields["tls"]
# If the "tls" field is not included in the response, then
# validation object MUST have its "tls" field set to "true".
tls = jose.Field("tls", omitempty=False)
@Challenge.register

View file

@ -46,26 +46,23 @@ class SimpleHTTPResponseTest(unittest.TestCase):
def setUp(self):
from acme.challenges import SimpleHTTPResponse
self.msg_http = SimpleHTTPResponse(
path='6tbIMBC5Anhl5bOlWT5ZFA', tls=False)
self.msg_https = SimpleHTTPResponse(path='6tbIMBC5Anhl5bOlWT5ZFA')
self.msg_http = SimpleHTTPResponse(tls=False)
self.msg_https = SimpleHTTPResponse(tls=True)
self.jmsg_http = {
'resource': 'challenge',
'type': 'simpleHttp',
'path': '6tbIMBC5Anhl5bOlWT5ZFA',
'tls': False,
}
self.jmsg_https = {
'resource': 'challenge',
'type': 'simpleHttp',
'path': '6tbIMBC5Anhl5bOlWT5ZFA',
'tls': True,
}
from acme.challenges import SimpleHTTP
self.chall = SimpleHTTP(token=(r"x" * 16))
self.resp_http = SimpleHTTPResponse(path="bar", tls=False)
self.resp_https = SimpleHTTPResponse(path="bar", tls=True)
self.chall = SimpleHTTP(token=(b"x" * 16))
self.resp_http = SimpleHTTPResponse(tls=False)
self.resp_https = SimpleHTTPResponse(tls=True)
self.good_headers = {'Content-Type': SimpleHTTPResponse.CONTENT_TYPE}
def test_to_partial_json(self):
@ -101,10 +98,12 @@ class SimpleHTTPResponseTest(unittest.TestCase):
def test_uri(self):
self.assertEqual(
'http://example.com/.well-known/acme-challenge/'
'6tbIMBC5Anhl5bOlWT5ZFA', self.msg_http.uri('example.com'))
'eHh4eHh4eHh4eHh4eHh4eA', self.msg_http.uri(
'example.com', self.chall))
self.assertEqual(
'https://example.com/.well-known/acme-challenge/'
'6tbIMBC5Anhl5bOlWT5ZFA', self.msg_https.uri('example.com'))
'eHh4eHh4eHh4eHh4eHh4eA', self.msg_https.uri(
'example.com', self.chall))
def test_gen_check_validation(self):
account_key = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
@ -138,7 +137,6 @@ class SimpleHTTPResponseTest(unittest.TestCase):
jose.JWS.sign(payload=bad_resource.json_dumps().encode('utf-8'),
alg=jose.RS256, key=account_key)
for bad_resource in (resource.update(tls=True),
resource.update(path='xxx'),
resource.update(token=r'x'*20))
)
for validation in validations: