diff --git a/.travis.yml b/.travis.yml index 9169a32d7..167d6ad74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ after_success: '[ "$TOXENV" == "cover" ] && coveralls' # matrix, which allows us to clearly distinguish which component under # test has failed env: + - TOXENV=py26 - TOXENV=py27 - TOXENV=lint - TOXENV=cover diff --git a/Dockerfile b/Dockerfile index b6a07388c..78aa7a75b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,6 +48,7 @@ COPY letsencrypt_apache /opt/letsencrypt/src/letsencrypt_apache/ COPY letsencrypt_nginx /opt/letsencrypt/src/letsencrypt_nginx/ +# requirements.txt not installed! RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \ /opt/letsencrypt/venv/bin/pip install -e /opt/letsencrypt/src diff --git a/acme/challenges.py b/acme/challenges.py index 26f71a2e3..9ea06645d 100644 --- a/acme/challenges.py +++ b/acme/challenges.py @@ -46,7 +46,6 @@ class SimpleHTTP(DVChallenge): """ACME "simpleHttp" challenge.""" typ = "simpleHttp" token = jose.Field("token") - tls = jose.Field("tls", default=True, omitempty=True) @ChallengeResponse.register @@ -54,20 +53,43 @@ class SimpleHTTPResponse(ChallengeResponse): """ACME "simpleHttp" challenge response.""" typ = "simpleHttp" path = jose.Field("path") + tls = jose.Field("tls", default=True, omitempty=True) - URI_TEMPLATE = "https://{domain}/.well-known/acme-challenge/{path}" - """URI template for HTTPS server provisioned resource.""" + 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.""" + + @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 + + """ + return len(self.path) <= 25 + + @property + def scheme(self): + """URL scheme for the provisioned resource.""" + return "https" if self.tls else "http" def uri(self, domain): """Create an URI to the provisioned resource. - Forms an URI to the HTTPS server provisioned resource (containing - :attr:`~SimpleHTTP.token`) by populating the :attr:`URI_TEMPLATE`. + Forms an URI to the HTTPS server provisioned resource + (containing :attr:`~SimpleHTTP.token`). :param str domain: Domain name being verified. """ - return self.URI_TEMPLATE.format(domain=domain, path=self.path) + return self._URI_TEMPLATE.format( + scheme=self.scheme, domain=domain, path=self.path) @Challenge.register diff --git a/acme/challenges_test.py b/acme/challenges_test.py index beeec6f73..4c61c0e3d 100644 --- a/acme/challenges_test.py +++ b/acme/challenges_test.py @@ -27,17 +27,8 @@ class SimpleHTTPTest(unittest.TestCase): self.jmsg = { 'type': 'simpleHttp', 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA', - 'tls': True, } - def test_no_tls(self): - from acme.challenges import SimpleHTTP - self.assertEqual(SimpleHTTP(token='tok', tls=False).to_json(), { - 'tls': False, - 'token': 'tok', - 'type': 'simpleHttp', - }) - def test_to_partial_json(self): self.assertEqual(self.jmsg, self.msg.to_partial_json()) @@ -54,27 +45,51 @@ class SimpleHTTPResponseTest(unittest.TestCase): def setUp(self): from acme.challenges import SimpleHTTPResponse - self.msg = SimpleHTTPResponse(path='6tbIMBC5Anhl5bOlWT5ZFA') - self.jmsg = { + self.msg_http = SimpleHTTPResponse( + path='6tbIMBC5Anhl5bOlWT5ZFA', tls=False) + self.msg_https = SimpleHTTPResponse(path='6tbIMBC5Anhl5bOlWT5ZFA') + self.jmsg_http = { 'type': 'simpleHttp', 'path': '6tbIMBC5Anhl5bOlWT5ZFA', + 'tls': False, + } + self.jmsg_https = { + 'type': 'simpleHttp', + 'path': '6tbIMBC5Anhl5bOlWT5ZFA', + 'tls': True, } + def test_good_path(self): + self.assertTrue(self.msg_http.good_path) + self.assertTrue(self.msg_https.good_path) + self.assertFalse( + self.msg_http.update(path=(self.msg_http.path * 10)).good_path) + + def test_scheme(self): + self.assertEqual('http', self.msg_http.scheme) + self.assertEqual('https', self.msg_https.scheme) + def test_uri(self): + self.assertEqual('http://example.com/.well-known/acme-challenge/' + '6tbIMBC5Anhl5bOlWT5ZFA', self.msg_http.uri('example.com')) self.assertEqual('https://example.com/.well-known/acme-challenge/' - '6tbIMBC5Anhl5bOlWT5ZFA', self.msg.uri('example.com')) + '6tbIMBC5Anhl5bOlWT5ZFA', self.msg_https.uri('example.com')) def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) + self.assertEqual(self.jmsg_http, self.msg_http.to_partial_json()) + self.assertEqual(self.jmsg_https, self.msg_https.to_partial_json()) def test_from_json(self): from acme.challenges import SimpleHTTPResponse self.assertEqual( - self.msg, SimpleHTTPResponse.from_json(self.jmsg)) + self.msg_http, SimpleHTTPResponse.from_json(self.jmsg_http)) + self.assertEqual( + self.msg_https, SimpleHTTPResponse.from_json(self.jmsg_https)) def test_from_json_hashable(self): from acme.challenges import SimpleHTTPResponse - hash(SimpleHTTPResponse.from_json(self.jmsg)) + hash(SimpleHTTPResponse.from_json(self.jmsg_http)) + hash(SimpleHTTPResponse.from_json(self.jmsg_https)) class DVSNITest(unittest.TestCase): diff --git a/letsencrypt/tests/acme_util.py b/letsencrypt/tests/acme_util.py index 51bb3cfbb..daf651059 100644 --- a/letsencrypt/tests/acme_util.py +++ b/letsencrypt/tests/acme_util.py @@ -16,7 +16,7 @@ KEY = jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( "acme.jose", os.path.join("testdata", "rsa512_key.pem")))) # Challenges -SIMPLE_HTTPS = challenges.SimpleHTTP( +SIMPLE_HTTP = challenges.SimpleHTTP( token="evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA") DVSNI = challenges.DVSNI( r="O*\xb4-\xad\xec\x95>\xed\xa9\r0\x94\xe8\x97\x9c&6\xbf'\xb3" @@ -47,7 +47,7 @@ POP = challenges.ProofOfPossession( ) ) -CHALLENGES = [SIMPLE_HTTPS, DVSNI, DNS, RECOVERY_CONTACT, RECOVERY_TOKEN, POP] +CHALLENGES = [SIMPLE_HTTP, DVSNI, DNS, RECOVERY_CONTACT, RECOVERY_TOKEN, POP] DV_CHALLENGES = [chall for chall in CHALLENGES if isinstance(chall, challenges.DVChallenge)] CONT_CHALLENGES = [chall for chall in CHALLENGES @@ -86,13 +86,13 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name # Pending ChallengeBody objects DVSNI_P = chall_to_challb(DVSNI, messages2.STATUS_PENDING) -SIMPLE_HTTPS_P = chall_to_challb(SIMPLE_HTTPS, messages2.STATUS_PENDING) +SIMPLE_HTTP_P = chall_to_challb(SIMPLE_HTTP, messages2.STATUS_PENDING) DNS_P = chall_to_challb(DNS, messages2.STATUS_PENDING) RECOVERY_CONTACT_P = chall_to_challb(RECOVERY_CONTACT, messages2.STATUS_PENDING) RECOVERY_TOKEN_P = chall_to_challb(RECOVERY_TOKEN, messages2.STATUS_PENDING) POP_P = chall_to_challb(POP, messages2.STATUS_PENDING) -CHALLENGES_P = [SIMPLE_HTTPS_P, DVSNI_P, DNS_P, +CHALLENGES_P = [SIMPLE_HTTP_P, DVSNI_P, DNS_P, RECOVERY_CONTACT_P, RECOVERY_TOKEN_P, POP_P] DV_CHALLENGES_P = [challb for challb in CHALLENGES_P if isinstance(challb.chall, challenges.DVChallenge)] diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index d7fd2c093..8cbc0e604 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -300,7 +300,7 @@ class GenChallengePathTest(unittest.TestCase): def test_common_case(self): """Given DVSNI and SimpleHTTP with appropriate combos.""" - challbs = (acme_util.DVSNI_P, acme_util.SIMPLE_HTTPS_P) + challbs = (acme_util.DVSNI_P, acme_util.SIMPLE_HTTP_P) prefs = [challenges.DVSNI] combos = ((0,), (1,)) @@ -315,7 +315,7 @@ class GenChallengePathTest(unittest.TestCase): challbs = (acme_util.RECOVERY_TOKEN_P, acme_util.RECOVERY_CONTACT_P, acme_util.DVSNI_P, - acme_util.SIMPLE_HTTPS_P) + acme_util.SIMPLE_HTTP_P) prefs = [challenges.RecoveryToken, challenges.DVSNI] combos = acme_util.gen_combos(challbs) self.assertEqual(self._call(challbs, prefs, combos), (0, 2)) @@ -328,7 +328,7 @@ class GenChallengePathTest(unittest.TestCase): acme_util.RECOVERY_CONTACT_P, acme_util.POP_P, acme_util.DVSNI_P, - acme_util.SIMPLE_HTTPS_P, + acme_util.SIMPLE_HTTP_P, acme_util.DNS_P) # Typical webserver client that can do everything except DNS # Attempted to make the order realistic @@ -413,7 +413,7 @@ class IsPreferredTest(unittest.TestCase): def test_mutually_exclusvie(self): self.assertFalse( self._call( - acme_util.DVSNI_P, frozenset([acme_util.SIMPLE_HTTPS_P]))) + acme_util.DVSNI_P, frozenset([acme_util.SIMPLE_HTTP_P]))) def test_mutually_exclusive_same_type(self): self.assertTrue( diff --git a/setup.py b/setup.py index ebcc2c9b3..46f53244c 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ install_requires = [ 'argparse', 'ConfigArgParse', 'configobj', - 'jsonschema', + 'jsonschema<2.5.1', # https://github.com/Julian/jsonschema/issues/233 'mock', 'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304) 'parsedatetime', @@ -53,7 +53,6 @@ install_requires = [ # order of items in install_requires DOES matter and M2Crypto has # to go last, see #152 'M2Crypto', - 'functools32' ] dev_extras = [