mirror of
https://github.com/certbot/certbot.git
synced 2026-06-14 19:20:09 -04:00
acme: simplehttp v04
This commit is contained in:
parent
ceed8a71c1
commit
57110f4c18
2 changed files with 36 additions and 43 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue