From 57110f4c18dde3dad287ea9de0552d026b06739a Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 31 Jul 2015 21:30:08 +0000 Subject: [PATCH] acme: simplehttp v04 --- acme/acme/challenges.py | 59 +++++++++++++++++------------------- acme/acme/challenges_test.py | 20 ++++++------ 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index abcbf6d1d..40de57812 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -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 diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index dd9760903..293e62bfe 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -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: