From d2b65b47f2e2968df62e0feea5ebf28bfdd3e4b2 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 26 Nov 2019 15:25:41 -0800 Subject: Refactor tests out of packaged module for acme plugin (#7600) * Move acme tests to tests/ directory outside of acme module * Fix call to messages_test in client_test * Move test_util.py and testdata/ into tests/ * Update manifest to package tests * Exclude pycache and .py[cod] --- acme/MANIFEST.in | 4 +- acme/acme/challenges_test.py | 419 ----------- acme/acme/client_test.py | 1308 ---------------------------------- acme/acme/crypto_util_test.py | 267 ------- acme/acme/errors_test.py | 53 -- acme/acme/fields_test.py | 72 -- acme/acme/jose_test.py | 53 -- acme/acme/jws_test.py | 67 -- acme/acme/magic_typing_test.py | 41 -- acme/acme/messages_test.py | 476 ------------- acme/acme/standalone_test.py | 190 ----- acme/acme/test_util.py | 68 -- acme/acme/testdata/README | 15 - acme/acme/testdata/cert-100sans.pem | 44 -- acme/acme/testdata/cert-idnsans.pem | 30 - acme/acme/testdata/cert-nocn.der | Bin 1397 -> 0 bytes acme/acme/testdata/cert-san.pem | 14 - acme/acme/testdata/cert.der | Bin 771 -> 0 bytes acme/acme/testdata/cert.pem | 13 - acme/acme/testdata/critical-san.pem | 28 - acme/acme/testdata/csr-100sans.pem | 41 -- acme/acme/testdata/csr-6sans.pem | 12 - acme/acme/testdata/csr-idnsans.pem | 27 - acme/acme/testdata/csr-nosans.pem | 8 - acme/acme/testdata/csr-san.pem | 10 - acme/acme/testdata/csr.der | Bin 607 -> 0 bytes acme/acme/testdata/csr.pem | 10 - acme/acme/testdata/dsa512_key.pem | 14 - acme/acme/testdata/rsa1024_key.pem | 15 - acme/acme/testdata/rsa2048_cert.pem | 22 - acme/acme/testdata/rsa2048_key.pem | 28 - acme/acme/testdata/rsa256_key.pem | 6 - acme/acme/testdata/rsa512_key.pem | 9 - acme/acme/util_test.py | 16 - acme/tests/challenges_test.py | 419 +++++++++++ acme/tests/client_test.py | 1308 ++++++++++++++++++++++++++++++++++ acme/tests/crypto_util_test.py | 267 +++++++ acme/tests/errors_test.py | 53 ++ acme/tests/fields_test.py | 72 ++ acme/tests/jose_test.py | 53 ++ acme/tests/jws_test.py | 67 ++ acme/tests/magic_typing_test.py | 41 ++ acme/tests/messages_test.py | 476 +++++++++++++ acme/tests/standalone_test.py | 190 +++++ acme/tests/test_util.py | 68 ++ acme/tests/testdata/README | 15 + acme/tests/testdata/cert-100sans.pem | 44 ++ acme/tests/testdata/cert-idnsans.pem | 30 + acme/tests/testdata/cert-nocn.der | Bin 0 -> 1397 bytes acme/tests/testdata/cert-san.pem | 14 + acme/tests/testdata/cert.der | Bin 0 -> 771 bytes acme/tests/testdata/cert.pem | 13 + acme/tests/testdata/critical-san.pem | 28 + acme/tests/testdata/csr-100sans.pem | 41 ++ acme/tests/testdata/csr-6sans.pem | 12 + acme/tests/testdata/csr-idnsans.pem | 27 + acme/tests/testdata/csr-nosans.pem | 8 + acme/tests/testdata/csr-san.pem | 10 + acme/tests/testdata/csr.der | Bin 0 -> 607 bytes acme/tests/testdata/csr.pem | 10 + acme/tests/testdata/dsa512_key.pem | 14 + acme/tests/testdata/rsa1024_key.pem | 15 + acme/tests/testdata/rsa2048_cert.pem | 22 + acme/tests/testdata/rsa2048_key.pem | 28 + acme/tests/testdata/rsa256_key.pem | 6 + acme/tests/testdata/rsa512_key.pem | 9 + acme/tests/util_test.py | 16 + 67 files changed, 3379 insertions(+), 3377 deletions(-) delete mode 100644 acme/acme/challenges_test.py delete mode 100644 acme/acme/client_test.py delete mode 100644 acme/acme/crypto_util_test.py delete mode 100644 acme/acme/errors_test.py delete mode 100644 acme/acme/fields_test.py delete mode 100644 acme/acme/jose_test.py delete mode 100644 acme/acme/jws_test.py delete mode 100644 acme/acme/magic_typing_test.py delete mode 100644 acme/acme/messages_test.py delete mode 100644 acme/acme/standalone_test.py delete mode 100644 acme/acme/test_util.py delete mode 100644 acme/acme/testdata/README delete mode 100644 acme/acme/testdata/cert-100sans.pem delete mode 100644 acme/acme/testdata/cert-idnsans.pem delete mode 100644 acme/acme/testdata/cert-nocn.der delete mode 100644 acme/acme/testdata/cert-san.pem delete mode 100644 acme/acme/testdata/cert.der delete mode 100644 acme/acme/testdata/cert.pem delete mode 100644 acme/acme/testdata/critical-san.pem delete mode 100644 acme/acme/testdata/csr-100sans.pem delete mode 100644 acme/acme/testdata/csr-6sans.pem delete mode 100644 acme/acme/testdata/csr-idnsans.pem delete mode 100644 acme/acme/testdata/csr-nosans.pem delete mode 100644 acme/acme/testdata/csr-san.pem delete mode 100644 acme/acme/testdata/csr.der delete mode 100644 acme/acme/testdata/csr.pem delete mode 100644 acme/acme/testdata/dsa512_key.pem delete mode 100644 acme/acme/testdata/rsa1024_key.pem delete mode 100644 acme/acme/testdata/rsa2048_cert.pem delete mode 100644 acme/acme/testdata/rsa2048_key.pem delete mode 100644 acme/acme/testdata/rsa256_key.pem delete mode 100644 acme/acme/testdata/rsa512_key.pem delete mode 100644 acme/acme/util_test.py create mode 100644 acme/tests/challenges_test.py create mode 100644 acme/tests/client_test.py create mode 100644 acme/tests/crypto_util_test.py create mode 100644 acme/tests/errors_test.py create mode 100644 acme/tests/fields_test.py create mode 100644 acme/tests/jose_test.py create mode 100644 acme/tests/jws_test.py create mode 100644 acme/tests/magic_typing_test.py create mode 100644 acme/tests/messages_test.py create mode 100644 acme/tests/standalone_test.py create mode 100644 acme/tests/test_util.py create mode 100644 acme/tests/testdata/README create mode 100644 acme/tests/testdata/cert-100sans.pem create mode 100644 acme/tests/testdata/cert-idnsans.pem create mode 100644 acme/tests/testdata/cert-nocn.der create mode 100644 acme/tests/testdata/cert-san.pem create mode 100644 acme/tests/testdata/cert.der create mode 100644 acme/tests/testdata/cert.pem create mode 100644 acme/tests/testdata/critical-san.pem create mode 100644 acme/tests/testdata/csr-100sans.pem create mode 100644 acme/tests/testdata/csr-6sans.pem create mode 100644 acme/tests/testdata/csr-idnsans.pem create mode 100644 acme/tests/testdata/csr-nosans.pem create mode 100644 acme/tests/testdata/csr-san.pem create mode 100644 acme/tests/testdata/csr.der create mode 100644 acme/tests/testdata/csr.pem create mode 100644 acme/tests/testdata/dsa512_key.pem create mode 100644 acme/tests/testdata/rsa1024_key.pem create mode 100644 acme/tests/testdata/rsa2048_cert.pem create mode 100644 acme/tests/testdata/rsa2048_key.pem create mode 100644 acme/tests/testdata/rsa256_key.pem create mode 100644 acme/tests/testdata/rsa512_key.pem create mode 100644 acme/tests/util_test.py diff --git a/acme/MANIFEST.in b/acme/MANIFEST.in index 1619bef69..de254250e 100644 --- a/acme/MANIFEST.in +++ b/acme/MANIFEST.in @@ -3,4 +3,6 @@ include README.rst include pytest.ini recursive-include docs * recursive-include examples * -recursive-include acme/testdata * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py deleted file mode 100644 index 4f728e4a4..000000000 --- a/acme/acme/challenges_test.py +++ /dev/null @@ -1,419 +0,0 @@ -"""Tests for acme.challenges.""" -import unittest - -import josepy as jose -import mock -import requests - -from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import - -from acme import test_util - -CERT = test_util.load_comparable_cert('cert.pem') -KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem')) - - -class ChallengeTest(unittest.TestCase): - - def test_from_json_unrecognized(self): - from acme.challenges import Challenge - from acme.challenges import UnrecognizedChallenge - chall = UnrecognizedChallenge({"type": "foo"}) - # pylint: disable=no-member - self.assertEqual(chall, Challenge.from_json(chall.jobj)) - - -class UnrecognizedChallengeTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import UnrecognizedChallenge - self.jobj = {"type": "foo"} - self.chall = UnrecognizedChallenge(self.jobj) - - def test_to_partial_json(self): - self.assertEqual(self.jobj, self.chall.to_partial_json()) - - def test_from_json(self): - from acme.challenges import UnrecognizedChallenge - self.assertEqual( - self.chall, UnrecognizedChallenge.from_json(self.jobj)) - - -class KeyAuthorizationChallengeResponseTest(unittest.TestCase): - - def setUp(self): - def _encode(name): - assert name == "token" - return "foo" - self.chall = mock.Mock() - self.chall.encode.side_effect = _encode - - def test_verify_ok(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') - self.assertTrue(response.verify(self.chall, KEY.public_key())) - - def test_verify_wrong_token(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='bar.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') - self.assertFalse(response.verify(self.chall, KEY.public_key())) - - def test_verify_wrong_thumbprint(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxv') - self.assertFalse(response.verify(self.chall, KEY.public_key())) - - def test_verify_wrong_form(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='.foo.oKGqedy-b-acd5eoybm2f-' - 'NVFxvyOoET5CNy3xnv8WY') - self.assertFalse(response.verify(self.chall, KEY.public_key())) - - -class DNS01ResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS01Response - self.msg = DNS01Response(key_authorization=u'foo') - self.jmsg = { - 'resource': 'challenge', - 'type': 'dns-01', - 'keyAuthorization': u'foo', - } - - from acme.challenges import DNS01 - self.chall = DNS01(token=(b'x' * 16)) - self.response = self.chall.response(KEY) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNS01Response - self.assertEqual(self.msg, DNS01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import DNS01Response - hash(DNS01Response.from_json(self.jmsg)) - - def test_simple_verify_failure(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - public_key = key2.public_key() - verified = self.response.simple_verify(self.chall, "local", public_key) - self.assertFalse(verified) - - def test_simple_verify_success(self): - public_key = KEY.public_key() - verified = self.response.simple_verify(self.chall, "local", public_key) - self.assertTrue(verified) - - -class DNS01Test(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS01 - self.msg = DNS01(token=jose.decode_b64jose( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) - self.jmsg = { - 'type': 'dns-01', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - } - - def test_validation_domain_name(self): - self.assertEqual('_acme-challenge.www.example.com', - self.msg.validation_domain_name('www.example.com')) - - def test_validation(self): - self.assertEqual( - "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk", - self.msg.validation(KEY)) - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNS01 - self.assertEqual(self.msg, DNS01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import DNS01 - hash(DNS01.from_json(self.jmsg)) - - -class HTTP01ResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import HTTP01Response - self.msg = HTTP01Response(key_authorization=u'foo') - self.jmsg = { - 'resource': 'challenge', - 'type': 'http-01', - 'keyAuthorization': u'foo', - } - - from acme.challenges import HTTP01 - self.chall = HTTP01(token=(b'x' * 16)) - self.response = self.chall.response(KEY) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import HTTP01Response - self.assertEqual( - self.msg, HTTP01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import HTTP01Response - hash(HTTP01Response.from_json(self.jmsg)) - - def test_simple_verify_bad_key_authorization(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - self.response.simple_verify(self.chall, "local", key2.public_key()) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_good_validation(self, mock_get): - validation = self.chall.validation(KEY) - mock_get.return_value = mock.MagicMock(text=validation) - self.assertTrue(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - mock_get.assert_called_once_with(self.chall.uri("local")) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_bad_validation(self, mock_get): - mock_get.return_value = mock.MagicMock(text="!") - self.assertFalse(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_whitespace_validation(self, mock_get): - from acme.challenges import HTTP01Response - mock_get.return_value = mock.MagicMock( - text=(self.chall.validation(KEY) + - HTTP01Response.WHITESPACE_CUTSET)) - self.assertTrue(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - mock_get.assert_called_once_with(self.chall.uri("local")) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_connection_error(self, mock_get): - mock_get.side_effect = requests.exceptions.RequestException - self.assertFalse(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_port(self, mock_get): - self.response.simple_verify( - self.chall, domain="local", - account_public_key=KEY.public_key(), port=8080) - self.assertEqual("local:8080", urllib_parse.urlparse( - mock_get.mock_calls[0][1][0]).netloc) - - -class HTTP01Test(unittest.TestCase): - - def setUp(self): - from acme.challenges import HTTP01 - self.msg = HTTP01( - token=jose.decode_b64jose( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) - self.jmsg = { - 'type': 'http-01', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - } - - def test_path(self): - self.assertEqual(self.msg.path, '/.well-known/acme-challenge/' - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA') - - def test_uri(self): - self.assertEqual( - 'http://example.com/.well-known/acme-challenge/' - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - self.msg.uri('example.com')) - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import HTTP01 - self.assertEqual(self.msg, HTTP01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import HTTP01 - hash(HTTP01.from_json(self.jmsg)) - - def test_good_token(self): - self.assertTrue(self.msg.good_token) - self.assertFalse( - self.msg.update(token=b'..').good_token) - - -class TLSALPN01ResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import TLSALPN01Response - self.msg = TLSALPN01Response(key_authorization=u'foo') - self.jmsg = { - 'resource': 'challenge', - 'type': 'tls-alpn-01', - 'keyAuthorization': u'foo', - } - - from acme.challenges import TLSALPN01 - self.chall = TLSALPN01(token=(b'x' * 16)) - self.response = self.chall.response(KEY) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSALPN01Response - self.assertEqual(self.msg, TLSALPN01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSALPN01Response - hash(TLSALPN01Response.from_json(self.jmsg)) - - -class TLSALPN01Test(unittest.TestCase): - - def setUp(self): - from acme.challenges import TLSALPN01 - self.msg = TLSALPN01( - token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) - self.jmsg = { - 'type': 'tls-alpn-01', - 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', - } - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSALPN01 - self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSALPN01 - hash(TLSALPN01.from_json(self.jmsg)) - - def test_from_json_invalid_token_length(self): - from acme.challenges import TLSALPN01 - self.jmsg['token'] = jose.encode_b64jose(b'abcd') - self.assertRaises( - jose.DeserializationError, TLSALPN01.from_json, self.jmsg) - - def test_validation(self): - self.assertRaises(NotImplementedError, self.msg.validation, KEY) - - -class DNSTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS - self.msg = DNS(token=jose.b64decode( - b'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) - self.jmsg = { - 'type': 'dns', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - } - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNS - self.assertEqual(self.msg, DNS.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import DNS - hash(DNS.from_json(self.jmsg)) - - def test_gen_check_validation(self): - self.assertTrue(self.msg.check_validation( - self.msg.gen_validation(KEY), KEY.public_key())) - - def test_gen_check_validation_wrong_key(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem')) - self.assertFalse(self.msg.check_validation( - self.msg.gen_validation(KEY), key2.public_key())) - - def test_check_validation_wrong_payload(self): - validations = tuple( - jose.JWS.sign(payload=payload, alg=jose.RS256, key=KEY) - for payload in (b'', b'{}') - ) - for validation in validations: - self.assertFalse(self.msg.check_validation( - validation, KEY.public_key())) - - def test_check_validation_wrong_fields(self): - bad_validation = jose.JWS.sign( - payload=self.msg.update( - token=b'x' * 20).json_dumps().encode('utf-8'), - alg=jose.RS256, key=KEY) - self.assertFalse(self.msg.check_validation( - bad_validation, KEY.public_key())) - - def test_gen_response(self): - with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen: - mock_gen.return_value = mock.sentinel.validation - response = self.msg.gen_response(KEY) - from acme.challenges import DNSResponse - self.assertTrue(isinstance(response, DNSResponse)) - self.assertEqual(response.validation, mock.sentinel.validation) - - def test_validation_domain_name(self): - self.assertEqual( - '_acme-challenge.le.wtf', self.msg.validation_domain_name('le.wtf')) - - -class DNSResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS - self.chall = DNS(token=jose.b64decode( - b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA")) - self.validation = jose.JWS.sign( - payload=self.chall.json_dumps(sort_keys=True).encode(), - key=KEY, alg=jose.RS256) - - from acme.challenges import DNSResponse - self.msg = DNSResponse(validation=self.validation) - self.jmsg_to = { - 'resource': 'challenge', - 'type': 'dns', - 'validation': self.validation, - } - self.jmsg_from = { - 'resource': 'challenge', - 'type': 'dns', - 'validation': self.validation.to_json(), - } - - def test_to_partial_json(self): - self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNSResponse - self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg_from)) - - def test_from_json_hashable(self): - from acme.challenges import DNSResponse - hash(DNSResponse.from_json(self.jmsg_from)) - - def test_check_validation(self): - self.assertTrue( - self.msg.check_validation(self.chall, KEY.public_key())) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py deleted file mode 100644 index a53ce799b..000000000 --- a/acme/acme/client_test.py +++ /dev/null @@ -1,1308 +0,0 @@ -"""Tests for acme.client.""" -# pylint: disable=too-many-lines -import copy -import datetime -import json -import unittest - -from six.moves import http_client # pylint: disable=import-error - -import josepy as jose -import mock -import OpenSSL -import requests - -from acme import challenges -from acme import errors -from acme import jws as acme_jws -from acme import messages -from acme import messages_test -from acme import test_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - - -CERT_DER = test_util.load_vector('cert.der') -CERT_SAN_PEM = test_util.load_vector('cert-san.pem') -CSR_SAN_PEM = test_util.load_vector('csr-san.pem') -KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) -KEY2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - -DIRECTORY_V1 = messages.Directory({ - messages.NewRegistration: - 'https://www.letsencrypt-demo.org/acme/new-reg', - messages.Revocation: - 'https://www.letsencrypt-demo.org/acme/revoke-cert', - messages.NewAuthorization: - 'https://www.letsencrypt-demo.org/acme/new-authz', - messages.CertificateRequest: - 'https://www.letsencrypt-demo.org/acme/new-cert', -}) - -DIRECTORY_V2 = messages.Directory({ - 'newAccount': 'https://www.letsencrypt-demo.org/acme/new-account', - 'newNonce': 'https://www.letsencrypt-demo.org/acme/new-nonce', - 'newOrder': 'https://www.letsencrypt-demo.org/acme/new-order', - 'revokeCert': 'https://www.letsencrypt-demo.org/acme/revoke-cert', -}) - - -class ClientTestBase(unittest.TestCase): - """Base for tests in acme.client.""" - - def setUp(self): - self.response = mock.MagicMock( - ok=True, status_code=http_client.OK, headers={}, links={}) - self.net = mock.MagicMock() - self.net.post.return_value = self.response - self.net.get.return_value = self.response - - self.identifier = messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='example.com') - - # Registration - self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212') - reg = messages.Registration( - contact=self.contact, key=KEY.public_key()) - the_arg = dict(reg) # type: Dict - self.new_reg = messages.NewRegistration(**the_arg) - self.regr = messages.RegistrationResource( - body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') - - # Authorization - authzr_uri = 'https://www.letsencrypt-demo.org/acme/authz/1' - challb = messages.ChallengeBody( - uri=(authzr_uri + '/1'), status=messages.STATUS_VALID, - chall=challenges.DNS(token=jose.b64decode( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'))) - self.challr = messages.ChallengeResource( - body=challb, authzr_uri=authzr_uri) - self.authz = messages.Authorization( - identifier=messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='example.com'), - challenges=(challb,), combinations=None) - self.authzr = messages.AuthorizationResource( - body=self.authz, uri=authzr_uri) - - # Reason code for revocation - self.rsn = 1 - - -class BackwardsCompatibleClientV2Test(ClientTestBase): - """Tests for acme.client.BackwardsCompatibleClientV2.""" - - def setUp(self): - super(BackwardsCompatibleClientV2Test, self).setUp() - # contains a loaded cert - self.certr = messages.CertificateResource( - body=messages_test.CERT) - - loaded = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, CERT_SAN_PEM) - wrapped = jose.ComparableX509(loaded) - self.chain = [wrapped, wrapped] - - self.cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped).decode() - - single_chain = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, loaded).decode() - self.chain_pem = single_chain + single_chain - - self.fullchain_pem = self.cert_pem + self.chain_pem - - self.orderr = messages.OrderResource( - csr_pem=CSR_SAN_PEM) - - def _init(self): - uri = 'http://www.letsencrypt-demo.org/directory' - from acme.client import BackwardsCompatibleClientV2 - return BackwardsCompatibleClientV2(net=self.net, - key=KEY, server=uri) - - def test_init_downloads_directory(self): - uri = 'http://www.letsencrypt-demo.org/directory' - from acme.client import BackwardsCompatibleClientV2 - BackwardsCompatibleClientV2(net=self.net, - key=KEY, server=uri) - self.net.get.assert_called_once_with(uri) - - def test_init_acme_version(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - client = self._init() - self.assertEqual(client.acme_version, 1) - - self.response.json.return_value = DIRECTORY_V2.to_json() - client = self._init() - self.assertEqual(client.acme_version, 2) - - def test_query_registration_client_v2(self): - self.response.json.return_value = DIRECTORY_V2.to_json() - client = self._init() - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, client.query_registration(self.regr)) - - def test_forwarding(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - client = self._init() - self.assertEqual(client.directory, client.client.directory) - self.assertEqual(client.key, KEY) - self.assertEqual(client.deactivate_registration, client.client.deactivate_registration) - self.assertRaises(AttributeError, client.__getattr__, 'nonexistent') - self.assertRaises(AttributeError, client.__getattr__, 'new_account_and_tos') - self.assertRaises(AttributeError, client.__getattr__, 'new_account') - - def test_new_account_and_tos(self): - # v2 no tos - self.response.json.return_value = DIRECTORY_V2.to_json() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.new_account_and_tos(self.new_reg) - mock_client().new_account.assert_called_with(self.new_reg) - - # v2 tos good - with mock.patch('acme.client.ClientV2') as mock_client: - mock_client().directory.meta.__contains__.return_value = True - client = self._init() - client.new_account_and_tos(self.new_reg, lambda x: True) - mock_client().new_account.assert_called_with( - self.new_reg.update(terms_of_service_agreed=True)) - - # v2 tos bad - with mock.patch('acme.client.ClientV2') as mock_client: - mock_client().directory.meta.__contains__.return_value = True - client = self._init() - def _tos_cb(tos): - raise errors.Error - self.assertRaises(errors.Error, client.new_account_and_tos, - self.new_reg, _tos_cb) - mock_client().new_account.assert_not_called() - - # v1 yes tos - self.response.json.return_value = DIRECTORY_V1.to_json() - with mock.patch('acme.client.Client') as mock_client: - regr = mock.MagicMock(terms_of_service="TOS") - mock_client().register.return_value = regr - client = self._init() - client.new_account_and_tos(self.new_reg) - mock_client().register.assert_called_once_with(self.new_reg) - mock_client().agree_to_tos.assert_called_once_with(regr) - - # v1 no tos - with mock.patch('acme.client.Client') as mock_client: - regr = mock.MagicMock(terms_of_service=None) - mock_client().register.return_value = regr - client = self._init() - client.new_account_and_tos(self.new_reg) - mock_client().register.assert_called_once_with(self.new_reg) - mock_client().agree_to_tos.assert_not_called() - - @mock.patch('OpenSSL.crypto.load_certificate_request') - @mock.patch('acme.crypto_util._pyopenssl_cert_or_req_all_names') - def test_new_order_v1(self, mock__pyopenssl_cert_or_req_all_names, - unused_mock_load_certificate_request): - self.response.json.return_value = DIRECTORY_V1.to_json() - mock__pyopenssl_cert_or_req_all_names.return_value = ['example.com', 'www.example.com'] - mock_csr_pem = mock.MagicMock() - with mock.patch('acme.client.Client') as mock_client: - mock_client().request_domain_challenges.return_value = mock.sentinel.auth - client = self._init() - orderr = client.new_order(mock_csr_pem) - self.assertEqual(orderr.authorizations, [mock.sentinel.auth, mock.sentinel.auth]) - - def test_new_order_v2(self): - self.response.json.return_value = DIRECTORY_V2.to_json() - mock_csr_pem = mock.MagicMock() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.new_order(mock_csr_pem) - mock_client().new_order.assert_called_once_with(mock_csr_pem) - - @mock.patch('acme.client.Client') - def test_finalize_order_v1_success(self, mock_client): - self.response.json.return_value = DIRECTORY_V1.to_json() - - mock_client().request_issuance.return_value = self.certr - mock_client().fetch_chain.return_value = self.chain - - deadline = datetime.datetime(9999, 9, 9) - client = self._init() - result = client.finalize_order(self.orderr, deadline) - self.assertEqual(result.fullchain_pem, self.fullchain_pem) - mock_client().fetch_chain.assert_called_once_with(self.certr) - - @mock.patch('acme.client.Client') - def test_finalize_order_v1_fetch_chain_error(self, mock_client): - self.response.json.return_value = DIRECTORY_V1.to_json() - - mock_client().request_issuance.return_value = self.certr - mock_client().fetch_chain.return_value = self.chain - mock_client().fetch_chain.side_effect = [errors.Error, self.chain] - - deadline = datetime.datetime(9999, 9, 9) - client = self._init() - result = client.finalize_order(self.orderr, deadline) - self.assertEqual(result.fullchain_pem, self.fullchain_pem) - self.assertEqual(mock_client().fetch_chain.call_count, 2) - - @mock.patch('acme.client.Client') - def test_finalize_order_v1_timeout(self, mock_client): - self.response.json.return_value = DIRECTORY_V1.to_json() - - mock_client().request_issuance.return_value = self.certr - - deadline = deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) - client = self._init() - self.assertRaises(errors.TimeoutError, client.finalize_order, - self.orderr, deadline) - - def test_finalize_order_v2(self): - self.response.json.return_value = DIRECTORY_V2.to_json() - mock_orderr = mock.MagicMock() - mock_deadline = mock.MagicMock() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.finalize_order(mock_orderr, mock_deadline) - mock_client().finalize_order.assert_called_once_with(mock_orderr, mock_deadline) - - def test_revoke(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - with mock.patch('acme.client.Client') as mock_client: - client = self._init() - client.revoke(messages_test.CERT, self.rsn) - mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) - - self.response.json.return_value = DIRECTORY_V2.to_json() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.revoke(messages_test.CERT, self.rsn) - mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) - - def test_update_registration(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - with mock.patch('acme.client.Client') as mock_client: - client = self._init() - client.update_registration(mock.sentinel.regr, None) - mock_client().update_registration.assert_called_once_with(mock.sentinel.regr, None) - - # newNonce present means it will pick acme_version 2 - def test_external_account_required_true(self): - self.response.json.return_value = messages.Directory({ - 'newNonce': 'http://letsencrypt-test.com/acme/new-nonce', - 'meta': messages.Directory.Meta(external_account_required=True), - }).to_json() - - client = self._init() - - self.assertTrue(client.external_account_required()) - - # newNonce present means it will pick acme_version 2 - def test_external_account_required_false(self): - self.response.json.return_value = messages.Directory({ - 'newNonce': 'http://letsencrypt-test.com/acme/new-nonce', - 'meta': messages.Directory.Meta(external_account_required=False), - }).to_json() - - client = self._init() - - self.assertFalse(client.external_account_required()) - - def test_external_account_required_false_v1(self): - self.response.json.return_value = messages.Directory({ - 'meta': messages.Directory.Meta(external_account_required=False), - }).to_json() - - client = self._init() - - self.assertFalse(client.external_account_required()) - - -class ClientTest(ClientTestBase): - """Tests for acme.client.Client.""" - - def setUp(self): - super(ClientTest, self).setUp() - - self.directory = DIRECTORY_V1 - - # Registration - self.regr = self.regr.update( - terms_of_service='https://www.letsencrypt-demo.org/tos') - - # Request issuance - self.certr = messages.CertificateResource( - body=messages_test.CERT, authzrs=(self.authzr,), - uri='https://www.letsencrypt-demo.org/acme/cert/1', - cert_chain_uri='https://www.letsencrypt-demo.org/ca') - - from acme.client import Client - self.client = Client( - directory=self.directory, key=KEY, alg=jose.RS256, net=self.net) - - def test_init_downloads_directory(self): - uri = 'http://www.letsencrypt-demo.org/directory' - from acme.client import Client - self.client = Client( - directory=uri, key=KEY, alg=jose.RS256, net=self.net) - self.net.get.assert_called_once_with(uri) - - @mock.patch('acme.client.ClientNetwork') - def test_init_without_net(self, mock_net): - mock_net.return_value = mock.sentinel.net - alg = jose.RS256 - from acme.client import Client - self.client = Client( - directory=self.directory, key=KEY, alg=alg) - mock_net.called_once_with(KEY, alg=alg, verify_ssl=True) - self.assertEqual(self.client.net, mock.sentinel.net) - - def test_register(self): - # "Instance of 'Field' has no to_json/update member" bug: - self.response.status_code = http_client.CREATED - self.response.json.return_value = self.regr.body.to_json() - self.response.headers['Location'] = self.regr.uri - self.response.links.update({ - 'terms-of-service': {'url': self.regr.terms_of_service}, - }) - - self.assertEqual(self.regr, self.client.register(self.new_reg)) - # TODO: test POST call arguments - - def test_update_registration(self): - # "Instance of 'Field' has no to_json/update member" bug: - self.response.headers['Location'] = self.regr.uri - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, self.client.update_registration(self.regr)) - # TODO: test POST call arguments - - # TODO: split here and separate test - self.response.json.return_value = self.regr.body.update( - contact=()).to_json() - - def test_deactivate_account(self): - self.response.headers['Location'] = self.regr.uri - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, - self.client.deactivate_registration(self.regr)) - - def test_query_registration(self): - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, self.client.query_registration(self.regr)) - - def test_agree_to_tos(self): - self.client.update_registration = mock.Mock() - self.client.agree_to_tos(self.regr) - regr = self.client.update_registration.call_args[0][0] - self.assertEqual(self.regr.terms_of_service, regr.body.agreement) - - def _prepare_response_for_request_challenges(self): - self.response.status_code = http_client.CREATED - self.response.headers['Location'] = self.authzr.uri - self.response.json.return_value = self.authz.to_json() - - def test_request_challenges(self): - self._prepare_response_for_request_challenges() - self.client.request_challenges(self.identifier) - self.net.post.assert_called_once_with( - self.directory.new_authz, - messages.NewAuthorization(identifier=self.identifier), - acme_version=1) - - def test_request_challenges_deprecated_arg(self): - self._prepare_response_for_request_challenges() - self.client.request_challenges(self.identifier, new_authzr_uri="hi") - self.net.post.assert_called_once_with( - self.directory.new_authz, - messages.NewAuthorization(identifier=self.identifier), - acme_version=1) - - def test_request_challenges_custom_uri(self): - self._prepare_response_for_request_challenges() - self.client.request_challenges(self.identifier) - self.net.post.assert_called_once_with( - 'https://www.letsencrypt-demo.org/acme/new-authz', mock.ANY, - acme_version=1) - - def test_request_challenges_unexpected_update(self): - self._prepare_response_for_request_challenges() - self.response.json.return_value = self.authz.update( - identifier=self.identifier.update(value='foo')).to_json() - self.assertRaises( - errors.UnexpectedUpdate, self.client.request_challenges, - self.identifier) - - def test_request_challenges_wildcard(self): - wildcard_identifier = messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='*.example.org') - self.assertRaises( - errors.WildcardUnsupportedError, self.client.request_challenges, - wildcard_identifier) - - def test_request_domain_challenges(self): - self.client.request_challenges = mock.MagicMock() - self.assertEqual( - self.client.request_challenges(self.identifier), - self.client.request_domain_challenges('example.com')) - - def test_answer_challenge(self): - self.response.links['up'] = {'url': self.challr.authzr_uri} - self.response.json.return_value = self.challr.body.to_json() - - chall_response = challenges.DNSResponse(validation=None) - - self.client.answer_challenge(self.challr.body, chall_response) - - # TODO: split here and separate test - self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge, - self.challr.body.update(uri='foo'), chall_response) - - def test_answer_challenge_missing_next(self): - self.assertRaises( - errors.ClientError, self.client.answer_challenge, - self.challr.body, challenges.DNSResponse(validation=None)) - - def test_retry_after_date(self): - self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' - self.assertEqual( - datetime.datetime(1999, 12, 31, 23, 59, 59), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_invalid(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - - self.response.headers['Retry-After'] = 'foooo' - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_overflow(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - dt_mock.datetime.side_effect = datetime.datetime - - self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST" - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_seconds(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - - self.response.headers['Retry-After'] = '50' - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 50), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_missing(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) - - def test_poll(self): - self.response.json.return_value = self.authzr.body.to_json() - self.assertEqual((self.authzr, self.response), - self.client.poll(self.authzr)) - - # TODO: split here and separate test - self.response.json.return_value = self.authz.update( - identifier=self.identifier.update(value='foo')).to_json() - self.assertRaises( - errors.UnexpectedUpdate, self.client.poll, self.authzr) - - def test_request_issuance(self): - self.response.content = CERT_DER - self.response.headers['Location'] = self.certr.uri - self.response.links['up'] = {'url': self.certr.cert_chain_uri} - self.assertEqual(self.certr, self.client.request_issuance( - messages_test.CSR, (self.authzr,))) - # TODO: check POST args - - def test_request_issuance_missing_up(self): - self.response.content = CERT_DER - self.response.headers['Location'] = self.certr.uri - self.assertEqual( - self.certr.update(cert_chain_uri=None), - self.client.request_issuance(messages_test.CSR, (self.authzr,))) - - def test_request_issuance_missing_location(self): - self.assertRaises( - errors.ClientError, self.client.request_issuance, - messages_test.CSR, (self.authzr,)) - - @mock.patch('acme.client.datetime') - @mock.patch('acme.client.time') - def test_poll_and_request_issuance(self, time_mock, dt_mock): - # clock.dt | pylint: disable=no-member - clock = mock.MagicMock(dt=datetime.datetime(2015, 3, 27)) - - def sleep(seconds): - """increment clock""" - clock.dt += datetime.timedelta(seconds=seconds) - time_mock.sleep.side_effect = sleep - - def now(): - """return current clock value""" - return clock.dt - dt_mock.datetime.now.side_effect = now - dt_mock.timedelta = datetime.timedelta - - def poll(authzr): # pylint: disable=missing-docstring - # record poll start time based on the current clock value - authzr.times.append(clock.dt) - - # suppose it takes 2 seconds for server to produce the - # result, increment clock - clock.dt += datetime.timedelta(seconds=2) - - if len(authzr.retries) == 1: # no more retries - done = mock.MagicMock(uri=authzr.uri, times=authzr.times) - done.body.status = authzr.retries[0] - return done, [] - - # response (2nd result tuple element) is reduced to only - # Retry-After header contents represented as integer - # seconds; authzr.retries is a list of Retry-After - # headers, head(retries) is peeled of as a current - # Retry-After header, and tail(retries) is persisted for - # later poll() calls - return (mock.MagicMock(retries=authzr.retries[1:], - uri=authzr.uri + '.', times=authzr.times), - authzr.retries[0]) - self.client.poll = mock.MagicMock(side_effect=poll) - - mintime = 7 - - def retry_after(response, default): - # pylint: disable=missing-docstring - # check that poll_and_request_issuance correctly passes mintime - self.assertEqual(default, mintime) - return clock.dt + datetime.timedelta(seconds=response) - self.client.retry_after = mock.MagicMock(side_effect=retry_after) - - def request_issuance(csr, authzrs): # pylint: disable=missing-docstring - return csr, authzrs - self.client.request_issuance = mock.MagicMock( - side_effect=request_issuance) - - csr = mock.MagicMock() - authzrs = ( - mock.MagicMock(uri='a', times=[], retries=( - 8, 20, 30, messages.STATUS_VALID)), - mock.MagicMock(uri='b', times=[], retries=( - 5, messages.STATUS_VALID)), - ) - - cert, updated_authzrs = self.client.poll_and_request_issuance( - csr, authzrs, mintime=mintime, - # make sure that max_attempts is per-authorization, rather - # than global - max_attempts=max(len(authzrs[0].retries), len(authzrs[1].retries))) - self.assertTrue(cert[0] is csr) - self.assertTrue(cert[1] is updated_authzrs) - self.assertEqual(updated_authzrs[0].uri, 'a...') - self.assertEqual(updated_authzrs[1].uri, 'b.') - self.assertEqual(updated_authzrs[0].times, [ - datetime.datetime(2015, 3, 27), - # a is scheduled for 10, but b is polling [9..11), so it - # will be picked up as soon as b is finished, without - # additional sleeping - datetime.datetime(2015, 3, 27, 0, 0, 11), - datetime.datetime(2015, 3, 27, 0, 0, 33), - datetime.datetime(2015, 3, 27, 0, 1, 5), - ]) - self.assertEqual(updated_authzrs[1].times, [ - datetime.datetime(2015, 3, 27, 0, 0, 2), - datetime.datetime(2015, 3, 27, 0, 0, 9), - ]) - self.assertEqual(clock.dt, datetime.datetime(2015, 3, 27, 0, 1, 7)) - - # CA sets invalid | TODO: move to a separate test - invalid_authzr = mock.MagicMock( - times=[], retries=[messages.STATUS_INVALID]) - self.assertRaises( - errors.PollError, self.client.poll_and_request_issuance, - csr, authzrs=(invalid_authzr,), mintime=mintime) - - # exceeded max_attempts | TODO: move to a separate test - self.assertRaises( - errors.PollError, self.client.poll_and_request_issuance, - csr, authzrs, mintime=mintime, max_attempts=2) - - def test_deactivate_authorization(self): - authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED) - self.response.json.return_value = authzb.to_json() - authzr = self.client.deactivate_authorization(self.authzr) - self.assertEqual(authzb, authzr.body) - self.assertEqual(self.client.net.post.call_count, 1) - self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0]) - - def test_check_cert(self): - self.response.headers['Location'] = self.certr.uri - self.response.content = CERT_DER - self.assertEqual(self.certr.update(body=messages_test.CERT), - self.client.check_cert(self.certr)) - - # TODO: split here and separate test - self.response.headers['Location'] = 'foo' - self.assertRaises( - errors.UnexpectedUpdate, self.client.check_cert, self.certr) - - def test_check_cert_missing_location(self): - self.response.content = CERT_DER - self.assertRaises( - errors.ClientError, self.client.check_cert, self.certr) - - def test_refresh(self): - self.client.check_cert = mock.MagicMock() - self.assertEqual( - self.client.check_cert(self.certr), self.client.refresh(self.certr)) - - def test_fetch_chain_no_up_link(self): - self.assertEqual([], self.client.fetch_chain(self.certr.update( - cert_chain_uri=None))) - - def test_fetch_chain_single(self): - # pylint: disable=protected-access - self.client._get_cert = mock.MagicMock() - self.client._get_cert.return_value = ( - mock.MagicMock(links={}), "certificate") - self.assertEqual([self.client._get_cert(self.certr.cert_chain_uri)[1]], - self.client.fetch_chain(self.certr)) - - def test_fetch_chain_max(self): - # pylint: disable=protected-access - up_response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) - noup_response = mock.MagicMock(links={}) - self.client._get_cert = mock.MagicMock() - self.client._get_cert.side_effect = [ - (up_response, "cert")] * 9 + [(noup_response, "last_cert")] - chain = self.client.fetch_chain(self.certr, max_length=10) - self.assertEqual(chain, ["cert"] * 9 + ["last_cert"]) - - def test_fetch_chain_too_many(self): # recursive - # pylint: disable=protected-access - response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) - self.client._get_cert = mock.MagicMock() - self.client._get_cert.return_value = (response, "certificate") - self.assertRaises(errors.Error, self.client.fetch_chain, self.certr) - - def test_revoke(self): - self.client.revoke(self.certr.body, self.rsn) - self.net.post.assert_called_once_with( - self.directory[messages.Revocation], mock.ANY, acme_version=1) - - def test_revocation_payload(self): - obj = messages.Revocation(certificate=self.certr.body, reason=self.rsn) - self.assertTrue('reason' in obj.to_partial_json().keys()) - self.assertEqual(self.rsn, obj.to_partial_json()['reason']) - - def test_revoke_bad_status_raises_error(self): - self.response.status_code = http_client.METHOD_NOT_ALLOWED - self.assertRaises( - errors.ClientError, - self.client.revoke, - self.certr, - self.rsn) - - -class ClientV2Test(ClientTestBase): - """Tests for acme.client.ClientV2.""" - - def setUp(self): - super(ClientV2Test, self).setUp() - - self.directory = DIRECTORY_V2 - - from acme.client import ClientV2 - self.client = ClientV2(self.directory, self.net) - - self.new_reg = self.new_reg.update(terms_of_service_agreed=True) - - self.authzr_uri2 = 'https://www.letsencrypt-demo.org/acme/authz/2' - self.authz2 = self.authz.update(identifier=messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='www.example.com'), - status=messages.STATUS_PENDING) - self.authzr2 = messages.AuthorizationResource( - body=self.authz2, uri=self.authzr_uri2) - - self.order = messages.Order( - identifiers=(self.authz.identifier, self.authz2.identifier), - status=messages.STATUS_PENDING, - authorizations=(self.authzr.uri, self.authzr_uri2), - finalize='https://www.letsencrypt-demo.org/acme/acct/1/order/1/finalize') - self.orderr = messages.OrderResource( - body=self.order, - uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1', - authorizations=[self.authzr, self.authzr2], csr_pem=CSR_SAN_PEM) - - def test_new_account(self): - self.response.status_code = http_client.CREATED - self.response.json.return_value = self.regr.body.to_json() - self.response.headers['Location'] = self.regr.uri - - self.assertEqual(self.regr, self.client.new_account(self.new_reg)) - - def test_new_account_conflict(self): - self.response.status_code = http_client.OK - self.response.headers['Location'] = self.regr.uri - self.assertRaises(errors.ConflictError, self.client.new_account, self.new_reg) - - def test_new_order(self): - order_response = copy.deepcopy(self.response) - order_response.status_code = http_client.CREATED - order_response.json.return_value = self.order.to_json() - order_response.headers['Location'] = self.orderr.uri - self.net.post.return_value = order_response - - authz_response = copy.deepcopy(self.response) - authz_response.json.return_value = self.authz.to_json() - authz_response.headers['Location'] = self.authzr.uri - authz_response2 = self.response - authz_response2.json.return_value = self.authz2.to_json() - authz_response2.headers['Location'] = self.authzr2.uri - - with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get: - mock_post_as_get.side_effect = (authz_response, authz_response2) - self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr) - - @mock.patch('acme.client.datetime') - def test_poll_and_finalize(self, mock_datetime): - mock_datetime.datetime.now.return_value = datetime.datetime(2018, 2, 15) - mock_datetime.timedelta = datetime.timedelta - expected_deadline = mock_datetime.datetime.now() + datetime.timedelta(seconds=90) - - self.client.poll_authorizations = mock.Mock(return_value=self.orderr) - self.client.finalize_order = mock.Mock(return_value=self.orderr) - - self.assertEqual(self.client.poll_and_finalize(self.orderr), self.orderr) - self.client.poll_authorizations.assert_called_once_with(self.orderr, expected_deadline) - self.client.finalize_order.assert_called_once_with(self.orderr, expected_deadline) - - @mock.patch('acme.client.datetime') - def test_poll_authorizations_timeout(self, mock_datetime): - now_side_effect = [datetime.datetime(2018, 2, 15), - datetime.datetime(2018, 2, 16), - datetime.datetime(2018, 2, 17)] - mock_datetime.datetime.now.side_effect = now_side_effect - self.response.json.side_effect = [ - self.authz.to_json(), self.authz2.to_json(), self.authz2.to_json()] - - self.assertRaises( - errors.TimeoutError, self.client.poll_authorizations, self.orderr, now_side_effect[1]) - - def test_poll_authorizations_failure(self): - deadline = datetime.datetime(9999, 9, 9) - challb = self.challr.body.update(status=messages.STATUS_INVALID, - error=messages.Error.with_code('unauthorized')) - authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,)) - self.response.json.return_value = authz.to_json() - - self.assertRaises( - errors.ValidationError, self.client.poll_authorizations, self.orderr, deadline) - - def test_poll_authorizations_success(self): - deadline = datetime.datetime(9999, 9, 9) - updated_authz2 = self.authz2.update(status=messages.STATUS_VALID) - updated_authzr2 = messages.AuthorizationResource( - body=updated_authz2, uri=self.authzr_uri2) - updated_orderr = self.orderr.update(authorizations=[self.authzr, updated_authzr2]) - - self.response.json.side_effect = ( - self.authz.to_json(), self.authz2.to_json(), updated_authz2.to_json()) - self.assertEqual(self.client.poll_authorizations(self.orderr, deadline), updated_orderr) - - def test_finalize_order_success(self): - updated_order = self.order.update( - certificate='https://www.letsencrypt-demo.org/acme/cert/') - updated_orderr = self.orderr.update(body=updated_order, fullchain_pem=CERT_SAN_PEM) - - self.response.json.return_value = updated_order.to_json() - self.response.text = CERT_SAN_PEM - - deadline = datetime.datetime(9999, 9, 9) - self.assertEqual(self.client.finalize_order(self.orderr, deadline), updated_orderr) - - def test_finalize_order_error(self): - updated_order = self.order.update(error=messages.Error.with_code('unauthorized')) - self.response.json.return_value = updated_order.to_json() - - deadline = datetime.datetime(9999, 9, 9) - self.assertRaises(errors.IssuanceError, self.client.finalize_order, self.orderr, deadline) - - def test_finalize_order_timeout(self): - deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) - self.assertRaises(errors.TimeoutError, self.client.finalize_order, self.orderr, deadline) - - def test_revoke(self): - self.client.revoke(messages_test.CERT, self.rsn) - self.net.post.assert_called_once_with( - self.directory["revokeCert"], mock.ANY, acme_version=2, - new_nonce_url=DIRECTORY_V2['newNonce']) - - def test_update_registration(self): - # "Instance of 'Field' has no to_json/update member" bug: - self.response.headers['Location'] = self.regr.uri - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, self.client.update_registration(self.regr)) - self.assertNotEqual(self.client.net.account, None) - self.assertEqual(self.client.net.post.call_count, 2) - self.assertTrue(DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0]) - - self.response.json.return_value = self.regr.body.update( - contact=()).to_json() - - def test_external_account_required_true(self): - self.client.directory = messages.Directory({ - 'meta': messages.Directory.Meta(external_account_required=True) - }) - - self.assertTrue(self.client.external_account_required()) - - def test_external_account_required_false(self): - self.client.directory = messages.Directory({ - 'meta': messages.Directory.Meta(external_account_required=False) - }) - - self.assertFalse(self.client.external_account_required()) - - def test_external_account_required_default(self): - self.assertFalse(self.client.external_account_required()) - - def test_post_as_get(self): - with mock.patch('acme.client.ClientV2._authzr_from_response') as mock_client: - mock_client.return_value = self.authzr2 - - self.client.poll(self.authzr2) # pylint: disable=protected-access - - self.client.net.post.assert_called_once_with( - self.authzr2.uri, None, acme_version=2, - new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce') - self.client.net.get.assert_not_called() - - class FakeError(messages.Error): - """Fake error to reproduce a malformed request ACME error""" - def __init__(self): # pylint: disable=super-init-not-called - pass - @property - def code(self): - return 'malformed' - self.client.net.post.side_effect = FakeError() - - self.client.poll(self.authzr2) # pylint: disable=protected-access - - self.client.net.get.assert_called_once_with(self.authzr2.uri) - - -class MockJSONDeSerializable(jose.JSONDeSerializable): - # pylint: disable=missing-docstring - def __init__(self, value): - self.value = value - - def to_partial_json(self): - return {'foo': self.value} - - @classmethod - def from_json(cls, jobj): - pass # pragma: no cover - - -class ClientNetworkTest(unittest.TestCase): - """Tests for acme.client.ClientNetwork.""" - - def setUp(self): - self.verify_ssl = mock.MagicMock() - self.wrap_in_jws = mock.MagicMock(return_value=mock.sentinel.wrapped) - - from acme.client import ClientNetwork - self.net = ClientNetwork( - key=KEY, alg=jose.RS256, verify_ssl=self.verify_ssl, - user_agent='acme-python-test') - - self.response = mock.MagicMock(ok=True, status_code=http_client.OK) - self.response.headers = {} - self.response.links = {} - - def test_init(self): - self.assertTrue(self.net.verify_ssl is self.verify_ssl) - - def test_wrap_in_jws(self): - # pylint: disable=protected-access - jws_dump = self.net._wrap_in_jws( - MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", - acme_version=1) - jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) - self.assertEqual(jws.signature.combined.nonce, b'Tg') - - def test_wrap_in_jws_v2(self): - self.net.account = {'uri': 'acct-uri'} - # pylint: disable=protected-access - jws_dump = self.net._wrap_in_jws( - MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", - acme_version=2) - jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) - self.assertEqual(jws.signature.combined.nonce, b'Tg') - self.assertEqual(jws.signature.combined.kid, u'acct-uri') - self.assertEqual(jws.signature.combined.url, u'url') - - def test_check_response_not_ok_jobj_no_error(self): - self.response.ok = False - self.response.json.return_value = {} - with mock.patch('acme.client.messages.Error.from_json') as from_json: - from_json.side_effect = jose.DeserializationError - # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response) - - def test_check_response_not_ok_jobj_error(self): - self.response.ok = False - self.response.json.return_value = messages.Error( - detail='foo', typ='serverInternal', title='some title').to_json() - # pylint: disable=protected-access - self.assertRaises( - messages.Error, self.net._check_response, self.response) - - def test_check_response_not_ok_no_jobj(self): - self.response.ok = False - self.response.json.side_effect = ValueError - # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response) - - def test_check_response_ok_no_jobj_ct_required(self): - self.response.json.side_effect = ValueError - for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: - self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response, - content_type=self.net.JSON_CONTENT_TYPE) - - def test_check_response_ok_no_jobj_no_ct(self): - self.response.json.side_effect = ValueError - for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: - self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter - self.assertEqual( - self.response, self.net._check_response(self.response)) - - def test_check_response_conflict(self): - self.response.ok = False - self.response.status_code = 409 - # pylint: disable=protected-access - self.assertRaises(errors.ConflictError, self.net._check_response, self.response) - - def test_check_response_jobj(self): - self.response.json.return_value = {} - for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: - self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter - self.assertEqual( - self.response, self.net._check_response(self.response)) - - def test_send_request(self): - self.net.session = mock.MagicMock() - self.net.session.request.return_value = self.response - # pylint: disable=protected-access - self.assertEqual(self.response, self.net._send_request( - 'HEAD', 'http://example.com/', 'foo', bar='baz')) - self.net.session.request.assert_called_once_with( - 'HEAD', 'http://example.com/', 'foo', - headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, bar='baz') - - @mock.patch('acme.client.logger') - def test_send_request_get_der(self, mock_logger): - self.net.session = mock.MagicMock() - self.net.session.request.return_value = mock.MagicMock( - ok=True, status_code=http_client.OK, - headers={"Content-Type": "application/pkix-cert"}, - content=b"hi") - # pylint: disable=protected-access - self.net._send_request('HEAD', 'http://example.com/', 'foo', - timeout=mock.ANY, bar='baz') - mock_logger.debug.assert_called_with( - 'Received response:\nHTTP %d\n%s\n\n%s', 200, - 'Content-Type: application/pkix-cert', b'aGk=') - - def test_send_request_post(self): - self.net.session = mock.MagicMock() - self.net.session.request.return_value = self.response - # pylint: disable=protected-access - self.assertEqual(self.response, self.net._send_request( - 'POST', 'http://example.com/', 'foo', data='qux', bar='baz')) - self.net.session.request.assert_called_once_with( - 'POST', 'http://example.com/', 'foo', - headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, data='qux', bar='baz') - - def test_send_request_verify_ssl(self): - # pylint: disable=protected-access - for verify in True, False: - self.net.session = mock.MagicMock() - self.net.session.request.return_value = self.response - self.net.verify_ssl = verify - # pylint: disable=protected-access - self.assertEqual( - self.response, - self.net._send_request('GET', 'http://example.com/')) - self.net.session.request.assert_called_once_with( - 'GET', 'http://example.com/', verify=verify, - timeout=mock.ANY, headers=mock.ANY) - - def test_send_request_user_agent(self): - self.net.session = mock.MagicMock() - # pylint: disable=protected-access - self.net._send_request('GET', 'http://example.com/', - headers={'bar': 'baz'}) - self.net.session.request.assert_called_once_with( - 'GET', 'http://example.com/', verify=mock.ANY, - timeout=mock.ANY, - headers={'User-Agent': 'acme-python-test', 'bar': 'baz'}) - - self.net._send_request('GET', 'http://example.com/', - headers={'User-Agent': 'foo2'}) - self.net.session.request.assert_called_with( - 'GET', 'http://example.com/', - verify=mock.ANY, timeout=mock.ANY, headers={'User-Agent': 'foo2'}) - - def test_send_request_timeout(self): - self.net.session = mock.MagicMock() - # pylint: disable=protected-access - self.net._send_request('GET', 'http://example.com/', - headers={'bar': 'baz'}) - self.net.session.request.assert_called_once_with( - mock.ANY, mock.ANY, verify=mock.ANY, headers=mock.ANY, - timeout=45) - - def test_del(self, close_exception=None): - sess = mock.MagicMock() - - if close_exception is not None: - sess.close.side_effect = close_exception - - self.net.session = sess - del self.net - sess.close.assert_called_once_with() - - def test_del_error(self): - self.test_del(ReferenceError) - - @mock.patch('acme.client.requests') - def test_requests_error_passthrough(self, mock_requests): - mock_requests.exceptions = requests.exceptions - mock_requests.request.side_effect = requests.exceptions.RequestException - # pylint: disable=protected-access - self.assertRaises(requests.exceptions.RequestException, - self.net._send_request, 'GET', 'uri') - - def test_urllib_error(self): - # Using a connection error to test a properly formatted error message - try: - # pylint: disable=protected-access - self.net._send_request('GET', "http://localhost:19123/nonexistent.txt") - - # Value Error Generated Exceptions - except ValueError as y: - self.assertEqual("Requesting localhost/nonexistent: " - "Connection refused", str(y)) - - # Requests Library Exceptions - except requests.exceptions.ConnectionError as z: #pragma: no cover - self.assertTrue("'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z)) - - -class ClientNetworkWithMockedResponseTest(unittest.TestCase): - """Tests for acme.client.ClientNetwork which mock out response.""" - - def setUp(self): - from acme.client import ClientNetwork - self.net = ClientNetwork(key=None, alg=None) - - self.response = mock.MagicMock(ok=True, status_code=http_client.OK) - self.response.headers = {} - self.response.links = {} - self.response.checked = False - self.acmev1_nonce_response = mock.MagicMock(ok=False, - status_code=http_client.METHOD_NOT_ALLOWED) - self.acmev1_nonce_response.headers = {} - self.obj = mock.MagicMock() - self.wrapped_obj = mock.MagicMock() - self.content_type = mock.sentinel.content_type - - self.all_nonces = [ - jose.b64encode(b'Nonce'), - jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')] - self.available_nonces = self.all_nonces[:] - - def send_request(*args, **kwargs): - # pylint: disable=unused-argument,missing-docstring - self.assertFalse("new_nonce_url" in kwargs) - method = args[0] - uri = args[1] - if method == 'HEAD' and uri != "new_nonce_uri": - response = self.acmev1_nonce_response - else: - response = self.response - - if self.available_nonces: - response.headers = { - self.net.REPLAY_NONCE_HEADER: - self.available_nonces.pop().decode()} - else: - response.headers = {} - return response - - # pylint: disable=protected-access - self.net._send_request = self.send_request = mock.MagicMock( - side_effect=send_request) - self.net._check_response = self.check_response - self.net._wrap_in_jws = mock.MagicMock(return_value=self.wrapped_obj) - - def check_response(self, response, content_type): - # pylint: disable=missing-docstring - self.assertEqual(self.response, response) - self.assertEqual(self.content_type, content_type) - self.assertTrue(self.response.ok) - self.response.checked = True - return self.response - - def test_head(self): - self.assertEqual(self.acmev1_nonce_response, self.net.head( - 'http://example.com/', 'foo', bar='baz')) - self.send_request.assert_called_once_with( - 'HEAD', 'http://example.com/', 'foo', bar='baz') - - def test_head_v2(self): - self.assertEqual(self.response, self.net.head( - 'new_nonce_uri', 'foo', bar='baz')) - self.send_request.assert_called_once_with( - 'HEAD', 'new_nonce_uri', 'foo', bar='baz') - - def test_get(self): - self.assertEqual(self.response, self.net.get( - 'http://example.com/', content_type=self.content_type, bar='baz')) - self.assertTrue(self.response.checked) - self.send_request.assert_called_once_with( - 'GET', 'http://example.com/', bar='baz') - - def test_post_no_content_type(self): - self.content_type = self.net.JOSE_CONTENT_TYPE - self.assertEqual(self.response, self.net.post('uri', self.obj)) - self.assertTrue(self.response.checked) - - def test_post(self): - # pylint: disable=protected-access - self.assertEqual(self.response, self.net.post( - 'uri', self.obj, content_type=self.content_type)) - self.assertTrue(self.response.checked) - self.net._wrap_in_jws.assert_called_once_with( - self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) - - self.available_nonces = [] - self.assertRaises(errors.MissingNonce, self.net.post, - 'uri', self.obj, content_type=self.content_type) - self.net._wrap_in_jws.assert_called_with( - self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) - - def test_post_wrong_initial_nonce(self): # HEAD - self.available_nonces = [b'f', jose.b64encode(b'good')] - self.assertRaises(errors.BadNonce, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_wrong_post_response_nonce(self): - self.available_nonces = [jose.b64encode(b'good'), b'f'] - self.assertRaises(errors.BadNonce, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_failed_retry(self): - check_response = mock.MagicMock() - check_response.side_effect = messages.Error.with_code('badNonce') - - # pylint: disable=protected-access - self.net._check_response = check_response - self.assertRaises(messages.Error, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_not_retried(self): - check_response = mock.MagicMock() - check_response.side_effect = [messages.Error.with_code('malformed'), - self.response] - - # pylint: disable=protected-access - self.net._check_response = check_response - self.assertRaises(messages.Error, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_successful_retry(self): - post_once = mock.MagicMock() - post_once.side_effect = [messages.Error.with_code('badNonce'), - self.response] - - # pylint: disable=protected-access - self.assertEqual(self.response, self.net.post( - 'uri', self.obj, content_type=self.content_type)) - - def test_head_get_post_error_passthrough(self): - self.send_request.side_effect = requests.exceptions.RequestException - for method in self.net.head, self.net.get: - self.assertRaises( - requests.exceptions.RequestException, method, 'GET', 'uri') - self.assertRaises(requests.exceptions.RequestException, - self.net.post, 'uri', obj=self.obj) - - def test_post_bad_nonce_head(self): - # pylint: disable=protected-access - # regression test for https://github.com/certbot/certbot/issues/6092 - bad_response = mock.MagicMock(ok=False, status_code=http_client.SERVICE_UNAVAILABLE) - self.net._send_request = mock.MagicMock() - self.net._send_request.return_value = bad_response - self.content_type = None - check_response = mock.MagicMock() - self.net._check_response = check_response - self.assertRaises(errors.ClientError, self.net.post, 'uri', - self.obj, content_type=self.content_type, acme_version=2, - new_nonce_url='new_nonce_uri') - self.assertEqual(check_response.call_count, 1) - - def test_new_nonce_uri_removed(self): - self.content_type = None - self.net.post('uri', self.obj, content_type=None, - acme_version=2, new_nonce_url='new_nonce_uri') - - -class ClientNetworkSourceAddressBindingTest(unittest.TestCase): - """Tests that if ClientNetwork has a source IP set manually, the underlying library has - used the provided source address.""" - - def setUp(self): - self.source_address = "8.8.8.8" - - def test_source_address_set(self): - from acme.client import ClientNetwork - net = ClientNetwork(key=None, alg=None, source_address=self.source_address) - for adapter in net.session.adapters.values(): - self.assertTrue(self.source_address in adapter.source_address) - - def test_behavior_assumption(self): - """This is a test that guardrails the HTTPAdapter behavior so that if the default for - a Session() changes, the assumptions here aren't violated silently.""" - from acme.client import ClientNetwork - # Source address not specified, so the default adapter type should be bound -- this - # test should fail if the default adapter type is changed by requests - net = ClientNetwork(key=None, alg=None) - session = requests.Session() - for scheme in session.adapters.keys(): - client_network_adapter = net.session.adapters.get(scheme) - default_adapter = session.adapters.get(scheme) - self.assertEqual(client_network_adapter.__class__, default_adapter.__class__) - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py deleted file mode 100644 index 6477f6501..000000000 --- a/acme/acme/crypto_util_test.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Tests for acme.crypto_util.""" -import itertools -import socket -import threading -import time -import unittest - -import six -from six.moves import socketserver #type: ignore # pylint: disable=import-error - -import josepy as jose -import OpenSSL - -from acme import errors -from acme import test_util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - - -class SSLSocketAndProbeSNITest(unittest.TestCase): - """Tests for acme.crypto_util.SSLSocket/probe_sni.""" - - - def setUp(self): - self.cert = test_util.load_comparable_cert('rsa2048_cert.pem') - key = test_util.load_pyopenssl_private_key('rsa2048_key.pem') - # pylint: disable=protected-access - certs = {b'foo': (key, self.cert.wrapped)} - - from acme.crypto_util import SSLSocket - - class _TestServer(socketserver.TCPServer): - - # six.moves.* | pylint: disable=attribute-defined-outside-init,no-init - - def server_bind(self): # pylint: disable=missing-docstring - self.socket = SSLSocket(socket.socket(), certs=certs) - socketserver.TCPServer.server_bind(self) - - self.server = _TestServer(('', 0), socketserver.BaseRequestHandler) - self.port = self.server.socket.getsockname()[1] - self.server_thread = threading.Thread( - # pylint: disable=no-member - target=self.server.handle_request) - - def tearDown(self): - if self.server_thread.is_alive(): - # The thread may have already terminated. - self.server_thread.join() # pragma: no cover - - def _probe(self, name): - from acme.crypto_util import probe_sni - return jose.ComparableX509(probe_sni( - name, host='127.0.0.1', port=self.port)) - - def _start_server(self): - self.server_thread.start() - time.sleep(1) # TODO: avoid race conditions in other way - - def test_probe_ok(self): - self._start_server() - self.assertEqual(self.cert, self._probe(b'foo')) - - def test_probe_not_recognized_name(self): - self._start_server() - self.assertRaises(errors.Error, self._probe, b'bar') - - def test_probe_connection_error(self): - # pylint has a hard time with six - self.server.server_close() # pylint: disable=no-member - original_timeout = socket.getdefaulttimeout() - try: - socket.setdefaulttimeout(1) - self.assertRaises(errors.Error, self._probe, b'bar') - finally: - socket.setdefaulttimeout(original_timeout) - - -class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase): - """Test for acme.crypto_util._pyopenssl_cert_or_req_all_names.""" - - @classmethod - def _call(cls, loader, name): - # pylint: disable=protected-access - from acme.crypto_util import _pyopenssl_cert_or_req_all_names - return _pyopenssl_cert_or_req_all_names(loader(name)) - - def _call_cert(self, name): - return self._call(test_util.load_cert, name) - - def test_cert_one_san_no_common(self): - self.assertEqual(self._call_cert('cert-nocn.der'), - ['no-common-name.badssl.com']) - - def test_cert_no_sans_yes_common(self): - self.assertEqual(self._call_cert('cert.pem'), ['example.com']) - - def test_cert_two_sans_yes_common(self): - self.assertEqual(self._call_cert('cert-san.pem'), - ['example.com', 'www.example.com']) - - -class PyOpenSSLCertOrReqSANTest(unittest.TestCase): - """Test for acme.crypto_util._pyopenssl_cert_or_req_san.""" - - - @classmethod - def _call(cls, loader, name): - # pylint: disable=protected-access - from acme.crypto_util import _pyopenssl_cert_or_req_san - return _pyopenssl_cert_or_req_san(loader(name)) - - @classmethod - def _get_idn_names(cls): - """Returns expected names from '{cert,csr}-idnsans.pem'.""" - chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400), - range(0x641, 0x6fc), - range(0x1820, 0x1877))] - return [''.join(chars[i: i + 45]) + '.invalid' - for i in range(0, len(chars), 45)] - - def _call_cert(self, name): - return self._call(test_util.load_cert, name) - - def _call_csr(self, name): - return self._call(test_util.load_csr, name) - - def test_cert_no_sans(self): - self.assertEqual(self._call_cert('cert.pem'), []) - - def test_cert_two_sans(self): - self.assertEqual(self._call_cert('cert-san.pem'), - ['example.com', 'www.example.com']) - - def test_cert_hundred_sans(self): - self.assertEqual(self._call_cert('cert-100sans.pem'), - ['example{0}.com'.format(i) for i in range(1, 101)]) - - def test_cert_idn_sans(self): - self.assertEqual(self._call_cert('cert-idnsans.pem'), - self._get_idn_names()) - - def test_csr_no_sans(self): - self.assertEqual(self._call_csr('csr-nosans.pem'), []) - - def test_csr_one_san(self): - self.assertEqual(self._call_csr('csr.pem'), ['example.com']) - - def test_csr_two_sans(self): - self.assertEqual(self._call_csr('csr-san.pem'), - ['example.com', 'www.example.com']) - - def test_csr_six_sans(self): - self.assertEqual(self._call_csr('csr-6sans.pem'), - ['example.com', 'example.org', 'example.net', - 'example.info', 'subdomain.example.com', - 'other.subdomain.example.com']) - - def test_csr_hundred_sans(self): - self.assertEqual(self._call_csr('csr-100sans.pem'), - ['example{0}.com'.format(i) for i in range(1, 101)]) - - def test_csr_idn_sans(self): - self.assertEqual(self._call_csr('csr-idnsans.pem'), - self._get_idn_names()) - - def test_critical_san(self): - self.assertEqual(self._call_cert('critical-san.pem'), - ['chicago-cubs.venafi.example', 'cubs.venafi.example']) - - - -class RandomSnTest(unittest.TestCase): - """Test for random certificate serial numbers.""" - - - def setUp(self): - self.cert_count = 5 - self.serial_num = [] # type: List[int] - self.key = OpenSSL.crypto.PKey() - self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) - - def test_sn_collisions(self): - from acme.crypto_util import gen_ss_cert - - for _ in range(self.cert_count): - cert = gen_ss_cert(self.key, ['dummy'], force_san=True) - self.serial_num.append(cert.get_serial_number()) - self.assertTrue(len(set(self.serial_num)) > 1) - -class MakeCSRTest(unittest.TestCase): - """Test for standalone functions.""" - - @classmethod - def _call_with_key(cls, *args, **kwargs): - privkey = OpenSSL.crypto.PKey() - privkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) - privkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey) - from acme.crypto_util import make_csr - return make_csr(privkey_pem, *args, **kwargs) - - def test_make_csr(self): - csr_pem = self._call_with_key(["a.example", "b.example"]) - self.assertTrue(b'--BEGIN CERTIFICATE REQUEST--' in csr_pem) - self.assertTrue(b'--END CERTIFICATE REQUEST--' in csr_pem) - csr = OpenSSL.crypto.load_certificate_request( - OpenSSL.crypto.FILETYPE_PEM, csr_pem) - # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't - # have a get_extensions() method, so we skip this test if the method - # isn't available. - if hasattr(csr, 'get_extensions'): - self.assertEqual(len(csr.get_extensions()), 1) - self.assertEqual(csr.get_extensions()[0].get_data(), - OpenSSL.crypto.X509Extension( - b'subjectAltName', - critical=False, - value=b'DNS:a.example, DNS:b.example', - ).get_data(), - ) - - def test_make_csr_must_staple(self): - csr_pem = self._call_with_key(["a.example"], must_staple=True) - csr = OpenSSL.crypto.load_certificate_request( - OpenSSL.crypto.FILETYPE_PEM, csr_pem) - - # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't - # have a get_extensions() method, so we skip this test if the method - # isn't available. - if hasattr(csr, 'get_extensions'): - self.assertEqual(len(csr.get_extensions()), 2) - # NOTE: Ideally we would filter by the TLS Feature OID, but - # OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID, - # and the shortname field is just "UNDEF" - must_staple_exts = [e for e in csr.get_extensions() - if e.get_data() == b"0\x03\x02\x01\x05"] - self.assertEqual(len(must_staple_exts), 1, - "Expected exactly one Must Staple extension") - - -class DumpPyopensslChainTest(unittest.TestCase): - """Test for dump_pyopenssl_chain.""" - - @classmethod - def _call(cls, loaded): - # pylint: disable=protected-access - from acme.crypto_util import dump_pyopenssl_chain - return dump_pyopenssl_chain(loaded) - - def test_dump_pyopenssl_chain(self): - names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] - loaded = [test_util.load_cert(name) for name in names] - length = sum( - len(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)) - for cert in loaded) - self.assertEqual(len(self._call(loaded)), length) - - def test_dump_pyopenssl_chain_wrapped(self): - names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] - loaded = [test_util.load_cert(name) for name in names] - wrap_func = jose.ComparableX509 - wrapped = [wrap_func(cert) for cert in loaded] - dump_func = OpenSSL.crypto.dump_certificate - length = sum(len(dump_func(OpenSSL.crypto.FILETYPE_PEM, cert)) for cert in loaded) - self.assertEqual(len(self._call(wrapped)), length) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/errors_test.py b/acme/acme/errors_test.py deleted file mode 100644 index 1e5f3d479..000000000 --- a/acme/acme/errors_test.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Tests for acme.errors.""" -import unittest - -import mock - - -class BadNonceTest(unittest.TestCase): - """Tests for acme.errors.BadNonce.""" - - def setUp(self): - from acme.errors import BadNonce - self.error = BadNonce(nonce="xxx", error="error") - - def test_str(self): - self.assertEqual("Invalid nonce ('xxx'): error", str(self.error)) - - -class MissingNonceTest(unittest.TestCase): - """Tests for acme.errors.MissingNonce.""" - - def setUp(self): - from acme.errors import MissingNonce - self.response = mock.MagicMock(headers={}) - self.response.request.method = 'FOO' - self.error = MissingNonce(self.response) - - def test_str(self): - self.assertTrue("FOO" in str(self.error)) - self.assertTrue("{}" in str(self.error)) - - -class PollErrorTest(unittest.TestCase): - """Tests for acme.errors.PollError.""" - - def setUp(self): - from acme.errors import PollError - self.timeout = PollError( - exhausted=set([mock.sentinel.AR]), - updated={}) - self.invalid = PollError(exhausted=set(), updated={ - mock.sentinel.AR: mock.sentinel.AR2}) - - def test_timeout(self): - self.assertTrue(self.timeout.timeout) - self.assertFalse(self.invalid.timeout) - - def test_repr(self): - self.assertEqual('PollError(exhausted=%s, updated={sentinel.AR: ' - 'sentinel.AR2})' % repr(set()), repr(self.invalid)) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/acme/acme/fields_test.py b/acme/acme/fields_test.py deleted file mode 100644 index 69dde8b89..000000000 --- a/acme/acme/fields_test.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Tests for acme.fields.""" -import datetime -import unittest - -import josepy as jose -import pytz - - -class FixedTest(unittest.TestCase): - """Tests for acme.fields.Fixed.""" - - def setUp(self): - from acme.fields import Fixed - self.field = Fixed('name', 'x') - - def test_decode(self): - self.assertEqual('x', self.field.decode('x')) - - def test_decode_bad(self): - self.assertRaises(jose.DeserializationError, self.field.decode, 'y') - - def test_encode(self): - self.assertEqual('x', self.field.encode('x')) - - def test_encode_override(self): - self.assertEqual('y', self.field.encode('y')) - - -class RFC3339FieldTest(unittest.TestCase): - """Tests for acme.fields.RFC3339Field.""" - - def setUp(self): - self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.utc) - self.encoded = '2015-03-27T00:00:00Z' - - def test_default_encoder(self): - from acme.fields import RFC3339Field - self.assertEqual( - self.encoded, RFC3339Field.default_encoder(self.decoded)) - - def test_default_encoder_naive_fails(self): - from acme.fields import RFC3339Field - self.assertRaises( - ValueError, RFC3339Field.default_encoder, datetime.datetime.now()) - - def test_default_decoder(self): - from acme.fields import RFC3339Field - self.assertEqual( - self.decoded, RFC3339Field.default_decoder(self.encoded)) - - def test_default_decoder_raises_deserialization_error(self): - from acme.fields import RFC3339Field - self.assertRaises( - jose.DeserializationError, RFC3339Field.default_decoder, '') - - -class ResourceTest(unittest.TestCase): - """Tests for acme.fields.Resource.""" - - def setUp(self): - from acme.fields import Resource - self.field = Resource('x') - - def test_decode_good(self): - self.assertEqual('x', self.field.decode('x')) - - def test_decode_wrong(self): - self.assertRaises(jose.DeserializationError, self.field.decode, 'y') - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/jose_test.py b/acme/acme/jose_test.py deleted file mode 100644 index 340624a4f..000000000 --- a/acme/acme/jose_test.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Tests for acme.jose shim.""" -import importlib -import unittest - -class JoseTest(unittest.TestCase): - """Tests for acme.jose shim.""" - - def _test_it(self, submodule, attribute): - if submodule: - acme_jose_path = 'acme.jose.' + submodule - josepy_path = 'josepy.' + submodule - else: - acme_jose_path = 'acme.jose' - josepy_path = 'josepy' - acme_jose_mod = importlib.import_module(acme_jose_path) - josepy_mod = importlib.import_module(josepy_path) - - self.assertIs(acme_jose_mod, josepy_mod) - self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) - - # We use the imports below with eval, but pylint doesn't - # understand that. - # pylint: disable=eval-used,unused-variable - import acme - import josepy - acme_jose_mod = eval(acme_jose_path) - josepy_mod = eval(josepy_path) - self.assertIs(acme_jose_mod, josepy_mod) - self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) - - def test_top_level(self): - self._test_it('', 'RS512') - - def test_submodules(self): - # This test ensures that the modules in josepy that were - # available at the time it was moved into its own package are - # available under acme.jose. Backwards compatibility with new - # modules or testing code is not maintained. - mods_and_attrs = [('b64', 'b64decode',), - ('errors', 'Error',), - ('interfaces', 'JSONDeSerializable',), - ('json_util', 'Field',), - ('jwa', 'HS256',), - ('jwk', 'JWK',), - ('jws', 'JWS',), - ('util', 'ImmutableMap',),] - - for mod, attr in mods_and_attrs: - self._test_it(mod, attr) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/jws_test.py b/acme/acme/jws_test.py deleted file mode 100644 index aa3ccb700..000000000 --- a/acme/acme/jws_test.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Tests for acme.jws.""" -import unittest - -import josepy as jose - -from acme import test_util - - -KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) - - -class HeaderTest(unittest.TestCase): - """Tests for acme.jws.Header.""" - - good_nonce = jose.encode_b64jose(b'foo') - wrong_nonce = u'F' - # Following just makes sure wrong_nonce is wrong - try: - jose.b64decode(wrong_nonce) - except (ValueError, TypeError): - assert True - else: - assert False # pragma: no cover - - def test_nonce_decoder(self): - from acme.jws import Header - nonce_field = Header._fields['nonce'] - - self.assertRaises( - jose.DeserializationError, nonce_field.decode, self.wrong_nonce) - self.assertEqual(b'foo', nonce_field.decode(self.good_nonce)) - - -class JWSTest(unittest.TestCase): - """Tests for acme.jws.JWS.""" - - def setUp(self): - self.privkey = KEY - self.pubkey = self.privkey.public_key() - self.nonce = jose.b64encode(b'Nonce') - self.url = 'hi' - self.kid = 'baaaaa' - - def test_kid_serialize(self): - from acme.jws import JWS - jws = JWS.sign(payload=b'foo', key=self.privkey, - alg=jose.RS256, nonce=self.nonce, - url=self.url, kid=self.kid) - self.assertEqual(jws.signature.combined.nonce, self.nonce) - self.assertEqual(jws.signature.combined.url, self.url) - self.assertEqual(jws.signature.combined.kid, self.kid) - self.assertEqual(jws.signature.combined.jwk, None) - # TODO: check that nonce is in protected header - - self.assertEqual(jws, JWS.from_json(jws.to_json())) - - def test_jwk_serialize(self): - from acme.jws import JWS - jws = JWS.sign(payload=b'foo', key=self.privkey, - alg=jose.RS256, nonce=self.nonce, - url=self.url) - self.assertEqual(jws.signature.combined.kid, None) - self.assertEqual(jws.signature.combined.jwk, self.pubkey) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/magic_typing_test.py b/acme/acme/magic_typing_test.py deleted file mode 100644 index 23dfe3367..000000000 --- a/acme/acme/magic_typing_test.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Tests for acme.magic_typing.""" -import sys -import unittest - -import mock - - -class MagicTypingTest(unittest.TestCase): - """Tests for acme.magic_typing.""" - def test_import_success(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - typing_class_mock = mock.MagicMock() - text_mock = mock.MagicMock() - typing_class_mock.Text = text_mock - sys.modules['typing'] = typing_class_mock - if 'acme.magic_typing' in sys.modules: - del sys.modules['acme.magic_typing'] # pragma: no cover - from acme.magic_typing import Text # pylint: disable=no-name-in-module - self.assertEqual(Text, text_mock) - del sys.modules['acme.magic_typing'] - sys.modules['typing'] = temp_typing - - def test_import_failure(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - sys.modules['typing'] = None - if 'acme.magic_typing' in sys.modules: - del sys.modules['acme.magic_typing'] # pragma: no cover - from acme.magic_typing import Text # pylint: disable=no-name-in-module - self.assertTrue(Text is None) - del sys.modules['acme.magic_typing'] - sys.modules['typing'] = temp_typing - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py deleted file mode 100644 index 7efaaa1a3..000000000 --- a/acme/acme/messages_test.py +++ /dev/null @@ -1,476 +0,0 @@ -"""Tests for acme.messages.""" -import unittest - -import josepy as jose -import mock - -from acme import challenges -from acme import test_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - - -CERT = test_util.load_comparable_cert('cert.der') -CSR = test_util.load_comparable_csr('csr.der') -KEY = test_util.load_rsa_private_key('rsa512_key.pem') - - -class ErrorTest(unittest.TestCase): - """Tests for acme.messages.Error.""" - - def setUp(self): - from acme.messages import Error, ERROR_PREFIX - self.error = Error( - detail='foo', typ=ERROR_PREFIX + 'malformed', title='title') - self.jobj = { - 'detail': 'foo', - 'title': 'some title', - 'type': ERROR_PREFIX + 'malformed', - } - self.error_custom = Error(typ='custom', detail='bar') - self.empty_error = Error() - self.jobj_custom = {'type': 'custom', 'detail': 'bar'} - - def test_default_typ(self): - from acme.messages import Error - self.assertEqual(Error().typ, 'about:blank') - - def test_from_json_empty(self): - from acme.messages import Error - self.assertEqual(Error(), Error.from_json('{}')) - - def test_from_json_hashable(self): - from acme.messages import Error - hash(Error.from_json(self.error.to_json())) - - def test_description(self): - self.assertEqual( - 'The request message was malformed', self.error.description) - self.assertTrue(self.error_custom.description is None) - - def test_code(self): - from acme.messages import Error - self.assertEqual('malformed', self.error.code) - self.assertEqual(None, self.error_custom.code) - self.assertEqual(None, Error().code) - - def test_is_acme_error(self): - from acme.messages import is_acme_error - self.assertTrue(is_acme_error(self.error)) - self.assertFalse(is_acme_error(self.error_custom)) - self.assertFalse(is_acme_error(self.empty_error)) - self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}")) - - def test_unicode_error(self): - from acme.messages import Error, ERROR_PREFIX, is_acme_error - arabic_error = Error( - detail=u'\u0639\u062f\u0627\u0644\u0629', typ=ERROR_PREFIX + 'malformed', - title='title') - self.assertTrue(is_acme_error(arabic_error)) - - def test_with_code(self): - from acme.messages import Error, is_acme_error - self.assertTrue(is_acme_error(Error.with_code('badCSR'))) - self.assertRaises(ValueError, Error.with_code, 'not an ACME error code') - - def test_str(self): - self.assertEqual( - str(self.error), - u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}" - .format(self.error)) - - -class ConstantTest(unittest.TestCase): - """Tests for acme.messages._Constant.""" - - def setUp(self): - from acme.messages import _Constant - - class MockConstant(_Constant): # pylint: disable=missing-docstring - POSSIBLE_NAMES = {} # type: Dict - - self.MockConstant = MockConstant # pylint: disable=invalid-name - self.const_a = MockConstant('a') - self.const_b = MockConstant('b') - - def test_to_partial_json(self): - self.assertEqual('a', self.const_a.to_partial_json()) - self.assertEqual('b', self.const_b.to_partial_json()) - - def test_from_json(self): - self.assertEqual(self.const_a, self.MockConstant.from_json('a')) - self.assertRaises( - jose.DeserializationError, self.MockConstant.from_json, 'c') - - def test_from_json_hashable(self): - hash(self.MockConstant.from_json('a')) - - def test_repr(self): - self.assertEqual('MockConstant(a)', repr(self.const_a)) - self.assertEqual('MockConstant(b)', repr(self.const_b)) - - def test_equality(self): - const_a_prime = self.MockConstant('a') - self.assertFalse(self.const_a == self.const_b) - self.assertTrue(self.const_a == const_a_prime) - - self.assertTrue(self.const_a != self.const_b) - self.assertFalse(self.const_a != const_a_prime) - - -class DirectoryTest(unittest.TestCase): - """Tests for acme.messages.Directory.""" - - def setUp(self): - from acme.messages import Directory - self.dir = Directory({ - 'new-reg': 'reg', - mock.MagicMock(resource_type='new-cert'): 'cert', - 'meta': Directory.Meta( - terms_of_service='https://example.com/acme/terms', - website='https://www.example.com/', - caa_identities=['example.com'], - ), - }) - - def test_init_wrong_key_value_success(self): # pylint: disable=no-self-use - from acme.messages import Directory - Directory({'foo': 'bar'}) - - def test_getitem(self): - self.assertEqual('reg', self.dir['new-reg']) - from acme.messages import NewRegistration - self.assertEqual('reg', self.dir[NewRegistration]) - self.assertEqual('reg', self.dir[NewRegistration()]) - - def test_getitem_fails_with_key_error(self): - self.assertRaises(KeyError, self.dir.__getitem__, 'foo') - - def test_getattr(self): - self.assertEqual('reg', self.dir.new_reg) - - def test_getattr_fails_with_attribute_error(self): - self.assertRaises(AttributeError, self.dir.__getattr__, 'foo') - - def test_to_json(self): - self.assertEqual(self.dir.to_json(), { - 'new-reg': 'reg', - 'new-cert': 'cert', - 'meta': { - 'terms-of-service': 'https://example.com/acme/terms', - 'website': 'https://www.example.com/', - 'caaIdentities': ['example.com'], - }, - }) - - def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use - from acme.messages import Directory - Directory.from_json({'foo': 'bar'}) - - def test_iter_meta(self): - result = False - for k in self.dir.meta: - if k == 'terms_of_service': - result = self.dir.meta[k] == 'https://example.com/acme/terms' - self.assertTrue(result) - - -class ExternalAccountBindingTest(unittest.TestCase): - def setUp(self): - from acme.messages import Directory - self.key = jose.jwk.JWKRSA(key=KEY.public_key()) - self.kid = "kid-for-testing" - self.hmac_key = "hmac-key-for-testing" - self.dir = Directory({ - 'newAccount': 'http://url/acme/new-account', - }) - - def test_from_data(self): - from acme.messages import ExternalAccountBinding - eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir) - - self.assertEqual(len(eab), 3) - self.assertEqual(sorted(eab.keys()), sorted(['protected', 'payload', 'signature'])) - - -class RegistrationTest(unittest.TestCase): - """Tests for acme.messages.Registration.""" - - def setUp(self): - key = jose.jwk.JWKRSA(key=KEY.public_key()) - contact = ( - 'mailto:admin@foo.com', - 'tel:1234', - ) - agreement = 'https://letsencrypt.org/terms' - - from acme.messages import Registration - self.reg = Registration(key=key, contact=contact, agreement=agreement) - self.reg_none = Registration() - - self.jobj_to = { - 'contact': contact, - 'agreement': agreement, - 'key': key, - } - self.jobj_from = self.jobj_to.copy() - self.jobj_from['key'] = key.to_json() - - def test_from_data(self): - from acme.messages import Registration - reg = Registration.from_data(phone='1234', email='admin@foo.com') - self.assertEqual(reg.contact, ( - 'tel:1234', - 'mailto:admin@foo.com', - )) - - def test_new_registration_from_data_with_eab(self): - from acme.messages import NewRegistration, ExternalAccountBinding, Directory - key = jose.jwk.JWKRSA(key=KEY.public_key()) - kid = "kid-for-testing" - hmac_key = "hmac-key-for-testing" - directory = Directory({ - 'newAccount': 'http://url/acme/new-account', - }) - eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory) - reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab) - self.assertEqual(reg.contact, ( - 'mailto:admin@foo.com', - )) - self.assertEqual(sorted(reg.external_account_binding.keys()), - sorted(['protected', 'payload', 'signature'])) - - def test_phones(self): - self.assertEqual(('1234',), self.reg.phones) - - def test_emails(self): - self.assertEqual(('admin@foo.com',), self.reg.emails) - - def test_to_partial_json(self): - self.assertEqual(self.jobj_to, self.reg.to_partial_json()) - - def test_from_json(self): - from acme.messages import Registration - self.assertEqual(self.reg, Registration.from_json(self.jobj_from)) - - def test_from_json_hashable(self): - from acme.messages import Registration - hash(Registration.from_json(self.jobj_from)) - - -class UpdateRegistrationTest(unittest.TestCase): - """Tests for acme.messages.UpdateRegistration.""" - - def test_empty(self): - from acme.messages import UpdateRegistration - jstring = '{"resource": "reg"}' - self.assertEqual(jstring, UpdateRegistration().json_dumps()) - self.assertEqual( - UpdateRegistration(), UpdateRegistration.json_loads(jstring)) - - -class RegistrationResourceTest(unittest.TestCase): - """Tests for acme.messages.RegistrationResource.""" - - def setUp(self): - from acme.messages import RegistrationResource - self.regr = RegistrationResource( - body=mock.sentinel.body, uri=mock.sentinel.uri, - terms_of_service=mock.sentinel.terms_of_service) - - def test_to_partial_json(self): - self.assertEqual(self.regr.to_json(), { - 'body': mock.sentinel.body, - 'uri': mock.sentinel.uri, - 'terms_of_service': mock.sentinel.terms_of_service, - }) - - -class ChallengeResourceTest(unittest.TestCase): - """Tests for acme.messages.ChallengeResource.""" - - def test_uri(self): - from acme.messages import ChallengeResource - self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock( - uri='http://challb'), authzr_uri='http://authz').uri) - - -class ChallengeBodyTest(unittest.TestCase): - """Tests for acme.messages.ChallengeBody.""" - - def setUp(self): - self.chall = challenges.DNS(token=jose.b64decode( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) - - from acme.messages import ChallengeBody - from acme.messages import Error - from acme.messages import STATUS_INVALID - self.status = STATUS_INVALID - error = Error(typ='urn:ietf:params:acme:error:serverInternal', - detail='Unable to communicate with DNS server') - self.challb = ChallengeBody( - uri='http://challb', chall=self.chall, status=self.status, - error=error) - - self.jobj_to = { - 'uri': 'http://challb', - 'status': self.status, - 'type': 'dns', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - 'error': error, - } - self.jobj_from = self.jobj_to.copy() - self.jobj_from['status'] = 'invalid' - self.jobj_from['error'] = { - 'type': 'urn:ietf:params:acme:error:serverInternal', - 'detail': 'Unable to communicate with DNS server', - } - - def test_encode(self): - self.assertEqual(self.challb.encode('uri'), self.challb.uri) - - def test_to_partial_json(self): - self.assertEqual(self.jobj_to, self.challb.to_partial_json()) - - def test_from_json(self): - from acme.messages import ChallengeBody - self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from)) - - def test_from_json_hashable(self): - from acme.messages import ChallengeBody - hash(ChallengeBody.from_json(self.jobj_from)) - - def test_proxy(self): - self.assertEqual(jose.b64decode( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'), self.challb.token) - - -class AuthorizationTest(unittest.TestCase): - """Tests for acme.messages.Authorization.""" - - def setUp(self): - from acme.messages import ChallengeBody - from acme.messages import STATUS_VALID - - self.challbs = ( - ChallengeBody( - uri='http://challb1', status=STATUS_VALID, - chall=challenges.HTTP01(token=b'IlirfxKKXAsHtmzK29Pj8A')), - ChallengeBody(uri='http://challb2', status=STATUS_VALID, - chall=challenges.DNS( - token=b'DGyRejmCefe7v4NfDGDKfA')), - ) - combinations = ((0,), (1,)) - - from acme.messages import Authorization - from acme.messages import Identifier - from acme.messages import IDENTIFIER_FQDN - identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com') - self.authz = Authorization( - identifier=identifier, combinations=combinations, - challenges=self.challbs) - - self.jobj_from = { - 'identifier': identifier.to_json(), - 'challenges': [challb.to_json() for challb in self.challbs], - 'combinations': combinations, - } - - def test_from_json(self): - from acme.messages import Authorization - Authorization.from_json(self.jobj_from) - - def test_from_json_hashable(self): - from acme.messages import Authorization - hash(Authorization.from_json(self.jobj_from)) - - def test_resolved_combinations(self): - self.assertEqual(self.authz.resolved_combinations, ( - (self.challbs[0],), - (self.challbs[1],), - )) - - -class AuthorizationResourceTest(unittest.TestCase): - """Tests for acme.messages.AuthorizationResource.""" - - def test_json_de_serializable(self): - from acme.messages import AuthorizationResource - authzr = AuthorizationResource( - uri=mock.sentinel.uri, - body=mock.sentinel.body) - self.assertTrue(isinstance(authzr, jose.JSONDeSerializable)) - - -class CertificateRequestTest(unittest.TestCase): - """Tests for acme.messages.CertificateRequest.""" - - def setUp(self): - from acme.messages import CertificateRequest - self.req = CertificateRequest(csr=CSR) - - def test_json_de_serializable(self): - self.assertTrue(isinstance(self.req, jose.JSONDeSerializable)) - from acme.messages import CertificateRequest - self.assertEqual( - self.req, CertificateRequest.from_json(self.req.to_json())) - - -class CertificateResourceTest(unittest.TestCase): - """Tests for acme.messages.CertificateResourceTest.""" - - def setUp(self): - from acme.messages import CertificateResource - self.certr = CertificateResource( - body=CERT, uri=mock.sentinel.uri, authzrs=(), - cert_chain_uri=mock.sentinel.cert_chain_uri) - - def test_json_de_serializable(self): - self.assertTrue(isinstance(self.certr, jose.JSONDeSerializable)) - from acme.messages import CertificateResource - self.assertEqual( - self.certr, CertificateResource.from_json(self.certr.to_json())) - - -class RevocationTest(unittest.TestCase): - """Tests for acme.messages.RevocationTest.""" - - def setUp(self): - from acme.messages import Revocation - self.rev = Revocation(certificate=CERT) - - def test_from_json_hashable(self): - from acme.messages import Revocation - hash(Revocation.from_json(self.rev.to_json())) - - -class OrderResourceTest(unittest.TestCase): - """Tests for acme.messages.OrderResource.""" - - def setUp(self): - from acme.messages import OrderResource - self.regr = OrderResource( - body=mock.sentinel.body, uri=mock.sentinel.uri) - - def test_to_partial_json(self): - self.assertEqual(self.regr.to_json(), { - 'body': mock.sentinel.body, - 'uri': mock.sentinel.uri, - 'authorizations': None, - }) - -class NewOrderTest(unittest.TestCase): - """Tests for acme.messages.NewOrder.""" - - def setUp(self): - from acme.messages import NewOrder - self.reg = NewOrder( - identifiers=mock.sentinel.identifiers) - - def test_to_partial_json(self): - self.assertEqual(self.reg.to_json(), { - 'identifiers': mock.sentinel.identifiers, - }) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py deleted file mode 100644 index 9f9249b07..000000000 --- a/acme/acme/standalone_test.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Tests for acme.standalone.""" -import socket -import threading -import unittest - -from six.moves import http_client # pylint: disable=import-error -from six.moves import socketserver # type: ignore # pylint: disable=import-error - -import josepy as jose -import mock -import requests - -from acme import challenges -from acme import test_util -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module - - -class TLSServerTest(unittest.TestCase): - """Tests for acme.standalone.TLSServer.""" - - - def test_bind(self): # pylint: disable=no-self-use - from acme.standalone import TLSServer - server = TLSServer( - ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True) - server.server_close() - - def test_ipv6(self): - if socket.has_ipv6: - from acme.standalone import TLSServer - server = TLSServer( - ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True, ipv6=True) - server.server_close() - - -class HTTP01ServerTest(unittest.TestCase): - """Tests for acme.standalone.HTTP01Server.""" - - - def setUp(self): - self.account_key = jose.JWK.load( - test_util.load_vector('rsa1024_key.pem')) - self.resources = set() # type: Set - - from acme.standalone import HTTP01Server - self.server = HTTP01Server(('', 0), resources=self.resources) - - self.port = self.server.socket.getsockname()[1] - self.thread = threading.Thread(target=self.server.serve_forever) - self.thread.start() - - def tearDown(self): - self.server.shutdown() - self.thread.join() - - def test_index(self): - response = requests.get( - 'http://localhost:{0}'.format(self.port), verify=False) - self.assertEqual( - response.text, 'ACME client standalone challenge solver') - self.assertTrue(response.ok) - - def test_404(self): - response = requests.get( - 'http://localhost:{0}/foo'.format(self.port), verify=False) - self.assertEqual(response.status_code, http_client.NOT_FOUND) - - def _test_http01(self, add): - chall = challenges.HTTP01(token=(b'x' * 16)) - response, validation = chall.response_and_validation(self.account_key) - - from acme.standalone import HTTP01RequestHandler - resource = HTTP01RequestHandler.HTTP01Resource( - chall=chall, response=response, validation=validation) - if add: - self.resources.add(resource) - return resource.response.simple_verify( - resource.chall, 'localhost', self.account_key.public_key(), - port=self.port) - - def test_http01_found(self): - self.assertTrue(self._test_http01(add=True)) - - def test_http01_not_found(self): - self.assertFalse(self._test_http01(add=False)) - - -class BaseDualNetworkedServersTest(unittest.TestCase): - """Test for acme.standalone.BaseDualNetworkedServers.""" - - - class SingleProtocolServer(socketserver.TCPServer): - """Server that only serves on a single protocol. FreeBSD has this behavior for AF_INET6.""" - def __init__(self, *args, **kwargs): - ipv6 = kwargs.pop("ipv6", False) - if ipv6: - self.address_family = socket.AF_INET6 - kwargs["bind_and_activate"] = False - else: - self.address_family = socket.AF_INET - socketserver.TCPServer.__init__(self, *args, **kwargs) - if ipv6: - # NB: On Windows, socket.IPPROTO_IPV6 constant may be missing. - # We use the corresponding value (41) instead. - level = getattr(socket, "IPPROTO_IPV6", 41) - self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1) - try: - self.server_bind() - self.server_activate() - except: - self.server_close() - raise - - @mock.patch("socket.socket.bind") - def test_fail_to_bind(self, mock_bind): - mock_bind.side_effect = socket.error - from acme.standalone import BaseDualNetworkedServers - self.assertRaises(socket.error, BaseDualNetworkedServers, - BaseDualNetworkedServersTest.SingleProtocolServer, - ('', 0), - socketserver.BaseRequestHandler) - - def test_ports_equal(self): - from acme.standalone import BaseDualNetworkedServers - servers = BaseDualNetworkedServers( - BaseDualNetworkedServersTest.SingleProtocolServer, - ('', 0), - socketserver.BaseRequestHandler) - socknames = servers.getsocknames() - prev_port = None - # assert ports are equal - for sockname in socknames: - port = sockname[1] - if prev_port: - self.assertEqual(prev_port, port) - prev_port = port - - -class HTTP01DualNetworkedServersTest(unittest.TestCase): - """Tests for acme.standalone.HTTP01DualNetworkedServers.""" - - - def setUp(self): - self.account_key = jose.JWK.load( - test_util.load_vector('rsa1024_key.pem')) - self.resources = set() # type: Set - - from acme.standalone import HTTP01DualNetworkedServers - self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources) - - self.port = self.servers.getsocknames()[0][1] - self.servers.serve_forever() - - def tearDown(self): - self.servers.shutdown_and_server_close() - - def test_index(self): - response = requests.get( - 'http://localhost:{0}'.format(self.port), verify=False) - self.assertEqual( - response.text, 'ACME client standalone challenge solver') - self.assertTrue(response.ok) - - def test_404(self): - response = requests.get( - 'http://localhost:{0}/foo'.format(self.port), verify=False) - self.assertEqual(response.status_code, http_client.NOT_FOUND) - - def _test_http01(self, add): - chall = challenges.HTTP01(token=(b'x' * 16)) - response, validation = chall.response_and_validation(self.account_key) - - from acme.standalone import HTTP01RequestHandler - resource = HTTP01RequestHandler.HTTP01Resource( - chall=chall, response=response, validation=validation) - if add: - self.resources.add(resource) - return resource.response.simple_verify( - resource.chall, 'localhost', self.account_key.public_key(), - port=self.port) - - def test_http01_found(self): - self.assertTrue(self._test_http01(add=True)) - - def test_http01_not_found(self): - self.assertFalse(self._test_http01(add=False)) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/acme/acme/test_util.py b/acme/acme/test_util.py deleted file mode 100644 index 6737bff4e..000000000 --- a/acme/acme/test_util.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Test utilities. - -.. warning:: This module is not part of the public API. - -""" -import os -import pkg_resources - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -import josepy as jose -from OpenSSL import crypto - - -def load_vector(*names): - """Load contents of a test vector.""" - # luckily, resource_string opens file in binary mode - return pkg_resources.resource_string( - __name__, os.path.join('testdata', *names)) - - -def _guess_loader(filename, loader_pem, loader_der): - _, ext = os.path.splitext(filename) - if ext.lower() == '.pem': - return loader_pem - elif ext.lower() == '.der': - return loader_der - else: # pragma: no cover - raise ValueError("Loader could not be recognized based on extension") - - -def load_cert(*names): - """Load certificate.""" - loader = _guess_loader( - names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) - return crypto.load_certificate(loader, load_vector(*names)) - - -def load_comparable_cert(*names): - """Load ComparableX509 cert.""" - return jose.ComparableX509(load_cert(*names)) - - -def load_csr(*names): - """Load certificate request.""" - loader = _guess_loader( - names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) - return crypto.load_certificate_request(loader, load_vector(*names)) - - -def load_comparable_csr(*names): - """Load ComparableX509 certificate request.""" - return jose.ComparableX509(load_csr(*names)) - - -def load_rsa_private_key(*names): - """Load RSA private key.""" - loader = _guess_loader(names[-1], serialization.load_pem_private_key, - serialization.load_der_private_key) - return jose.ComparableRSAKey(loader( - load_vector(*names), password=None, backend=default_backend())) - - -def load_pyopenssl_private_key(*names): - """Load pyOpenSSL private key.""" - loader = _guess_loader( - names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) - return crypto.load_privatekey(loader, load_vector(*names)) diff --git a/acme/acme/testdata/README b/acme/acme/testdata/README deleted file mode 100644 index dfe3f5405..000000000 --- a/acme/acme/testdata/README +++ /dev/null @@ -1,15 +0,0 @@ -In order for acme.test_util._guess_loader to work properly, make sure -to use appropriate extension for vector filenames: .pem for PEM and -.der for DER. - -The following command has been used to generate test keys: - - for x in 256 512 1024 2048; do openssl genrsa -out rsa${k}_key.pem $k; done - -and for the CSR: - - openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der - -and for the certificate: - - openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der diff --git a/acme/acme/testdata/cert-100sans.pem b/acme/acme/testdata/cert-100sans.pem deleted file mode 100644 index 3fdc9404f..000000000 --- a/acme/acme/testdata/cert-100sans.pem +++ /dev/null @@ -1,44 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV -BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv -bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X -DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU -BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp -ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B -AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 -rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T -BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t -ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt -cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j -b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN -ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh -bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs -ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx -LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv -bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN -ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh -bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs -ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3 -LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv -bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN -ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh -bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs -ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz -LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv -bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN -ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh -bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs -ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5 -LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv -bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN -ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh -bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs -ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1 -LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv -bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN -ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh -bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs -ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN -AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ -XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG ------END CERTIFICATE----- diff --git a/acme/acme/testdata/cert-idnsans.pem b/acme/acme/testdata/cert-idnsans.pem deleted file mode 100644 index 932649692..000000000 --- a/acme/acme/testdata/cert-idnsans.pem +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV -BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv -bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X -DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU -BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp -ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B -AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 -rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T -BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I -z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g -z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z -z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM -2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf -2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3 -2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi -2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi -2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs -aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN -247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p -bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4 -27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt -4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh -oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh -oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh -oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm -4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2 -LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT -TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G ------END CERTIFICATE----- diff --git a/acme/acme/testdata/cert-nocn.der b/acme/acme/testdata/cert-nocn.der deleted file mode 100644 index 59da83ccc..000000000 Binary files a/acme/acme/testdata/cert-nocn.der and /dev/null differ diff --git a/acme/acme/testdata/cert-san.pem b/acme/acme/testdata/cert-san.pem deleted file mode 100644 index dcb835994..000000000 --- a/acme/acme/testdata/cert-san.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx -ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM -IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 -YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG -A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix -KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS -BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR -7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c -+pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt -cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF -nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 -RDjyGMKy5ZgM2w== ------END CERTIFICATE----- diff --git a/acme/acme/testdata/cert.der b/acme/acme/testdata/cert.der deleted file mode 100644 index ab231982f..000000000 Binary files a/acme/acme/testdata/cert.der and /dev/null differ diff --git a/acme/acme/testdata/cert.pem b/acme/acme/testdata/cert.pem deleted file mode 100644 index 96c55cbf4..000000000 --- a/acme/acme/testdata/cert.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx -ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM -IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 -YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG -A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix -KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS -BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR -7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c -+pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll -vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn -B/o= ------END CERTIFICATE----- diff --git a/acme/acme/testdata/critical-san.pem b/acme/acme/testdata/critical-san.pem deleted file mode 100644 index 7aec8ab1c..000000000 --- a/acme/acme/testdata/critical-san.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIErTCCA5WgAwIBAgIKETb7VQAAAAAdGTANBgkqhkiG9w0BAQsFADCBkTELMAkG -A1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5 -MRUwEwYDVQQKEwxWZW5hZmksIEluYy4xHzAdBgNVBAsTFkRlbW9uc3RyYXRpb24g -U2VydmljZXMxIjAgBgNVBAMTGVZlbmFmaSBFeGFtcGxlIElzc3VpbmcgQ0EwHhcN -MTcwNzEwMjMxNjA1WhcNMTcwODA5MjMxNjA1WjAAMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEA7CU5qRIzCs9hCRiSUvLZ8r81l4zIYbx1V1vZz6x1cS4M -0keNfFJ1wB+zuvx80KaMYkWPYlg4Rsm9Ok3ZapakXDlaWtrfg78lxtHuPw1o7AYV -EXDwwPkNugLMJfYw5hWYSr8PCLcOJoY00YQ0fJ44L+kVsUyGjN4UTRRZmOh/yNVU -0W12dTCz4X7BAW01OuY6SxxwewnW3sBEep+APfr2jd/oIx7fgZmVB8aRCDPj4AFl -XINWIwxmptOwnKPbwLN/vhCvJRUkO6rA8lpYwQkedFf6fHhqi2Sq/NCEOg4RvMCF -fKbMpncOXxz+f4/i43SVLrPz/UyhjNbKGJZ+zFrQowIDAQABo4IBlTCCAZEwPgYD -VR0RAQH/BDQwMoIbY2hpY2Fnby1jdWJzLnZlbmFmaS5leGFtcGxlghNjdWJzLnZl -bmFmaS5leGFtcGxlMB0GA1UdDgQWBBTgKZXVSFNyPHHtO/phtIALPcCF5DAfBgNV -HSMEGDAWgBT/JJ6Wei/pzf+9DRHuv6Wgdk2HsjBSBgNVHR8ESzBJMEegRaBDhkFo -dHRwOi8vcGtpLnZlbmFmaS5leGFtcGxlL2NybC9WZW5hZmklMjBFeGFtcGxlJTIw -SXNzdWluZyUyMENBLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0 -dHA6Ly9wa2kudmVuYWZpLmV4YW1wbGUvb2NzcDAOBgNVHQ8BAf8EBAMCBaAwPQYJ -KwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhIDLGYTvsSSEnZ8ehvD5UofP4hMEgobv -DIGy4mcCAWQCAQIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGwYJKwYBBAGCNxUKBA4w -DDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEA3YW4t1AzxEn384OqdU6L -ny8XkMhWpRM0W0Z9ZC3gRZKbVUu49nG/KB5hbVn/de33zdX9HOZJKc0vXzkGZQUs -OUCCsKX4VKzV5naGXOuGRbvV4CJh5P0kPlDzyb5t312S49nJdcdBf0Y/uL5Qzhst -bXy8qNfFNG3SIKKRAUpqE9OVIl+F+JBwexa+v/4dFtUOqMipfXxB3TaxnDqvU1dS -yO34ZTvIMGXJIZ5nn/d/LNc3N3vBg2SHkMpladqw0Hr7mL0bFOe0b+lJgkDP06Be -n08fikhz1j2AW4/ZHa9w4DUz7J21+RtHMhh+Vd1On0EAeZ563svDe7Z+yrg6zOVv -KA== ------END CERTIFICATE----- \ No newline at end of file diff --git a/acme/acme/testdata/csr-100sans.pem b/acme/acme/testdata/csr-100sans.pem deleted file mode 100644 index 199814126..000000000 --- a/acme/acme/testdata/csr-100sans.pem +++ /dev/null @@ -1,41 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz -Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG -A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt -H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 -lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud -EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv -bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh -bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu -Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C -DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4 -YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w -bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy -MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j -b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C -DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4 -YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w -bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz -Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j -b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C -DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4 -YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w -bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1 -My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j -b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C -DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4 -YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w -bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2 -OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j -b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C -DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4 -YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w -bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4 -NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j -b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C -DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4 -YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w -bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3 -DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo -duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg== ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-6sans.pem b/acme/acme/testdata/csr-6sans.pem deleted file mode 100644 index 8f6b52bd7..000000000 --- a/acme/acme/testdata/csr-6sans.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw -EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0 -9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG -9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL -ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t -ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd -k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv -IvzVBz/nD11drfz/RNuX ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-idnsans.pem b/acme/acme/testdata/csr-idnsans.pem deleted file mode 100644 index d6e91a420..000000000 --- a/acme/acme/testdata/csr-idnsans.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz -Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG -A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt -H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 -lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud -EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP -iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P -oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP -s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ -jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z -n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ -t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC -YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa -otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh -bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb -jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu -aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb -uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg -reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5 -4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ -4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ -4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh -puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh -ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr -dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng== ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-nosans.pem b/acme/acme/testdata/csr-nosans.pem deleted file mode 100644 index 813db67b0..000000000 --- a/acme/acme/testdata/csr-nosans.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh -MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt -cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn -BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz -AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo -wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA= ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-san.pem b/acme/acme/testdata/csr-san.pem deleted file mode 100644 index a7128e35c..000000000 --- a/acme/acme/testdata/csr-san.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw -EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f -p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN -AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t -MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy -tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A== ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr.der b/acme/acme/testdata/csr.der deleted file mode 100644 index d43ac85a1..000000000 Binary files a/acme/acme/testdata/csr.der and /dev/null differ diff --git a/acme/acme/testdata/csr.pem b/acme/acme/testdata/csr.pem deleted file mode 100644 index b6818e39d..000000000 --- a/acme/acme/testdata/csr.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw -EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f -p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoCkwJwYJKoZIhvcN -AQkOMRowGDAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAANB -AHJH/O6BtC9aGzEVCMGOZ7z9iIRHWSzr9x/bOzn7hLwsbXPAgO1QxEwL+X+4g20G -n9XBE1N9W6HCIEut2d8wACg= ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/dsa512_key.pem b/acme/acme/testdata/dsa512_key.pem deleted file mode 100644 index 78e164712..000000000 --- a/acme/acme/testdata/dsa512_key.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN DSA PARAMETERS----- -MIGdAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqfn6GC -OixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSPAkEA -qfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xmrfvl -41pgNJpgu99YOYqPpS0g7A== ------END DSA PARAMETERS----- ------BEGIN DSA PRIVATE KEY----- -MIH5AgEAAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqf -n6GCOixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSP -AkEAqfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xm -rfvl41pgNJpgu99YOYqPpS0g7AJATQ2LUzjGQSM6UljcPY5I2OD9THkUR9kH2tth -zZd70UoI9btrVaTizgqYShuok94glSQNK0H92JgUk3scJPaAkAIVAMDn61h6vrCE -mNv063So6E+eYaIN ------END DSA PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa1024_key.pem b/acme/acme/testdata/rsa1024_key.pem deleted file mode 100644 index de5339d03..000000000 --- a/acme/acme/testdata/rsa1024_key.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCaifO0fGlcAcjjcYEAPYcIL0Hf0KiNa9VCJ14RBdlZxLWRrVFi -4tdNCKSKqzKuKrrA8DWd4PHFD7UpLyRrPPXY6GozAyCT+5UFBClGJ2KyNKu+eU6/ -w4C1kpO4lpeXs8ptFc1lA9P8V1M/MkWzTE402nPNK0uUmZNo2tsFpGJUSQIDAQAB -AoGAFjLWxQhSAhtnhfRZ+XTdHrnbFpFchOQGgDgzdPKIJDLzefeRh0jacIBbUmgB -Ia+Vn/1hVkpnsEzvUvkonBbnoYWlYVQdpNTmrrew7SOztf8/1fYCsSkyDAvqGTXc -TmHM0PaLS+junoWcKOvQRVb0N3k+43OnBkr2b393Sx30qGECQQDNO2IBWOsYs8cB -CZQAZs8zBlbwBFZibqovqpLwXt9adBIsT9XzgagGbJMpzSuoHTUn3QqqJd9uHD8X -UTmmoh4NAkEAwMRauo+PlNj8W1lusflko52KL17+E5cmeOERM2xvhZNpO7d3/1ak -Co9dxVMicrYSh7jXbcXFNt3xNDTv6Dg8LQJAPuJwMDt/pc0IMCAwMkNOP7M0lkyt -73E7QmnAplhblcq0+tDnnLpgsr84BHnyY4u3iuRm7SW3pXSQPGPOB2nrTQJANBXa -HgakWSe4KEal7ljgpITwzZPxOwHgV1EZALgP+hu2l3gfaFLUyDWstKCd8jjYEOwU -6YhCnWyiu+SB3lEzkQJBAJapJpfypFyY8kQNYlYILLBcPu5fmy3QUZKHJ4L3rIVJ -c2UTLMeBBgGFHT04CtWntmjwzSv+V6lwiCxKXsIUySc= ------END RSA PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa2048_cert.pem b/acme/acme/testdata/rsa2048_cert.pem deleted file mode 100644 index 3944cd1db..000000000 --- a/acme/acme/testdata/rsa2048_cert.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjjCCAnagAwIBAgIJALVG/VbBb5U7MA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNV -BAYTAkFVMQswCQYDVQQIDAJXQTEeMBwGA1UEBwwVVGhlIG1pZGRsZSBvZiBub3do -ZXJlMR8wHQYDVQQKDBZDZXJ0Ym90IFRlc3QgQ2VydHMgSW5jMCAXDTE2MTEyODIx -MzUzN1oYDzIyOTAwOTEzMjEzNTM3WjBbMQswCQYDVQQGEwJBVTELMAkGA1UECAwC -V0ExHjAcBgNVBAcMFVRoZSBtaWRkbGUgb2Ygbm93aGVyZTEfMB0GA1UECgwWQ2Vy -dGJvdCBUZXN0IENlcnRzIEluYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBANoVT1pdvRUUBOqvm7M2ebLEHV7higUH7qAGUZEkfP6W4YriYVY+IHrH1svN -PSa+oPTK7weDNmT11ehWnGyECIM9z2r2Hi9yVV0ycxh4hWQ4Nt8BAKZwCwaXpyWm -7Gj6m2EzpSN5Dd67g5YAQBrUUh1+RRbFi9c0Ls/6ZOExMvfg8kqt4c2sXCgH1IFn -xvvOjBYop7xh0x3L1Akyax0tw8qgQp/z5mkupmVDNJYPFmbzFPMNyDR61ed6QUTD -g7P4UAuFkejLLzFvz5YaO7vC+huaTuPhInAhpzqpr4yU97KIjos2/83Itu/Cv8U1 -RAeEeRTkh0WjUfltoem/5f8bIdsCAwEAAaNTMFEwHQYDVR0OBBYEFHy+bEYqwvFU -uQLTkIfQ36AM2DQiMB8GA1UdIwQYMBaAFHy+bEYqwvFUuQLTkIfQ36AM2DQiMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH3ANVzB59FcunZV/F8T -RiCD6/gV7Jc3CswU8N8tVjzMCg2jOdTFF9iYZzNNKQvG13o/n5LkQr/lkKRQkWTx -nkE5WZbR7vNqlzXgPa9NBiK5rPjgSt8azPW+Skct3Bj4B3PhTMSpoQ7PsUJ8UeV8 -kTNR5xrRLt6/mLfRJTXWXBM43GEZi8lL5q0nqz0tPGISADshHMo6ZlUu5Hvfp5v+ -aonpO4sVS9hGOVxjGNMXYApEUy4jid9jjAfEk6jeELJMbXGLy/botFgIJK/QPe6P -AfbdFgtg/qzG7Uy0A1iXXfWdgwmVrhCoGYYWCn4yWCAm894QKtdim87CHSDP0WUf -Esg= ------END CERTIFICATE----- diff --git a/acme/acme/testdata/rsa2048_key.pem b/acme/acme/testdata/rsa2048_key.pem deleted file mode 100644 index 5847aed55..000000000 --- a/acme/acme/testdata/rsa2048_key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDaFU9aXb0VFATq -r5uzNnmyxB1e4YoFB+6gBlGRJHz+luGK4mFWPiB6x9bLzT0mvqD0yu8HgzZk9dXo -VpxshAiDPc9q9h4vclVdMnMYeIVkODbfAQCmcAsGl6clpuxo+pthM6UjeQ3eu4OW -AEAa1FIdfkUWxYvXNC7P+mThMTL34PJKreHNrFwoB9SBZ8b7zowWKKe8YdMdy9QJ -MmsdLcPKoEKf8+ZpLqZlQzSWDxZm8xTzDcg0etXnekFEw4Oz+FALhZHoyy8xb8+W -Gju7wvobmk7j4SJwIac6qa+MlPeyiI6LNv/NyLbvwr/FNUQHhHkU5IdFo1H5baHp -v+X/GyHbAgMBAAECggEAURFe4C68XRuGAF+rN2Fmt+djK6QXlGswb1gp9hRkSpd3 -3BLvMAoENOAYnsX6l26Bkr3lQRurmrgv/iBEIaqrJ25QrmgzLFwKE4zvcAdNPsYO -z7MltLktwBOb1MlKVHPkUqvKFXeoikWWUqphKhgHNmN7900UALmrNTDVU0jgs3fB -o35o8d5SjoC52K4wCTjhPyjt4cdbfbziRs2qFhfGdawidRO1xLlDM4tTTW+5yWGK -lt0SwyvDVC6XWeNoT3nXyKjXWP7hcYqm0iS7ffL9YzEC2RXNGQUqeR50i9Y0rDdH -Vqcr+Rqio2ww68zbDWBpC/jU133BSoHuSE1wstxIkQKBgQDxlEr42WJfgdajbZ1a -hUIeLEgvhezLmD1hcYwZuQCLgizmY2ovvmeAH74koCDEsUUQunPYHsRla7wT3q1/ -IkR1KgJPwESpkQaKuAqxeEAkv7Gn8Lzcn22jCoRCfGA68wKJz2ECFZDc0RDvRrT/ -9GhiiGUoO47jv9ezrSDO1eu5/QKBgQDnGfYVMNLiA0fy4AxSyY2vdo7vruOFGpRP -n94gwxZ+0dQDWHzn3J4rHivxtcyd/MOZv4I8PtYK7tmmjYv1ngQ6sGl4p8bpUtwj -9++/B1CyB1W5/VPqMkd+Sj0dbejycME55+F6/r4basPXxBFFCfknjAlVvyvbBhUy -ftNpHxZGtwKBgChJM4t2LPqCW3nbgL8ks9b2SX9rVQbKt4m1dsifWmDpb3VoJMAb -f4UVRg8ziONkMIFOppzm3JeRNMcXflVSMJpdTA9in9CrN60QbfAUfpXiRc0cz1H3 -YEAtM8smlKGf/s9efu3rDMJWNv3AC9UXPAUae8wOypBeYKk8+NilQe89AoGAXEA3 -xFO+CqyGnwQixzVf0qf//NuSRQLMK1DEyc02gJ9gA4niKmgd11Zu8kjBClvo9MnG -wifPJ4Qa6+pa8UwHoinjoF9Q/rit2cnSMS5JXxegd+MRCU7SzS3zYXkLYSPzbhsL -Hh7sYmNnFA1XW3jUtZ2n6EusxPyTn5mS6MaZDNcCgYBelFKFjNIQ50NbOnm8DewK -jUd5OFKowKodlQVcHiF9CVbjvpN8ZPRcBSmqDU4kpT/rmcybVoL6Zfa/zWkw8+Oh -QxKb3BYf5vRUMd/RA+/t5KG4ZOIIYB3qoltAYlhVaINukL6cGVG1qvV/ntcsfsn6 -kmf1UgGFcKrJuXgwEtTVxw== ------END PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa256_key.pem b/acme/acme/testdata/rsa256_key.pem deleted file mode 100644 index 659274d1d..000000000 --- a/acme/acme/testdata/rsa256_key.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh -AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N -E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 -rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt ------END RSA PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa512_key.pem b/acme/acme/testdata/rsa512_key.pem deleted file mode 100644 index 610c8d315..000000000 --- a/acme/acme/testdata/rsa512_key.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 -vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn -elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc -mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp -Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj -8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq -6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ ------END RSA PRIVATE KEY----- diff --git a/acme/acme/util_test.py b/acme/acme/util_test.py deleted file mode 100644 index 00aa8b02d..000000000 --- a/acme/acme/util_test.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Tests for acme.util.""" -import unittest - - -class MapKeysTest(unittest.TestCase): - """Tests for acme.util.map_keys.""" - - def test_it(self): - from acme.util import map_keys - self.assertEqual({'a': 'b', 'c': 'd'}, - map_keys({'a': 'b', 'c': 'd'}, lambda key: key)) - self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1)) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/tests/challenges_test.py b/acme/tests/challenges_test.py new file mode 100644 index 000000000..819ba9261 --- /dev/null +++ b/acme/tests/challenges_test.py @@ -0,0 +1,419 @@ +"""Tests for acme.challenges.""" +import unittest + +import josepy as jose +import mock +import requests + +from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import + +import test_util + +CERT = test_util.load_comparable_cert('cert.pem') +KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem')) + + +class ChallengeTest(unittest.TestCase): + + def test_from_json_unrecognized(self): + from acme.challenges import Challenge + from acme.challenges import UnrecognizedChallenge + chall = UnrecognizedChallenge({"type": "foo"}) + # pylint: disable=no-member + self.assertEqual(chall, Challenge.from_json(chall.jobj)) + + +class UnrecognizedChallengeTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import UnrecognizedChallenge + self.jobj = {"type": "foo"} + self.chall = UnrecognizedChallenge(self.jobj) + + def test_to_partial_json(self): + self.assertEqual(self.jobj, self.chall.to_partial_json()) + + def test_from_json(self): + from acme.challenges import UnrecognizedChallenge + self.assertEqual( + self.chall, UnrecognizedChallenge.from_json(self.jobj)) + + +class KeyAuthorizationChallengeResponseTest(unittest.TestCase): + + def setUp(self): + def _encode(name): + assert name == "token" + return "foo" + self.chall = mock.Mock() + self.chall.encode.side_effect = _encode + + def test_verify_ok(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') + self.assertTrue(response.verify(self.chall, KEY.public_key())) + + def test_verify_wrong_token(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='bar.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') + self.assertFalse(response.verify(self.chall, KEY.public_key())) + + def test_verify_wrong_thumbprint(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxv') + self.assertFalse(response.verify(self.chall, KEY.public_key())) + + def test_verify_wrong_form(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='.foo.oKGqedy-b-acd5eoybm2f-' + 'NVFxvyOoET5CNy3xnv8WY') + self.assertFalse(response.verify(self.chall, KEY.public_key())) + + +class DNS01ResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS01Response + self.msg = DNS01Response(key_authorization=u'foo') + self.jmsg = { + 'resource': 'challenge', + 'type': 'dns-01', + 'keyAuthorization': u'foo', + } + + from acme.challenges import DNS01 + self.chall = DNS01(token=(b'x' * 16)) + self.response = self.chall.response(KEY) + + def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNS01Response + self.assertEqual(self.msg, DNS01Response.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import DNS01Response + hash(DNS01Response.from_json(self.jmsg)) + + def test_simple_verify_failure(self): + key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) + public_key = key2.public_key() + verified = self.response.simple_verify(self.chall, "local", public_key) + self.assertFalse(verified) + + def test_simple_verify_success(self): + public_key = KEY.public_key() + verified = self.response.simple_verify(self.chall, "local", public_key) + self.assertTrue(verified) + + +class DNS01Test(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS01 + self.msg = DNS01(token=jose.decode_b64jose( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) + self.jmsg = { + 'type': 'dns-01', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + } + + def test_validation_domain_name(self): + self.assertEqual('_acme-challenge.www.example.com', + self.msg.validation_domain_name('www.example.com')) + + def test_validation(self): + self.assertEqual( + "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk", + self.msg.validation(KEY)) + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNS01 + self.assertEqual(self.msg, DNS01.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import DNS01 + hash(DNS01.from_json(self.jmsg)) + + +class HTTP01ResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import HTTP01Response + self.msg = HTTP01Response(key_authorization=u'foo') + self.jmsg = { + 'resource': 'challenge', + 'type': 'http-01', + 'keyAuthorization': u'foo', + } + + from acme.challenges import HTTP01 + self.chall = HTTP01(token=(b'x' * 16)) + self.response = self.chall.response(KEY) + + def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import HTTP01Response + self.assertEqual( + self.msg, HTTP01Response.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import HTTP01Response + hash(HTTP01Response.from_json(self.jmsg)) + + def test_simple_verify_bad_key_authorization(self): + key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) + self.response.simple_verify(self.chall, "local", key2.public_key()) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_good_validation(self, mock_get): + validation = self.chall.validation(KEY) + mock_get.return_value = mock.MagicMock(text=validation) + self.assertTrue(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + mock_get.assert_called_once_with(self.chall.uri("local")) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_bad_validation(self, mock_get): + mock_get.return_value = mock.MagicMock(text="!") + self.assertFalse(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_whitespace_validation(self, mock_get): + from acme.challenges import HTTP01Response + mock_get.return_value = mock.MagicMock( + text=(self.chall.validation(KEY) + + HTTP01Response.WHITESPACE_CUTSET)) + self.assertTrue(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + mock_get.assert_called_once_with(self.chall.uri("local")) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_connection_error(self, mock_get): + mock_get.side_effect = requests.exceptions.RequestException + self.assertFalse(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_port(self, mock_get): + self.response.simple_verify( + self.chall, domain="local", + account_public_key=KEY.public_key(), port=8080) + self.assertEqual("local:8080", urllib_parse.urlparse( + mock_get.mock_calls[0][1][0]).netloc) + + +class HTTP01Test(unittest.TestCase): + + def setUp(self): + from acme.challenges import HTTP01 + self.msg = HTTP01( + token=jose.decode_b64jose( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) + self.jmsg = { + 'type': 'http-01', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + } + + def test_path(self): + self.assertEqual(self.msg.path, '/.well-known/acme-challenge/' + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA') + + def test_uri(self): + self.assertEqual( + 'http://example.com/.well-known/acme-challenge/' + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + self.msg.uri('example.com')) + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import HTTP01 + self.assertEqual(self.msg, HTTP01.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import HTTP01 + hash(HTTP01.from_json(self.jmsg)) + + def test_good_token(self): + self.assertTrue(self.msg.good_token) + self.assertFalse( + self.msg.update(token=b'..').good_token) + + +class TLSALPN01ResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import TLSALPN01Response + self.msg = TLSALPN01Response(key_authorization=u'foo') + self.jmsg = { + 'resource': 'challenge', + 'type': 'tls-alpn-01', + 'keyAuthorization': u'foo', + } + + from acme.challenges import TLSALPN01 + self.chall = TLSALPN01(token=(b'x' * 16)) + self.response = self.chall.response(KEY) + + def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import TLSALPN01Response + self.assertEqual(self.msg, TLSALPN01Response.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import TLSALPN01Response + hash(TLSALPN01Response.from_json(self.jmsg)) + + +class TLSALPN01Test(unittest.TestCase): + + def setUp(self): + from acme.challenges import TLSALPN01 + self.msg = TLSALPN01( + token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) + self.jmsg = { + 'type': 'tls-alpn-01', + 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', + } + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import TLSALPN01 + self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import TLSALPN01 + hash(TLSALPN01.from_json(self.jmsg)) + + def test_from_json_invalid_token_length(self): + from acme.challenges import TLSALPN01 + self.jmsg['token'] = jose.encode_b64jose(b'abcd') + self.assertRaises( + jose.DeserializationError, TLSALPN01.from_json, self.jmsg) + + def test_validation(self): + self.assertRaises(NotImplementedError, self.msg.validation, KEY) + + +class DNSTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS + self.msg = DNS(token=jose.b64decode( + b'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) + self.jmsg = { + 'type': 'dns', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + } + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNS + self.assertEqual(self.msg, DNS.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import DNS + hash(DNS.from_json(self.jmsg)) + + def test_gen_check_validation(self): + self.assertTrue(self.msg.check_validation( + self.msg.gen_validation(KEY), KEY.public_key())) + + def test_gen_check_validation_wrong_key(self): + key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem')) + self.assertFalse(self.msg.check_validation( + self.msg.gen_validation(KEY), key2.public_key())) + + def test_check_validation_wrong_payload(self): + validations = tuple( + jose.JWS.sign(payload=payload, alg=jose.RS256, key=KEY) + for payload in (b'', b'{}') + ) + for validation in validations: + self.assertFalse(self.msg.check_validation( + validation, KEY.public_key())) + + def test_check_validation_wrong_fields(self): + bad_validation = jose.JWS.sign( + payload=self.msg.update( + token=b'x' * 20).json_dumps().encode('utf-8'), + alg=jose.RS256, key=KEY) + self.assertFalse(self.msg.check_validation( + bad_validation, KEY.public_key())) + + def test_gen_response(self): + with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen: + mock_gen.return_value = mock.sentinel.validation + response = self.msg.gen_response(KEY) + from acme.challenges import DNSResponse + self.assertTrue(isinstance(response, DNSResponse)) + self.assertEqual(response.validation, mock.sentinel.validation) + + def test_validation_domain_name(self): + self.assertEqual( + '_acme-challenge.le.wtf', self.msg.validation_domain_name('le.wtf')) + + +class DNSResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS + self.chall = DNS(token=jose.b64decode( + b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA")) + self.validation = jose.JWS.sign( + payload=self.chall.json_dumps(sort_keys=True).encode(), + key=KEY, alg=jose.RS256) + + from acme.challenges import DNSResponse + self.msg = DNSResponse(validation=self.validation) + self.jmsg_to = { + 'resource': 'challenge', + 'type': 'dns', + 'validation': self.validation, + } + self.jmsg_from = { + 'resource': 'challenge', + 'type': 'dns', + 'validation': self.validation.to_json(), + } + + def test_to_partial_json(self): + self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNSResponse + self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg_from)) + + def test_from_json_hashable(self): + from acme.challenges import DNSResponse + hash(DNSResponse.from_json(self.jmsg_from)) + + def test_check_validation(self): + self.assertTrue( + self.msg.check_validation(self.chall, KEY.public_key())) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py new file mode 100644 index 000000000..22eb3fc45 --- /dev/null +++ b/acme/tests/client_test.py @@ -0,0 +1,1308 @@ +"""Tests for acme.client.""" +# pylint: disable=too-many-lines +import copy +import datetime +import json +import unittest + +from six.moves import http_client # pylint: disable=import-error + +import josepy as jose +import mock +import OpenSSL +import requests + +from acme import challenges +from acme import errors +from acme import jws as acme_jws +from acme import messages +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + +import messages_test +import test_util + +CERT_DER = test_util.load_vector('cert.der') +CERT_SAN_PEM = test_util.load_vector('cert-san.pem') +CSR_SAN_PEM = test_util.load_vector('csr-san.pem') +KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) +KEY2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) + +DIRECTORY_V1 = messages.Directory({ + messages.NewRegistration: + 'https://www.letsencrypt-demo.org/acme/new-reg', + messages.Revocation: + 'https://www.letsencrypt-demo.org/acme/revoke-cert', + messages.NewAuthorization: + 'https://www.letsencrypt-demo.org/acme/new-authz', + messages.CertificateRequest: + 'https://www.letsencrypt-demo.org/acme/new-cert', +}) + +DIRECTORY_V2 = messages.Directory({ + 'newAccount': 'https://www.letsencrypt-demo.org/acme/new-account', + 'newNonce': 'https://www.letsencrypt-demo.org/acme/new-nonce', + 'newOrder': 'https://www.letsencrypt-demo.org/acme/new-order', + 'revokeCert': 'https://www.letsencrypt-demo.org/acme/revoke-cert', +}) + + +class ClientTestBase(unittest.TestCase): + """Base for tests in acme.client.""" + + def setUp(self): + self.response = mock.MagicMock( + ok=True, status_code=http_client.OK, headers={}, links={}) + self.net = mock.MagicMock() + self.net.post.return_value = self.response + self.net.get.return_value = self.response + + self.identifier = messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='example.com') + + # Registration + self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212') + reg = messages.Registration( + contact=self.contact, key=KEY.public_key()) + the_arg = dict(reg) # type: Dict + self.new_reg = messages.NewRegistration(**the_arg) + self.regr = messages.RegistrationResource( + body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') + + # Authorization + authzr_uri = 'https://www.letsencrypt-demo.org/acme/authz/1' + challb = messages.ChallengeBody( + uri=(authzr_uri + '/1'), status=messages.STATUS_VALID, + chall=challenges.DNS(token=jose.b64decode( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'))) + self.challr = messages.ChallengeResource( + body=challb, authzr_uri=authzr_uri) + self.authz = messages.Authorization( + identifier=messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='example.com'), + challenges=(challb,), combinations=None) + self.authzr = messages.AuthorizationResource( + body=self.authz, uri=authzr_uri) + + # Reason code for revocation + self.rsn = 1 + + +class BackwardsCompatibleClientV2Test(ClientTestBase): + """Tests for acme.client.BackwardsCompatibleClientV2.""" + + def setUp(self): + super(BackwardsCompatibleClientV2Test, self).setUp() + # contains a loaded cert + self.certr = messages.CertificateResource( + body=messages_test.CERT) + + loaded = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, CERT_SAN_PEM) + wrapped = jose.ComparableX509(loaded) + self.chain = [wrapped, wrapped] + + self.cert_pem = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped).decode() + + single_chain = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, loaded).decode() + self.chain_pem = single_chain + single_chain + + self.fullchain_pem = self.cert_pem + self.chain_pem + + self.orderr = messages.OrderResource( + csr_pem=CSR_SAN_PEM) + + def _init(self): + uri = 'http://www.letsencrypt-demo.org/directory' + from acme.client import BackwardsCompatibleClientV2 + return BackwardsCompatibleClientV2(net=self.net, + key=KEY, server=uri) + + def test_init_downloads_directory(self): + uri = 'http://www.letsencrypt-demo.org/directory' + from acme.client import BackwardsCompatibleClientV2 + BackwardsCompatibleClientV2(net=self.net, + key=KEY, server=uri) + self.net.get.assert_called_once_with(uri) + + def test_init_acme_version(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + client = self._init() + self.assertEqual(client.acme_version, 1) + + self.response.json.return_value = DIRECTORY_V2.to_json() + client = self._init() + self.assertEqual(client.acme_version, 2) + + def test_query_registration_client_v2(self): + self.response.json.return_value = DIRECTORY_V2.to_json() + client = self._init() + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, client.query_registration(self.regr)) + + def test_forwarding(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + client = self._init() + self.assertEqual(client.directory, client.client.directory) + self.assertEqual(client.key, KEY) + self.assertEqual(client.deactivate_registration, client.client.deactivate_registration) + self.assertRaises(AttributeError, client.__getattr__, 'nonexistent') + self.assertRaises(AttributeError, client.__getattr__, 'new_account_and_tos') + self.assertRaises(AttributeError, client.__getattr__, 'new_account') + + def test_new_account_and_tos(self): + # v2 no tos + self.response.json.return_value = DIRECTORY_V2.to_json() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.new_account_and_tos(self.new_reg) + mock_client().new_account.assert_called_with(self.new_reg) + + # v2 tos good + with mock.patch('acme.client.ClientV2') as mock_client: + mock_client().directory.meta.__contains__.return_value = True + client = self._init() + client.new_account_and_tos(self.new_reg, lambda x: True) + mock_client().new_account.assert_called_with( + self.new_reg.update(terms_of_service_agreed=True)) + + # v2 tos bad + with mock.patch('acme.client.ClientV2') as mock_client: + mock_client().directory.meta.__contains__.return_value = True + client = self._init() + def _tos_cb(tos): + raise errors.Error + self.assertRaises(errors.Error, client.new_account_and_tos, + self.new_reg, _tos_cb) + mock_client().new_account.assert_not_called() + + # v1 yes tos + self.response.json.return_value = DIRECTORY_V1.to_json() + with mock.patch('acme.client.Client') as mock_client: + regr = mock.MagicMock(terms_of_service="TOS") + mock_client().register.return_value = regr + client = self._init() + client.new_account_and_tos(self.new_reg) + mock_client().register.assert_called_once_with(self.new_reg) + mock_client().agree_to_tos.assert_called_once_with(regr) + + # v1 no tos + with mock.patch('acme.client.Client') as mock_client: + regr = mock.MagicMock(terms_of_service=None) + mock_client().register.return_value = regr + client = self._init() + client.new_account_and_tos(self.new_reg) + mock_client().register.assert_called_once_with(self.new_reg) + mock_client().agree_to_tos.assert_not_called() + + @mock.patch('OpenSSL.crypto.load_certificate_request') + @mock.patch('acme.crypto_util._pyopenssl_cert_or_req_all_names') + def test_new_order_v1(self, mock__pyopenssl_cert_or_req_all_names, + unused_mock_load_certificate_request): + self.response.json.return_value = DIRECTORY_V1.to_json() + mock__pyopenssl_cert_or_req_all_names.return_value = ['example.com', 'www.example.com'] + mock_csr_pem = mock.MagicMock() + with mock.patch('acme.client.Client') as mock_client: + mock_client().request_domain_challenges.return_value = mock.sentinel.auth + client = self._init() + orderr = client.new_order(mock_csr_pem) + self.assertEqual(orderr.authorizations, [mock.sentinel.auth, mock.sentinel.auth]) + + def test_new_order_v2(self): + self.response.json.return_value = DIRECTORY_V2.to_json() + mock_csr_pem = mock.MagicMock() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.new_order(mock_csr_pem) + mock_client().new_order.assert_called_once_with(mock_csr_pem) + + @mock.patch('acme.client.Client') + def test_finalize_order_v1_success(self, mock_client): + self.response.json.return_value = DIRECTORY_V1.to_json() + + mock_client().request_issuance.return_value = self.certr + mock_client().fetch_chain.return_value = self.chain + + deadline = datetime.datetime(9999, 9, 9) + client = self._init() + result = client.finalize_order(self.orderr, deadline) + self.assertEqual(result.fullchain_pem, self.fullchain_pem) + mock_client().fetch_chain.assert_called_once_with(self.certr) + + @mock.patch('acme.client.Client') + def test_finalize_order_v1_fetch_chain_error(self, mock_client): + self.response.json.return_value = DIRECTORY_V1.to_json() + + mock_client().request_issuance.return_value = self.certr + mock_client().fetch_chain.return_value = self.chain + mock_client().fetch_chain.side_effect = [errors.Error, self.chain] + + deadline = datetime.datetime(9999, 9, 9) + client = self._init() + result = client.finalize_order(self.orderr, deadline) + self.assertEqual(result.fullchain_pem, self.fullchain_pem) + self.assertEqual(mock_client().fetch_chain.call_count, 2) + + @mock.patch('acme.client.Client') + def test_finalize_order_v1_timeout(self, mock_client): + self.response.json.return_value = DIRECTORY_V1.to_json() + + mock_client().request_issuance.return_value = self.certr + + deadline = deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) + client = self._init() + self.assertRaises(errors.TimeoutError, client.finalize_order, + self.orderr, deadline) + + def test_finalize_order_v2(self): + self.response.json.return_value = DIRECTORY_V2.to_json() + mock_orderr = mock.MagicMock() + mock_deadline = mock.MagicMock() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.finalize_order(mock_orderr, mock_deadline) + mock_client().finalize_order.assert_called_once_with(mock_orderr, mock_deadline) + + def test_revoke(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + with mock.patch('acme.client.Client') as mock_client: + client = self._init() + client.revoke(messages_test.CERT, self.rsn) + mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) + + self.response.json.return_value = DIRECTORY_V2.to_json() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.revoke(messages_test.CERT, self.rsn) + mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) + + def test_update_registration(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + with mock.patch('acme.client.Client') as mock_client: + client = self._init() + client.update_registration(mock.sentinel.regr, None) + mock_client().update_registration.assert_called_once_with(mock.sentinel.regr, None) + + # newNonce present means it will pick acme_version 2 + def test_external_account_required_true(self): + self.response.json.return_value = messages.Directory({ + 'newNonce': 'http://letsencrypt-test.com/acme/new-nonce', + 'meta': messages.Directory.Meta(external_account_required=True), + }).to_json() + + client = self._init() + + self.assertTrue(client.external_account_required()) + + # newNonce present means it will pick acme_version 2 + def test_external_account_required_false(self): + self.response.json.return_value = messages.Directory({ + 'newNonce': 'http://letsencrypt-test.com/acme/new-nonce', + 'meta': messages.Directory.Meta(external_account_required=False), + }).to_json() + + client = self._init() + + self.assertFalse(client.external_account_required()) + + def test_external_account_required_false_v1(self): + self.response.json.return_value = messages.Directory({ + 'meta': messages.Directory.Meta(external_account_required=False), + }).to_json() + + client = self._init() + + self.assertFalse(client.external_account_required()) + + +class ClientTest(ClientTestBase): + """Tests for acme.client.Client.""" + + def setUp(self): + super(ClientTest, self).setUp() + + self.directory = DIRECTORY_V1 + + # Registration + self.regr = self.regr.update( + terms_of_service='https://www.letsencrypt-demo.org/tos') + + # Request issuance + self.certr = messages.CertificateResource( + body=messages_test.CERT, authzrs=(self.authzr,), + uri='https://www.letsencrypt-demo.org/acme/cert/1', + cert_chain_uri='https://www.letsencrypt-demo.org/ca') + + from acme.client import Client + self.client = Client( + directory=self.directory, key=KEY, alg=jose.RS256, net=self.net) + + def test_init_downloads_directory(self): + uri = 'http://www.letsencrypt-demo.org/directory' + from acme.client import Client + self.client = Client( + directory=uri, key=KEY, alg=jose.RS256, net=self.net) + self.net.get.assert_called_once_with(uri) + + @mock.patch('acme.client.ClientNetwork') + def test_init_without_net(self, mock_net): + mock_net.return_value = mock.sentinel.net + alg = jose.RS256 + from acme.client import Client + self.client = Client( + directory=self.directory, key=KEY, alg=alg) + mock_net.called_once_with(KEY, alg=alg, verify_ssl=True) + self.assertEqual(self.client.net, mock.sentinel.net) + + def test_register(self): + # "Instance of 'Field' has no to_json/update member" bug: + self.response.status_code = http_client.CREATED + self.response.json.return_value = self.regr.body.to_json() + self.response.headers['Location'] = self.regr.uri + self.response.links.update({ + 'terms-of-service': {'url': self.regr.terms_of_service}, + }) + + self.assertEqual(self.regr, self.client.register(self.new_reg)) + # TODO: test POST call arguments + + def test_update_registration(self): + # "Instance of 'Field' has no to_json/update member" bug: + self.response.headers['Location'] = self.regr.uri + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, self.client.update_registration(self.regr)) + # TODO: test POST call arguments + + # TODO: split here and separate test + self.response.json.return_value = self.regr.body.update( + contact=()).to_json() + + def test_deactivate_account(self): + self.response.headers['Location'] = self.regr.uri + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, + self.client.deactivate_registration(self.regr)) + + def test_query_registration(self): + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, self.client.query_registration(self.regr)) + + def test_agree_to_tos(self): + self.client.update_registration = mock.Mock() + self.client.agree_to_tos(self.regr) + regr = self.client.update_registration.call_args[0][0] + self.assertEqual(self.regr.terms_of_service, regr.body.agreement) + + def _prepare_response_for_request_challenges(self): + self.response.status_code = http_client.CREATED + self.response.headers['Location'] = self.authzr.uri + self.response.json.return_value = self.authz.to_json() + + def test_request_challenges(self): + self._prepare_response_for_request_challenges() + self.client.request_challenges(self.identifier) + self.net.post.assert_called_once_with( + self.directory.new_authz, + messages.NewAuthorization(identifier=self.identifier), + acme_version=1) + + def test_request_challenges_deprecated_arg(self): + self._prepare_response_for_request_challenges() + self.client.request_challenges(self.identifier, new_authzr_uri="hi") + self.net.post.assert_called_once_with( + self.directory.new_authz, + messages.NewAuthorization(identifier=self.identifier), + acme_version=1) + + def test_request_challenges_custom_uri(self): + self._prepare_response_for_request_challenges() + self.client.request_challenges(self.identifier) + self.net.post.assert_called_once_with( + 'https://www.letsencrypt-demo.org/acme/new-authz', mock.ANY, + acme_version=1) + + def test_request_challenges_unexpected_update(self): + self._prepare_response_for_request_challenges() + self.response.json.return_value = self.authz.update( + identifier=self.identifier.update(value='foo')).to_json() + self.assertRaises( + errors.UnexpectedUpdate, self.client.request_challenges, + self.identifier) + + def test_request_challenges_wildcard(self): + wildcard_identifier = messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='*.example.org') + self.assertRaises( + errors.WildcardUnsupportedError, self.client.request_challenges, + wildcard_identifier) + + def test_request_domain_challenges(self): + self.client.request_challenges = mock.MagicMock() + self.assertEqual( + self.client.request_challenges(self.identifier), + self.client.request_domain_challenges('example.com')) + + def test_answer_challenge(self): + self.response.links['up'] = {'url': self.challr.authzr_uri} + self.response.json.return_value = self.challr.body.to_json() + + chall_response = challenges.DNSResponse(validation=None) + + self.client.answer_challenge(self.challr.body, chall_response) + + # TODO: split here and separate test + self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge, + self.challr.body.update(uri='foo'), chall_response) + + def test_answer_challenge_missing_next(self): + self.assertRaises( + errors.ClientError, self.client.answer_challenge, + self.challr.body, challenges.DNSResponse(validation=None)) + + def test_retry_after_date(self): + self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' + self.assertEqual( + datetime.datetime(1999, 12, 31, 23, 59, 59), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_invalid(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + + self.response.headers['Retry-After'] = 'foooo' + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 10), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_overflow(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + dt_mock.datetime.side_effect = datetime.datetime + + self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST" + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 10), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_seconds(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + + self.response.headers['Retry-After'] = '50' + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 50), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_missing(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 10), + self.client.retry_after(response=self.response, default=10)) + + def test_poll(self): + self.response.json.return_value = self.authzr.body.to_json() + self.assertEqual((self.authzr, self.response), + self.client.poll(self.authzr)) + + # TODO: split here and separate test + self.response.json.return_value = self.authz.update( + identifier=self.identifier.update(value='foo')).to_json() + self.assertRaises( + errors.UnexpectedUpdate, self.client.poll, self.authzr) + + def test_request_issuance(self): + self.response.content = CERT_DER + self.response.headers['Location'] = self.certr.uri + self.response.links['up'] = {'url': self.certr.cert_chain_uri} + self.assertEqual(self.certr, self.client.request_issuance( + messages_test.CSR, (self.authzr,))) + # TODO: check POST args + + def test_request_issuance_missing_up(self): + self.response.content = CERT_DER + self.response.headers['Location'] = self.certr.uri + self.assertEqual( + self.certr.update(cert_chain_uri=None), + self.client.request_issuance(messages_test.CSR, (self.authzr,))) + + def test_request_issuance_missing_location(self): + self.assertRaises( + errors.ClientError, self.client.request_issuance, + messages_test.CSR, (self.authzr,)) + + @mock.patch('acme.client.datetime') + @mock.patch('acme.client.time') + def test_poll_and_request_issuance(self, time_mock, dt_mock): + # clock.dt | pylint: disable=no-member + clock = mock.MagicMock(dt=datetime.datetime(2015, 3, 27)) + + def sleep(seconds): + """increment clock""" + clock.dt += datetime.timedelta(seconds=seconds) + time_mock.sleep.side_effect = sleep + + def now(): + """return current clock value""" + return clock.dt + dt_mock.datetime.now.side_effect = now + dt_mock.timedelta = datetime.timedelta + + def poll(authzr): # pylint: disable=missing-docstring + # record poll start time based on the current clock value + authzr.times.append(clock.dt) + + # suppose it takes 2 seconds for server to produce the + # result, increment clock + clock.dt += datetime.timedelta(seconds=2) + + if len(authzr.retries) == 1: # no more retries + done = mock.MagicMock(uri=authzr.uri, times=authzr.times) + done.body.status = authzr.retries[0] + return done, [] + + # response (2nd result tuple element) is reduced to only + # Retry-After header contents represented as integer + # seconds; authzr.retries is a list of Retry-After + # headers, head(retries) is peeled of as a current + # Retry-After header, and tail(retries) is persisted for + # later poll() calls + return (mock.MagicMock(retries=authzr.retries[1:], + uri=authzr.uri + '.', times=authzr.times), + authzr.retries[0]) + self.client.poll = mock.MagicMock(side_effect=poll) + + mintime = 7 + + def retry_after(response, default): + # pylint: disable=missing-docstring + # check that poll_and_request_issuance correctly passes mintime + self.assertEqual(default, mintime) + return clock.dt + datetime.timedelta(seconds=response) + self.client.retry_after = mock.MagicMock(side_effect=retry_after) + + def request_issuance(csr, authzrs): # pylint: disable=missing-docstring + return csr, authzrs + self.client.request_issuance = mock.MagicMock( + side_effect=request_issuance) + + csr = mock.MagicMock() + authzrs = ( + mock.MagicMock(uri='a', times=[], retries=( + 8, 20, 30, messages.STATUS_VALID)), + mock.MagicMock(uri='b', times=[], retries=( + 5, messages.STATUS_VALID)), + ) + + cert, updated_authzrs = self.client.poll_and_request_issuance( + csr, authzrs, mintime=mintime, + # make sure that max_attempts is per-authorization, rather + # than global + max_attempts=max(len(authzrs[0].retries), len(authzrs[1].retries))) + self.assertTrue(cert[0] is csr) + self.assertTrue(cert[1] is updated_authzrs) + self.assertEqual(updated_authzrs[0].uri, 'a...') + self.assertEqual(updated_authzrs[1].uri, 'b.') + self.assertEqual(updated_authzrs[0].times, [ + datetime.datetime(2015, 3, 27), + # a is scheduled for 10, but b is polling [9..11), so it + # will be picked up as soon as b is finished, without + # additional sleeping + datetime.datetime(2015, 3, 27, 0, 0, 11), + datetime.datetime(2015, 3, 27, 0, 0, 33), + datetime.datetime(2015, 3, 27, 0, 1, 5), + ]) + self.assertEqual(updated_authzrs[1].times, [ + datetime.datetime(2015, 3, 27, 0, 0, 2), + datetime.datetime(2015, 3, 27, 0, 0, 9), + ]) + self.assertEqual(clock.dt, datetime.datetime(2015, 3, 27, 0, 1, 7)) + + # CA sets invalid | TODO: move to a separate test + invalid_authzr = mock.MagicMock( + times=[], retries=[messages.STATUS_INVALID]) + self.assertRaises( + errors.PollError, self.client.poll_and_request_issuance, + csr, authzrs=(invalid_authzr,), mintime=mintime) + + # exceeded max_attempts | TODO: move to a separate test + self.assertRaises( + errors.PollError, self.client.poll_and_request_issuance, + csr, authzrs, mintime=mintime, max_attempts=2) + + def test_deactivate_authorization(self): + authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED) + self.response.json.return_value = authzb.to_json() + authzr = self.client.deactivate_authorization(self.authzr) + self.assertEqual(authzb, authzr.body) + self.assertEqual(self.client.net.post.call_count, 1) + self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0]) + + def test_check_cert(self): + self.response.headers['Location'] = self.certr.uri + self.response.content = CERT_DER + self.assertEqual(self.certr.update(body=messages_test.CERT), + self.client.check_cert(self.certr)) + + # TODO: split here and separate test + self.response.headers['Location'] = 'foo' + self.assertRaises( + errors.UnexpectedUpdate, self.client.check_cert, self.certr) + + def test_check_cert_missing_location(self): + self.response.content = CERT_DER + self.assertRaises( + errors.ClientError, self.client.check_cert, self.certr) + + def test_refresh(self): + self.client.check_cert = mock.MagicMock() + self.assertEqual( + self.client.check_cert(self.certr), self.client.refresh(self.certr)) + + def test_fetch_chain_no_up_link(self): + self.assertEqual([], self.client.fetch_chain(self.certr.update( + cert_chain_uri=None))) + + def test_fetch_chain_single(self): + # pylint: disable=protected-access + self.client._get_cert = mock.MagicMock() + self.client._get_cert.return_value = ( + mock.MagicMock(links={}), "certificate") + self.assertEqual([self.client._get_cert(self.certr.cert_chain_uri)[1]], + self.client.fetch_chain(self.certr)) + + def test_fetch_chain_max(self): + # pylint: disable=protected-access + up_response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) + noup_response = mock.MagicMock(links={}) + self.client._get_cert = mock.MagicMock() + self.client._get_cert.side_effect = [ + (up_response, "cert")] * 9 + [(noup_response, "last_cert")] + chain = self.client.fetch_chain(self.certr, max_length=10) + self.assertEqual(chain, ["cert"] * 9 + ["last_cert"]) + + def test_fetch_chain_too_many(self): # recursive + # pylint: disable=protected-access + response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) + self.client._get_cert = mock.MagicMock() + self.client._get_cert.return_value = (response, "certificate") + self.assertRaises(errors.Error, self.client.fetch_chain, self.certr) + + def test_revoke(self): + self.client.revoke(self.certr.body, self.rsn) + self.net.post.assert_called_once_with( + self.directory[messages.Revocation], mock.ANY, acme_version=1) + + def test_revocation_payload(self): + obj = messages.Revocation(certificate=self.certr.body, reason=self.rsn) + self.assertTrue('reason' in obj.to_partial_json().keys()) + self.assertEqual(self.rsn, obj.to_partial_json()['reason']) + + def test_revoke_bad_status_raises_error(self): + self.response.status_code = http_client.METHOD_NOT_ALLOWED + self.assertRaises( + errors.ClientError, + self.client.revoke, + self.certr, + self.rsn) + + +class ClientV2Test(ClientTestBase): + """Tests for acme.client.ClientV2.""" + + def setUp(self): + super(ClientV2Test, self).setUp() + + self.directory = DIRECTORY_V2 + + from acme.client import ClientV2 + self.client = ClientV2(self.directory, self.net) + + self.new_reg = self.new_reg.update(terms_of_service_agreed=True) + + self.authzr_uri2 = 'https://www.letsencrypt-demo.org/acme/authz/2' + self.authz2 = self.authz.update(identifier=messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='www.example.com'), + status=messages.STATUS_PENDING) + self.authzr2 = messages.AuthorizationResource( + body=self.authz2, uri=self.authzr_uri2) + + self.order = messages.Order( + identifiers=(self.authz.identifier, self.authz2.identifier), + status=messages.STATUS_PENDING, + authorizations=(self.authzr.uri, self.authzr_uri2), + finalize='https://www.letsencrypt-demo.org/acme/acct/1/order/1/finalize') + self.orderr = messages.OrderResource( + body=self.order, + uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1', + authorizations=[self.authzr, self.authzr2], csr_pem=CSR_SAN_PEM) + + def test_new_account(self): + self.response.status_code = http_client.CREATED + self.response.json.return_value = self.regr.body.to_json() + self.response.headers['Location'] = self.regr.uri + + self.assertEqual(self.regr, self.client.new_account(self.new_reg)) + + def test_new_account_conflict(self): + self.response.status_code = http_client.OK + self.response.headers['Location'] = self.regr.uri + self.assertRaises(errors.ConflictError, self.client.new_account, self.new_reg) + + def test_new_order(self): + order_response = copy.deepcopy(self.response) + order_response.status_code = http_client.CREATED + order_response.json.return_value = self.order.to_json() + order_response.headers['Location'] = self.orderr.uri + self.net.post.return_value = order_response + + authz_response = copy.deepcopy(self.response) + authz_response.json.return_value = self.authz.to_json() + authz_response.headers['Location'] = self.authzr.uri + authz_response2 = self.response + authz_response2.json.return_value = self.authz2.to_json() + authz_response2.headers['Location'] = self.authzr2.uri + + with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get: + mock_post_as_get.side_effect = (authz_response, authz_response2) + self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr) + + @mock.patch('acme.client.datetime') + def test_poll_and_finalize(self, mock_datetime): + mock_datetime.datetime.now.return_value = datetime.datetime(2018, 2, 15) + mock_datetime.timedelta = datetime.timedelta + expected_deadline = mock_datetime.datetime.now() + datetime.timedelta(seconds=90) + + self.client.poll_authorizations = mock.Mock(return_value=self.orderr) + self.client.finalize_order = mock.Mock(return_value=self.orderr) + + self.assertEqual(self.client.poll_and_finalize(self.orderr), self.orderr) + self.client.poll_authorizations.assert_called_once_with(self.orderr, expected_deadline) + self.client.finalize_order.assert_called_once_with(self.orderr, expected_deadline) + + @mock.patch('acme.client.datetime') + def test_poll_authorizations_timeout(self, mock_datetime): + now_side_effect = [datetime.datetime(2018, 2, 15), + datetime.datetime(2018, 2, 16), + datetime.datetime(2018, 2, 17)] + mock_datetime.datetime.now.side_effect = now_side_effect + self.response.json.side_effect = [ + self.authz.to_json(), self.authz2.to_json(), self.authz2.to_json()] + + self.assertRaises( + errors.TimeoutError, self.client.poll_authorizations, self.orderr, now_side_effect[1]) + + def test_poll_authorizations_failure(self): + deadline = datetime.datetime(9999, 9, 9) + challb = self.challr.body.update(status=messages.STATUS_INVALID, + error=messages.Error.with_code('unauthorized')) + authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,)) + self.response.json.return_value = authz.to_json() + + self.assertRaises( + errors.ValidationError, self.client.poll_authorizations, self.orderr, deadline) + + def test_poll_authorizations_success(self): + deadline = datetime.datetime(9999, 9, 9) + updated_authz2 = self.authz2.update(status=messages.STATUS_VALID) + updated_authzr2 = messages.AuthorizationResource( + body=updated_authz2, uri=self.authzr_uri2) + updated_orderr = self.orderr.update(authorizations=[self.authzr, updated_authzr2]) + + self.response.json.side_effect = ( + self.authz.to_json(), self.authz2.to_json(), updated_authz2.to_json()) + self.assertEqual(self.client.poll_authorizations(self.orderr, deadline), updated_orderr) + + def test_finalize_order_success(self): + updated_order = self.order.update( + certificate='https://www.letsencrypt-demo.org/acme/cert/') + updated_orderr = self.orderr.update(body=updated_order, fullchain_pem=CERT_SAN_PEM) + + self.response.json.return_value = updated_order.to_json() + self.response.text = CERT_SAN_PEM + + deadline = datetime.datetime(9999, 9, 9) + self.assertEqual(self.client.finalize_order(self.orderr, deadline), updated_orderr) + + def test_finalize_order_error(self): + updated_order = self.order.update(error=messages.Error.with_code('unauthorized')) + self.response.json.return_value = updated_order.to_json() + + deadline = datetime.datetime(9999, 9, 9) + self.assertRaises(errors.IssuanceError, self.client.finalize_order, self.orderr, deadline) + + def test_finalize_order_timeout(self): + deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) + self.assertRaises(errors.TimeoutError, self.client.finalize_order, self.orderr, deadline) + + def test_revoke(self): + self.client.revoke(messages_test.CERT, self.rsn) + self.net.post.assert_called_once_with( + self.directory["revokeCert"], mock.ANY, acme_version=2, + new_nonce_url=DIRECTORY_V2['newNonce']) + + def test_update_registration(self): + # "Instance of 'Field' has no to_json/update member" bug: + self.response.headers['Location'] = self.regr.uri + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, self.client.update_registration(self.regr)) + self.assertNotEqual(self.client.net.account, None) + self.assertEqual(self.client.net.post.call_count, 2) + self.assertTrue(DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0]) + + self.response.json.return_value = self.regr.body.update( + contact=()).to_json() + + def test_external_account_required_true(self): + self.client.directory = messages.Directory({ + 'meta': messages.Directory.Meta(external_account_required=True) + }) + + self.assertTrue(self.client.external_account_required()) + + def test_external_account_required_false(self): + self.client.directory = messages.Directory({ + 'meta': messages.Directory.Meta(external_account_required=False) + }) + + self.assertFalse(self.client.external_account_required()) + + def test_external_account_required_default(self): + self.assertFalse(self.client.external_account_required()) + + def test_post_as_get(self): + with mock.patch('acme.client.ClientV2._authzr_from_response') as mock_client: + mock_client.return_value = self.authzr2 + + self.client.poll(self.authzr2) # pylint: disable=protected-access + + self.client.net.post.assert_called_once_with( + self.authzr2.uri, None, acme_version=2, + new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce') + self.client.net.get.assert_not_called() + + class FakeError(messages.Error): + """Fake error to reproduce a malformed request ACME error""" + def __init__(self): # pylint: disable=super-init-not-called + pass + @property + def code(self): + return 'malformed' + self.client.net.post.side_effect = FakeError() + + self.client.poll(self.authzr2) # pylint: disable=protected-access + + self.client.net.get.assert_called_once_with(self.authzr2.uri) + + +class MockJSONDeSerializable(jose.JSONDeSerializable): + # pylint: disable=missing-docstring + def __init__(self, value): + self.value = value + + def to_partial_json(self): + return {'foo': self.value} + + @classmethod + def from_json(cls, jobj): + pass # pragma: no cover + + +class ClientNetworkTest(unittest.TestCase): + """Tests for acme.client.ClientNetwork.""" + + def setUp(self): + self.verify_ssl = mock.MagicMock() + self.wrap_in_jws = mock.MagicMock(return_value=mock.sentinel.wrapped) + + from acme.client import ClientNetwork + self.net = ClientNetwork( + key=KEY, alg=jose.RS256, verify_ssl=self.verify_ssl, + user_agent='acme-python-test') + + self.response = mock.MagicMock(ok=True, status_code=http_client.OK) + self.response.headers = {} + self.response.links = {} + + def test_init(self): + self.assertTrue(self.net.verify_ssl is self.verify_ssl) + + def test_wrap_in_jws(self): + # pylint: disable=protected-access + jws_dump = self.net._wrap_in_jws( + MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", + acme_version=1) + jws = acme_jws.JWS.json_loads(jws_dump) + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) + self.assertEqual(jws.signature.combined.nonce, b'Tg') + + def test_wrap_in_jws_v2(self): + self.net.account = {'uri': 'acct-uri'} + # pylint: disable=protected-access + jws_dump = self.net._wrap_in_jws( + MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", + acme_version=2) + jws = acme_jws.JWS.json_loads(jws_dump) + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) + self.assertEqual(jws.signature.combined.nonce, b'Tg') + self.assertEqual(jws.signature.combined.kid, u'acct-uri') + self.assertEqual(jws.signature.combined.url, u'url') + + def test_check_response_not_ok_jobj_no_error(self): + self.response.ok = False + self.response.json.return_value = {} + with mock.patch('acme.client.messages.Error.from_json') as from_json: + from_json.side_effect = jose.DeserializationError + # pylint: disable=protected-access + self.assertRaises( + errors.ClientError, self.net._check_response, self.response) + + def test_check_response_not_ok_jobj_error(self): + self.response.ok = False + self.response.json.return_value = messages.Error( + detail='foo', typ='serverInternal', title='some title').to_json() + # pylint: disable=protected-access + self.assertRaises( + messages.Error, self.net._check_response, self.response) + + def test_check_response_not_ok_no_jobj(self): + self.response.ok = False + self.response.json.side_effect = ValueError + # pylint: disable=protected-access + self.assertRaises( + errors.ClientError, self.net._check_response, self.response) + + def test_check_response_ok_no_jobj_ct_required(self): + self.response.json.side_effect = ValueError + for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: + self.response.headers['Content-Type'] = response_ct + # pylint: disable=protected-access + self.assertRaises( + errors.ClientError, self.net._check_response, self.response, + content_type=self.net.JSON_CONTENT_TYPE) + + def test_check_response_ok_no_jobj_no_ct(self): + self.response.json.side_effect = ValueError + for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: + self.response.headers['Content-Type'] = response_ct + # pylint: disable=protected-access,no-value-for-parameter + self.assertEqual( + self.response, self.net._check_response(self.response)) + + def test_check_response_conflict(self): + self.response.ok = False + self.response.status_code = 409 + # pylint: disable=protected-access + self.assertRaises(errors.ConflictError, self.net._check_response, self.response) + + def test_check_response_jobj(self): + self.response.json.return_value = {} + for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: + self.response.headers['Content-Type'] = response_ct + # pylint: disable=protected-access,no-value-for-parameter + self.assertEqual( + self.response, self.net._check_response(self.response)) + + def test_send_request(self): + self.net.session = mock.MagicMock() + self.net.session.request.return_value = self.response + # pylint: disable=protected-access + self.assertEqual(self.response, self.net._send_request( + 'HEAD', 'http://example.com/', 'foo', bar='baz')) + self.net.session.request.assert_called_once_with( + 'HEAD', 'http://example.com/', 'foo', + headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, bar='baz') + + @mock.patch('acme.client.logger') + def test_send_request_get_der(self, mock_logger): + self.net.session = mock.MagicMock() + self.net.session.request.return_value = mock.MagicMock( + ok=True, status_code=http_client.OK, + headers={"Content-Type": "application/pkix-cert"}, + content=b"hi") + # pylint: disable=protected-access + self.net._send_request('HEAD', 'http://example.com/', 'foo', + timeout=mock.ANY, bar='baz') + mock_logger.debug.assert_called_with( + 'Received response:\nHTTP %d\n%s\n\n%s', 200, + 'Content-Type: application/pkix-cert', b'aGk=') + + def test_send_request_post(self): + self.net.session = mock.MagicMock() + self.net.session.request.return_value = self.response + # pylint: disable=protected-access + self.assertEqual(self.response, self.net._send_request( + 'POST', 'http://example.com/', 'foo', data='qux', bar='baz')) + self.net.session.request.assert_called_once_with( + 'POST', 'http://example.com/', 'foo', + headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, data='qux', bar='baz') + + def test_send_request_verify_ssl(self): + # pylint: disable=protected-access + for verify in True, False: + self.net.session = mock.MagicMock() + self.net.session.request.return_value = self.response + self.net.verify_ssl = verify + # pylint: disable=protected-access + self.assertEqual( + self.response, + self.net._send_request('GET', 'http://example.com/')) + self.net.session.request.assert_called_once_with( + 'GET', 'http://example.com/', verify=verify, + timeout=mock.ANY, headers=mock.ANY) + + def test_send_request_user_agent(self): + self.net.session = mock.MagicMock() + # pylint: disable=protected-access + self.net._send_request('GET', 'http://example.com/', + headers={'bar': 'baz'}) + self.net.session.request.assert_called_once_with( + 'GET', 'http://example.com/', verify=mock.ANY, + timeout=mock.ANY, + headers={'User-Agent': 'acme-python-test', 'bar': 'baz'}) + + self.net._send_request('GET', 'http://example.com/', + headers={'User-Agent': 'foo2'}) + self.net.session.request.assert_called_with( + 'GET', 'http://example.com/', + verify=mock.ANY, timeout=mock.ANY, headers={'User-Agent': 'foo2'}) + + def test_send_request_timeout(self): + self.net.session = mock.MagicMock() + # pylint: disable=protected-access + self.net._send_request('GET', 'http://example.com/', + headers={'bar': 'baz'}) + self.net.session.request.assert_called_once_with( + mock.ANY, mock.ANY, verify=mock.ANY, headers=mock.ANY, + timeout=45) + + def test_del(self, close_exception=None): + sess = mock.MagicMock() + + if close_exception is not None: + sess.close.side_effect = close_exception + + self.net.session = sess + del self.net + sess.close.assert_called_once_with() + + def test_del_error(self): + self.test_del(ReferenceError) + + @mock.patch('acme.client.requests') + def test_requests_error_passthrough(self, mock_requests): + mock_requests.exceptions = requests.exceptions + mock_requests.request.side_effect = requests.exceptions.RequestException + # pylint: disable=protected-access + self.assertRaises(requests.exceptions.RequestException, + self.net._send_request, 'GET', 'uri') + + def test_urllib_error(self): + # Using a connection error to test a properly formatted error message + try: + # pylint: disable=protected-access + self.net._send_request('GET', "http://localhost:19123/nonexistent.txt") + + # Value Error Generated Exceptions + except ValueError as y: + self.assertEqual("Requesting localhost/nonexistent: " + "Connection refused", str(y)) + + # Requests Library Exceptions + except requests.exceptions.ConnectionError as z: #pragma: no cover + self.assertTrue("'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z)) + + +class ClientNetworkWithMockedResponseTest(unittest.TestCase): + """Tests for acme.client.ClientNetwork which mock out response.""" + + def setUp(self): + from acme.client import ClientNetwork + self.net = ClientNetwork(key=None, alg=None) + + self.response = mock.MagicMock(ok=True, status_code=http_client.OK) + self.response.headers = {} + self.response.links = {} + self.response.checked = False + self.acmev1_nonce_response = mock.MagicMock(ok=False, + status_code=http_client.METHOD_NOT_ALLOWED) + self.acmev1_nonce_response.headers = {} + self.obj = mock.MagicMock() + self.wrapped_obj = mock.MagicMock() + self.content_type = mock.sentinel.content_type + + self.all_nonces = [ + jose.b64encode(b'Nonce'), + jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')] + self.available_nonces = self.all_nonces[:] + + def send_request(*args, **kwargs): + # pylint: disable=unused-argument,missing-docstring + self.assertFalse("new_nonce_url" in kwargs) + method = args[0] + uri = args[1] + if method == 'HEAD' and uri != "new_nonce_uri": + response = self.acmev1_nonce_response + else: + response = self.response + + if self.available_nonces: + response.headers = { + self.net.REPLAY_NONCE_HEADER: + self.available_nonces.pop().decode()} + else: + response.headers = {} + return response + + # pylint: disable=protected-access + self.net._send_request = self.send_request = mock.MagicMock( + side_effect=send_request) + self.net._check_response = self.check_response + self.net._wrap_in_jws = mock.MagicMock(return_value=self.wrapped_obj) + + def check_response(self, response, content_type): + # pylint: disable=missing-docstring + self.assertEqual(self.response, response) + self.assertEqual(self.content_type, content_type) + self.assertTrue(self.response.ok) + self.response.checked = True + return self.response + + def test_head(self): + self.assertEqual(self.acmev1_nonce_response, self.net.head( + 'http://example.com/', 'foo', bar='baz')) + self.send_request.assert_called_once_with( + 'HEAD', 'http://example.com/', 'foo', bar='baz') + + def test_head_v2(self): + self.assertEqual(self.response, self.net.head( + 'new_nonce_uri', 'foo', bar='baz')) + self.send_request.assert_called_once_with( + 'HEAD', 'new_nonce_uri', 'foo', bar='baz') + + def test_get(self): + self.assertEqual(self.response, self.net.get( + 'http://example.com/', content_type=self.content_type, bar='baz')) + self.assertTrue(self.response.checked) + self.send_request.assert_called_once_with( + 'GET', 'http://example.com/', bar='baz') + + def test_post_no_content_type(self): + self.content_type = self.net.JOSE_CONTENT_TYPE + self.assertEqual(self.response, self.net.post('uri', self.obj)) + self.assertTrue(self.response.checked) + + def test_post(self): + # pylint: disable=protected-access + self.assertEqual(self.response, self.net.post( + 'uri', self.obj, content_type=self.content_type)) + self.assertTrue(self.response.checked) + self.net._wrap_in_jws.assert_called_once_with( + self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) + + self.available_nonces = [] + self.assertRaises(errors.MissingNonce, self.net.post, + 'uri', self.obj, content_type=self.content_type) + self.net._wrap_in_jws.assert_called_with( + self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) + + def test_post_wrong_initial_nonce(self): # HEAD + self.available_nonces = [b'f', jose.b64encode(b'good')] + self.assertRaises(errors.BadNonce, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_wrong_post_response_nonce(self): + self.available_nonces = [jose.b64encode(b'good'), b'f'] + self.assertRaises(errors.BadNonce, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_failed_retry(self): + check_response = mock.MagicMock() + check_response.side_effect = messages.Error.with_code('badNonce') + + # pylint: disable=protected-access + self.net._check_response = check_response + self.assertRaises(messages.Error, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_not_retried(self): + check_response = mock.MagicMock() + check_response.side_effect = [messages.Error.with_code('malformed'), + self.response] + + # pylint: disable=protected-access + self.net._check_response = check_response + self.assertRaises(messages.Error, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_successful_retry(self): + post_once = mock.MagicMock() + post_once.side_effect = [messages.Error.with_code('badNonce'), + self.response] + + # pylint: disable=protected-access + self.assertEqual(self.response, self.net.post( + 'uri', self.obj, content_type=self.content_type)) + + def test_head_get_post_error_passthrough(self): + self.send_request.side_effect = requests.exceptions.RequestException + for method in self.net.head, self.net.get: + self.assertRaises( + requests.exceptions.RequestException, method, 'GET', 'uri') + self.assertRaises(requests.exceptions.RequestException, + self.net.post, 'uri', obj=self.obj) + + def test_post_bad_nonce_head(self): + # pylint: disable=protected-access + # regression test for https://github.com/certbot/certbot/issues/6092 + bad_response = mock.MagicMock(ok=False, status_code=http_client.SERVICE_UNAVAILABLE) + self.net._send_request = mock.MagicMock() + self.net._send_request.return_value = bad_response + self.content_type = None + check_response = mock.MagicMock() + self.net._check_response = check_response + self.assertRaises(errors.ClientError, self.net.post, 'uri', + self.obj, content_type=self.content_type, acme_version=2, + new_nonce_url='new_nonce_uri') + self.assertEqual(check_response.call_count, 1) + + def test_new_nonce_uri_removed(self): + self.content_type = None + self.net.post('uri', self.obj, content_type=None, + acme_version=2, new_nonce_url='new_nonce_uri') + + +class ClientNetworkSourceAddressBindingTest(unittest.TestCase): + """Tests that if ClientNetwork has a source IP set manually, the underlying library has + used the provided source address.""" + + def setUp(self): + self.source_address = "8.8.8.8" + + def test_source_address_set(self): + from acme.client import ClientNetwork + net = ClientNetwork(key=None, alg=None, source_address=self.source_address) + for adapter in net.session.adapters.values(): + self.assertTrue(self.source_address in adapter.source_address) + + def test_behavior_assumption(self): + """This is a test that guardrails the HTTPAdapter behavior so that if the default for + a Session() changes, the assumptions here aren't violated silently.""" + from acme.client import ClientNetwork + # Source address not specified, so the default adapter type should be bound -- this + # test should fail if the default adapter type is changed by requests + net = ClientNetwork(key=None, alg=None) + session = requests.Session() + for scheme in session.adapters.keys(): + client_network_adapter = net.session.adapters.get(scheme) + default_adapter = session.adapters.get(scheme) + self.assertEqual(client_network_adapter.__class__, default_adapter.__class__) + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/crypto_util_test.py b/acme/tests/crypto_util_test.py new file mode 100644 index 000000000..d351c1a3d --- /dev/null +++ b/acme/tests/crypto_util_test.py @@ -0,0 +1,267 @@ +"""Tests for acme.crypto_util.""" +import itertools +import socket +import threading +import time +import unittest + +import six +from six.moves import socketserver #type: ignore # pylint: disable=import-error + +import josepy as jose +import OpenSSL + +from acme import errors +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +import test_util + +class SSLSocketAndProbeSNITest(unittest.TestCase): + """Tests for acme.crypto_util.SSLSocket/probe_sni.""" + + + def setUp(self): + self.cert = test_util.load_comparable_cert('rsa2048_cert.pem') + key = test_util.load_pyopenssl_private_key('rsa2048_key.pem') + # pylint: disable=protected-access + certs = {b'foo': (key, self.cert.wrapped)} + + from acme.crypto_util import SSLSocket + + class _TestServer(socketserver.TCPServer): + + # six.moves.* | pylint: disable=attribute-defined-outside-init,no-init + + def server_bind(self): # pylint: disable=missing-docstring + self.socket = SSLSocket(socket.socket(), certs=certs) + socketserver.TCPServer.server_bind(self) + + self.server = _TestServer(('', 0), socketserver.BaseRequestHandler) + self.port = self.server.socket.getsockname()[1] + self.server_thread = threading.Thread( + # pylint: disable=no-member + target=self.server.handle_request) + + def tearDown(self): + if self.server_thread.is_alive(): + # The thread may have already terminated. + self.server_thread.join() # pragma: no cover + + def _probe(self, name): + from acme.crypto_util import probe_sni + return jose.ComparableX509(probe_sni( + name, host='127.0.0.1', port=self.port)) + + def _start_server(self): + self.server_thread.start() + time.sleep(1) # TODO: avoid race conditions in other way + + def test_probe_ok(self): + self._start_server() + self.assertEqual(self.cert, self._probe(b'foo')) + + def test_probe_not_recognized_name(self): + self._start_server() + self.assertRaises(errors.Error, self._probe, b'bar') + + def test_probe_connection_error(self): + # pylint has a hard time with six + self.server.server_close() # pylint: disable=no-member + original_timeout = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(1) + self.assertRaises(errors.Error, self._probe, b'bar') + finally: + socket.setdefaulttimeout(original_timeout) + + +class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase): + """Test for acme.crypto_util._pyopenssl_cert_or_req_all_names.""" + + @classmethod + def _call(cls, loader, name): + # pylint: disable=protected-access + from acme.crypto_util import _pyopenssl_cert_or_req_all_names + return _pyopenssl_cert_or_req_all_names(loader(name)) + + def _call_cert(self, name): + return self._call(test_util.load_cert, name) + + def test_cert_one_san_no_common(self): + self.assertEqual(self._call_cert('cert-nocn.der'), + ['no-common-name.badssl.com']) + + def test_cert_no_sans_yes_common(self): + self.assertEqual(self._call_cert('cert.pem'), ['example.com']) + + def test_cert_two_sans_yes_common(self): + self.assertEqual(self._call_cert('cert-san.pem'), + ['example.com', 'www.example.com']) + + +class PyOpenSSLCertOrReqSANTest(unittest.TestCase): + """Test for acme.crypto_util._pyopenssl_cert_or_req_san.""" + + + @classmethod + def _call(cls, loader, name): + # pylint: disable=protected-access + from acme.crypto_util import _pyopenssl_cert_or_req_san + return _pyopenssl_cert_or_req_san(loader(name)) + + @classmethod + def _get_idn_names(cls): + """Returns expected names from '{cert,csr}-idnsans.pem'.""" + chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400), + range(0x641, 0x6fc), + range(0x1820, 0x1877))] + return [''.join(chars[i: i + 45]) + '.invalid' + for i in range(0, len(chars), 45)] + + def _call_cert(self, name): + return self._call(test_util.load_cert, name) + + def _call_csr(self, name): + return self._call(test_util.load_csr, name) + + def test_cert_no_sans(self): + self.assertEqual(self._call_cert('cert.pem'), []) + + def test_cert_two_sans(self): + self.assertEqual(self._call_cert('cert-san.pem'), + ['example.com', 'www.example.com']) + + def test_cert_hundred_sans(self): + self.assertEqual(self._call_cert('cert-100sans.pem'), + ['example{0}.com'.format(i) for i in range(1, 101)]) + + def test_cert_idn_sans(self): + self.assertEqual(self._call_cert('cert-idnsans.pem'), + self._get_idn_names()) + + def test_csr_no_sans(self): + self.assertEqual(self._call_csr('csr-nosans.pem'), []) + + def test_csr_one_san(self): + self.assertEqual(self._call_csr('csr.pem'), ['example.com']) + + def test_csr_two_sans(self): + self.assertEqual(self._call_csr('csr-san.pem'), + ['example.com', 'www.example.com']) + + def test_csr_six_sans(self): + self.assertEqual(self._call_csr('csr-6sans.pem'), + ['example.com', 'example.org', 'example.net', + 'example.info', 'subdomain.example.com', + 'other.subdomain.example.com']) + + def test_csr_hundred_sans(self): + self.assertEqual(self._call_csr('csr-100sans.pem'), + ['example{0}.com'.format(i) for i in range(1, 101)]) + + def test_csr_idn_sans(self): + self.assertEqual(self._call_csr('csr-idnsans.pem'), + self._get_idn_names()) + + def test_critical_san(self): + self.assertEqual(self._call_cert('critical-san.pem'), + ['chicago-cubs.venafi.example', 'cubs.venafi.example']) + + + +class RandomSnTest(unittest.TestCase): + """Test for random certificate serial numbers.""" + + + def setUp(self): + self.cert_count = 5 + self.serial_num = [] # type: List[int] + self.key = OpenSSL.crypto.PKey() + self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + + def test_sn_collisions(self): + from acme.crypto_util import gen_ss_cert + + for _ in range(self.cert_count): + cert = gen_ss_cert(self.key, ['dummy'], force_san=True) + self.serial_num.append(cert.get_serial_number()) + self.assertTrue(len(set(self.serial_num)) > 1) + +class MakeCSRTest(unittest.TestCase): + """Test for standalone functions.""" + + @classmethod + def _call_with_key(cls, *args, **kwargs): + privkey = OpenSSL.crypto.PKey() + privkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + privkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey) + from acme.crypto_util import make_csr + return make_csr(privkey_pem, *args, **kwargs) + + def test_make_csr(self): + csr_pem = self._call_with_key(["a.example", "b.example"]) + self.assertTrue(b'--BEGIN CERTIFICATE REQUEST--' in csr_pem) + self.assertTrue(b'--END CERTIFICATE REQUEST--' in csr_pem) + csr = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr_pem) + # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't + # have a get_extensions() method, so we skip this test if the method + # isn't available. + if hasattr(csr, 'get_extensions'): + self.assertEqual(len(csr.get_extensions()), 1) + self.assertEqual(csr.get_extensions()[0].get_data(), + OpenSSL.crypto.X509Extension( + b'subjectAltName', + critical=False, + value=b'DNS:a.example, DNS:b.example', + ).get_data(), + ) + + def test_make_csr_must_staple(self): + csr_pem = self._call_with_key(["a.example"], must_staple=True) + csr = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr_pem) + + # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't + # have a get_extensions() method, so we skip this test if the method + # isn't available. + if hasattr(csr, 'get_extensions'): + self.assertEqual(len(csr.get_extensions()), 2) + # NOTE: Ideally we would filter by the TLS Feature OID, but + # OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID, + # and the shortname field is just "UNDEF" + must_staple_exts = [e for e in csr.get_extensions() + if e.get_data() == b"0\x03\x02\x01\x05"] + self.assertEqual(len(must_staple_exts), 1, + "Expected exactly one Must Staple extension") + + +class DumpPyopensslChainTest(unittest.TestCase): + """Test for dump_pyopenssl_chain.""" + + @classmethod + def _call(cls, loaded): + # pylint: disable=protected-access + from acme.crypto_util import dump_pyopenssl_chain + return dump_pyopenssl_chain(loaded) + + def test_dump_pyopenssl_chain(self): + names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] + loaded = [test_util.load_cert(name) for name in names] + length = sum( + len(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)) + for cert in loaded) + self.assertEqual(len(self._call(loaded)), length) + + def test_dump_pyopenssl_chain_wrapped(self): + names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] + loaded = [test_util.load_cert(name) for name in names] + wrap_func = jose.ComparableX509 + wrapped = [wrap_func(cert) for cert in loaded] + dump_func = OpenSSL.crypto.dump_certificate + length = sum(len(dump_func(OpenSSL.crypto.FILETYPE_PEM, cert)) for cert in loaded) + self.assertEqual(len(self._call(wrapped)), length) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/errors_test.py b/acme/tests/errors_test.py new file mode 100644 index 000000000..1e5f3d479 --- /dev/null +++ b/acme/tests/errors_test.py @@ -0,0 +1,53 @@ +"""Tests for acme.errors.""" +import unittest + +import mock + + +class BadNonceTest(unittest.TestCase): + """Tests for acme.errors.BadNonce.""" + + def setUp(self): + from acme.errors import BadNonce + self.error = BadNonce(nonce="xxx", error="error") + + def test_str(self): + self.assertEqual("Invalid nonce ('xxx'): error", str(self.error)) + + +class MissingNonceTest(unittest.TestCase): + """Tests for acme.errors.MissingNonce.""" + + def setUp(self): + from acme.errors import MissingNonce + self.response = mock.MagicMock(headers={}) + self.response.request.method = 'FOO' + self.error = MissingNonce(self.response) + + def test_str(self): + self.assertTrue("FOO" in str(self.error)) + self.assertTrue("{}" in str(self.error)) + + +class PollErrorTest(unittest.TestCase): + """Tests for acme.errors.PollError.""" + + def setUp(self): + from acme.errors import PollError + self.timeout = PollError( + exhausted=set([mock.sentinel.AR]), + updated={}) + self.invalid = PollError(exhausted=set(), updated={ + mock.sentinel.AR: mock.sentinel.AR2}) + + def test_timeout(self): + self.assertTrue(self.timeout.timeout) + self.assertFalse(self.invalid.timeout) + + def test_repr(self): + self.assertEqual('PollError(exhausted=%s, updated={sentinel.AR: ' + 'sentinel.AR2})' % repr(set()), repr(self.invalid)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/acme/tests/fields_test.py b/acme/tests/fields_test.py new file mode 100644 index 000000000..69dde8b89 --- /dev/null +++ b/acme/tests/fields_test.py @@ -0,0 +1,72 @@ +"""Tests for acme.fields.""" +import datetime +import unittest + +import josepy as jose +import pytz + + +class FixedTest(unittest.TestCase): + """Tests for acme.fields.Fixed.""" + + def setUp(self): + from acme.fields import Fixed + self.field = Fixed('name', 'x') + + def test_decode(self): + self.assertEqual('x', self.field.decode('x')) + + def test_decode_bad(self): + self.assertRaises(jose.DeserializationError, self.field.decode, 'y') + + def test_encode(self): + self.assertEqual('x', self.field.encode('x')) + + def test_encode_override(self): + self.assertEqual('y', self.field.encode('y')) + + +class RFC3339FieldTest(unittest.TestCase): + """Tests for acme.fields.RFC3339Field.""" + + def setUp(self): + self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.utc) + self.encoded = '2015-03-27T00:00:00Z' + + def test_default_encoder(self): + from acme.fields import RFC3339Field + self.assertEqual( + self.encoded, RFC3339Field.default_encoder(self.decoded)) + + def test_default_encoder_naive_fails(self): + from acme.fields import RFC3339Field + self.assertRaises( + ValueError, RFC3339Field.default_encoder, datetime.datetime.now()) + + def test_default_decoder(self): + from acme.fields import RFC3339Field + self.assertEqual( + self.decoded, RFC3339Field.default_decoder(self.encoded)) + + def test_default_decoder_raises_deserialization_error(self): + from acme.fields import RFC3339Field + self.assertRaises( + jose.DeserializationError, RFC3339Field.default_decoder, '') + + +class ResourceTest(unittest.TestCase): + """Tests for acme.fields.Resource.""" + + def setUp(self): + from acme.fields import Resource + self.field = Resource('x') + + def test_decode_good(self): + self.assertEqual('x', self.field.decode('x')) + + def test_decode_wrong(self): + self.assertRaises(jose.DeserializationError, self.field.decode, 'y') + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/jose_test.py b/acme/tests/jose_test.py new file mode 100644 index 000000000..340624a4f --- /dev/null +++ b/acme/tests/jose_test.py @@ -0,0 +1,53 @@ +"""Tests for acme.jose shim.""" +import importlib +import unittest + +class JoseTest(unittest.TestCase): + """Tests for acme.jose shim.""" + + def _test_it(self, submodule, attribute): + if submodule: + acme_jose_path = 'acme.jose.' + submodule + josepy_path = 'josepy.' + submodule + else: + acme_jose_path = 'acme.jose' + josepy_path = 'josepy' + acme_jose_mod = importlib.import_module(acme_jose_path) + josepy_mod = importlib.import_module(josepy_path) + + self.assertIs(acme_jose_mod, josepy_mod) + self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) + + # We use the imports below with eval, but pylint doesn't + # understand that. + # pylint: disable=eval-used,unused-variable + import acme + import josepy + acme_jose_mod = eval(acme_jose_path) + josepy_mod = eval(josepy_path) + self.assertIs(acme_jose_mod, josepy_mod) + self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) + + def test_top_level(self): + self._test_it('', 'RS512') + + def test_submodules(self): + # This test ensures that the modules in josepy that were + # available at the time it was moved into its own package are + # available under acme.jose. Backwards compatibility with new + # modules or testing code is not maintained. + mods_and_attrs = [('b64', 'b64decode',), + ('errors', 'Error',), + ('interfaces', 'JSONDeSerializable',), + ('json_util', 'Field',), + ('jwa', 'HS256',), + ('jwk', 'JWK',), + ('jws', 'JWS',), + ('util', 'ImmutableMap',),] + + for mod, attr in mods_and_attrs: + self._test_it(mod, attr) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/jws_test.py b/acme/tests/jws_test.py new file mode 100644 index 000000000..e43ed55e6 --- /dev/null +++ b/acme/tests/jws_test.py @@ -0,0 +1,67 @@ +"""Tests for acme.jws.""" +import unittest + +import josepy as jose + +import test_util + + +KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) + + +class HeaderTest(unittest.TestCase): + """Tests for acme.jws.Header.""" + + good_nonce = jose.encode_b64jose(b'foo') + wrong_nonce = u'F' + # Following just makes sure wrong_nonce is wrong + try: + jose.b64decode(wrong_nonce) + except (ValueError, TypeError): + assert True + else: + assert False # pragma: no cover + + def test_nonce_decoder(self): + from acme.jws import Header + nonce_field = Header._fields['nonce'] + + self.assertRaises( + jose.DeserializationError, nonce_field.decode, self.wrong_nonce) + self.assertEqual(b'foo', nonce_field.decode(self.good_nonce)) + + +class JWSTest(unittest.TestCase): + """Tests for acme.jws.JWS.""" + + def setUp(self): + self.privkey = KEY + self.pubkey = self.privkey.public_key() + self.nonce = jose.b64encode(b'Nonce') + self.url = 'hi' + self.kid = 'baaaaa' + + def test_kid_serialize(self): + from acme.jws import JWS + jws = JWS.sign(payload=b'foo', key=self.privkey, + alg=jose.RS256, nonce=self.nonce, + url=self.url, kid=self.kid) + self.assertEqual(jws.signature.combined.nonce, self.nonce) + self.assertEqual(jws.signature.combined.url, self.url) + self.assertEqual(jws.signature.combined.kid, self.kid) + self.assertEqual(jws.signature.combined.jwk, None) + # TODO: check that nonce is in protected header + + self.assertEqual(jws, JWS.from_json(jws.to_json())) + + def test_jwk_serialize(self): + from acme.jws import JWS + jws = JWS.sign(payload=b'foo', key=self.privkey, + alg=jose.RS256, nonce=self.nonce, + url=self.url) + self.assertEqual(jws.signature.combined.kid, None) + self.assertEqual(jws.signature.combined.jwk, self.pubkey) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/magic_typing_test.py b/acme/tests/magic_typing_test.py new file mode 100644 index 000000000..23dfe3367 --- /dev/null +++ b/acme/tests/magic_typing_test.py @@ -0,0 +1,41 @@ +"""Tests for acme.magic_typing.""" +import sys +import unittest + +import mock + + +class MagicTypingTest(unittest.TestCase): + """Tests for acme.magic_typing.""" + def test_import_success(self): + try: + import typing as temp_typing + except ImportError: # pragma: no cover + temp_typing = None # pragma: no cover + typing_class_mock = mock.MagicMock() + text_mock = mock.MagicMock() + typing_class_mock.Text = text_mock + sys.modules['typing'] = typing_class_mock + if 'acme.magic_typing' in sys.modules: + del sys.modules['acme.magic_typing'] # pragma: no cover + from acme.magic_typing import Text # pylint: disable=no-name-in-module + self.assertEqual(Text, text_mock) + del sys.modules['acme.magic_typing'] + sys.modules['typing'] = temp_typing + + def test_import_failure(self): + try: + import typing as temp_typing + except ImportError: # pragma: no cover + temp_typing = None # pragma: no cover + sys.modules['typing'] = None + if 'acme.magic_typing' in sys.modules: + del sys.modules['acme.magic_typing'] # pragma: no cover + from acme.magic_typing import Text # pylint: disable=no-name-in-module + self.assertTrue(Text is None) + del sys.modules['acme.magic_typing'] + sys.modules['typing'] = temp_typing + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/messages_test.py b/acme/tests/messages_test.py new file mode 100644 index 000000000..269970b1c --- /dev/null +++ b/acme/tests/messages_test.py @@ -0,0 +1,476 @@ +"""Tests for acme.messages.""" +import unittest + +import josepy as jose +import mock + +from acme import challenges +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + +import test_util + +CERT = test_util.load_comparable_cert('cert.der') +CSR = test_util.load_comparable_csr('csr.der') +KEY = test_util.load_rsa_private_key('rsa512_key.pem') + + +class ErrorTest(unittest.TestCase): + """Tests for acme.messages.Error.""" + + def setUp(self): + from acme.messages import Error, ERROR_PREFIX + self.error = Error( + detail='foo', typ=ERROR_PREFIX + 'malformed', title='title') + self.jobj = { + 'detail': 'foo', + 'title': 'some title', + 'type': ERROR_PREFIX + 'malformed', + } + self.error_custom = Error(typ='custom', detail='bar') + self.empty_error = Error() + self.jobj_custom = {'type': 'custom', 'detail': 'bar'} + + def test_default_typ(self): + from acme.messages import Error + self.assertEqual(Error().typ, 'about:blank') + + def test_from_json_empty(self): + from acme.messages import Error + self.assertEqual(Error(), Error.from_json('{}')) + + def test_from_json_hashable(self): + from acme.messages import Error + hash(Error.from_json(self.error.to_json())) + + def test_description(self): + self.assertEqual( + 'The request message was malformed', self.error.description) + self.assertTrue(self.error_custom.description is None) + + def test_code(self): + from acme.messages import Error + self.assertEqual('malformed', self.error.code) + self.assertEqual(None, self.error_custom.code) + self.assertEqual(None, Error().code) + + def test_is_acme_error(self): + from acme.messages import is_acme_error + self.assertTrue(is_acme_error(self.error)) + self.assertFalse(is_acme_error(self.error_custom)) + self.assertFalse(is_acme_error(self.empty_error)) + self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}")) + + def test_unicode_error(self): + from acme.messages import Error, ERROR_PREFIX, is_acme_error + arabic_error = Error( + detail=u'\u0639\u062f\u0627\u0644\u0629', typ=ERROR_PREFIX + 'malformed', + title='title') + self.assertTrue(is_acme_error(arabic_error)) + + def test_with_code(self): + from acme.messages import Error, is_acme_error + self.assertTrue(is_acme_error(Error.with_code('badCSR'))) + self.assertRaises(ValueError, Error.with_code, 'not an ACME error code') + + def test_str(self): + self.assertEqual( + str(self.error), + u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}" + .format(self.error)) + + +class ConstantTest(unittest.TestCase): + """Tests for acme.messages._Constant.""" + + def setUp(self): + from acme.messages import _Constant + + class MockConstant(_Constant): # pylint: disable=missing-docstring + POSSIBLE_NAMES = {} # type: Dict + + self.MockConstant = MockConstant # pylint: disable=invalid-name + self.const_a = MockConstant('a') + self.const_b = MockConstant('b') + + def test_to_partial_json(self): + self.assertEqual('a', self.const_a.to_partial_json()) + self.assertEqual('b', self.const_b.to_partial_json()) + + def test_from_json(self): + self.assertEqual(self.const_a, self.MockConstant.from_json('a')) + self.assertRaises( + jose.DeserializationError, self.MockConstant.from_json, 'c') + + def test_from_json_hashable(self): + hash(self.MockConstant.from_json('a')) + + def test_repr(self): + self.assertEqual('MockConstant(a)', repr(self.const_a)) + self.assertEqual('MockConstant(b)', repr(self.const_b)) + + def test_equality(self): + const_a_prime = self.MockConstant('a') + self.assertFalse(self.const_a == self.const_b) + self.assertTrue(self.const_a == const_a_prime) + + self.assertTrue(self.const_a != self.const_b) + self.assertFalse(self.const_a != const_a_prime) + + +class DirectoryTest(unittest.TestCase): + """Tests for acme.messages.Directory.""" + + def setUp(self): + from acme.messages import Directory + self.dir = Directory({ + 'new-reg': 'reg', + mock.MagicMock(resource_type='new-cert'): 'cert', + 'meta': Directory.Meta( + terms_of_service='https://example.com/acme/terms', + website='https://www.example.com/', + caa_identities=['example.com'], + ), + }) + + def test_init_wrong_key_value_success(self): # pylint: disable=no-self-use + from acme.messages import Directory + Directory({'foo': 'bar'}) + + def test_getitem(self): + self.assertEqual('reg', self.dir['new-reg']) + from acme.messages import NewRegistration + self.assertEqual('reg', self.dir[NewRegistration]) + self.assertEqual('reg', self.dir[NewRegistration()]) + + def test_getitem_fails_with_key_error(self): + self.assertRaises(KeyError, self.dir.__getitem__, 'foo') + + def test_getattr(self): + self.assertEqual('reg', self.dir.new_reg) + + def test_getattr_fails_with_attribute_error(self): + self.assertRaises(AttributeError, self.dir.__getattr__, 'foo') + + def test_to_json(self): + self.assertEqual(self.dir.to_json(), { + 'new-reg': 'reg', + 'new-cert': 'cert', + 'meta': { + 'terms-of-service': 'https://example.com/acme/terms', + 'website': 'https://www.example.com/', + 'caaIdentities': ['example.com'], + }, + }) + + def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use + from acme.messages import Directory + Directory.from_json({'foo': 'bar'}) + + def test_iter_meta(self): + result = False + for k in self.dir.meta: + if k == 'terms_of_service': + result = self.dir.meta[k] == 'https://example.com/acme/terms' + self.assertTrue(result) + + +class ExternalAccountBindingTest(unittest.TestCase): + def setUp(self): + from acme.messages import Directory + self.key = jose.jwk.JWKRSA(key=KEY.public_key()) + self.kid = "kid-for-testing" + self.hmac_key = "hmac-key-for-testing" + self.dir = Directory({ + 'newAccount': 'http://url/acme/new-account', + }) + + def test_from_data(self): + from acme.messages import ExternalAccountBinding + eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir) + + self.assertEqual(len(eab), 3) + self.assertEqual(sorted(eab.keys()), sorted(['protected', 'payload', 'signature'])) + + +class RegistrationTest(unittest.TestCase): + """Tests for acme.messages.Registration.""" + + def setUp(self): + key = jose.jwk.JWKRSA(key=KEY.public_key()) + contact = ( + 'mailto:admin@foo.com', + 'tel:1234', + ) + agreement = 'https://letsencrypt.org/terms' + + from acme.messages import Registration + self.reg = Registration(key=key, contact=contact, agreement=agreement) + self.reg_none = Registration() + + self.jobj_to = { + 'contact': contact, + 'agreement': agreement, + 'key': key, + } + self.jobj_from = self.jobj_to.copy() + self.jobj_from['key'] = key.to_json() + + def test_from_data(self): + from acme.messages import Registration + reg = Registration.from_data(phone='1234', email='admin@foo.com') + self.assertEqual(reg.contact, ( + 'tel:1234', + 'mailto:admin@foo.com', + )) + + def test_new_registration_from_data_with_eab(self): + from acme.messages import NewRegistration, ExternalAccountBinding, Directory + key = jose.jwk.JWKRSA(key=KEY.public_key()) + kid = "kid-for-testing" + hmac_key = "hmac-key-for-testing" + directory = Directory({ + 'newAccount': 'http://url/acme/new-account', + }) + eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory) + reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab) + self.assertEqual(reg.contact, ( + 'mailto:admin@foo.com', + )) + self.assertEqual(sorted(reg.external_account_binding.keys()), + sorted(['protected', 'payload', 'signature'])) + + def test_phones(self): + self.assertEqual(('1234',), self.reg.phones) + + def test_emails(self): + self.assertEqual(('admin@foo.com',), self.reg.emails) + + def test_to_partial_json(self): + self.assertEqual(self.jobj_to, self.reg.to_partial_json()) + + def test_from_json(self): + from acme.messages import Registration + self.assertEqual(self.reg, Registration.from_json(self.jobj_from)) + + def test_from_json_hashable(self): + from acme.messages import Registration + hash(Registration.from_json(self.jobj_from)) + + +class UpdateRegistrationTest(unittest.TestCase): + """Tests for acme.messages.UpdateRegistration.""" + + def test_empty(self): + from acme.messages import UpdateRegistration + jstring = '{"resource": "reg"}' + self.assertEqual(jstring, UpdateRegistration().json_dumps()) + self.assertEqual( + UpdateRegistration(), UpdateRegistration.json_loads(jstring)) + + +class RegistrationResourceTest(unittest.TestCase): + """Tests for acme.messages.RegistrationResource.""" + + def setUp(self): + from acme.messages import RegistrationResource + self.regr = RegistrationResource( + body=mock.sentinel.body, uri=mock.sentinel.uri, + terms_of_service=mock.sentinel.terms_of_service) + + def test_to_partial_json(self): + self.assertEqual(self.regr.to_json(), { + 'body': mock.sentinel.body, + 'uri': mock.sentinel.uri, + 'terms_of_service': mock.sentinel.terms_of_service, + }) + + +class ChallengeResourceTest(unittest.TestCase): + """Tests for acme.messages.ChallengeResource.""" + + def test_uri(self): + from acme.messages import ChallengeResource + self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock( + uri='http://challb'), authzr_uri='http://authz').uri) + + +class ChallengeBodyTest(unittest.TestCase): + """Tests for acme.messages.ChallengeBody.""" + + def setUp(self): + self.chall = challenges.DNS(token=jose.b64decode( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) + + from acme.messages import ChallengeBody + from acme.messages import Error + from acme.messages import STATUS_INVALID + self.status = STATUS_INVALID + error = Error(typ='urn:ietf:params:acme:error:serverInternal', + detail='Unable to communicate with DNS server') + self.challb = ChallengeBody( + uri='http://challb', chall=self.chall, status=self.status, + error=error) + + self.jobj_to = { + 'uri': 'http://challb', + 'status': self.status, + 'type': 'dns', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + 'error': error, + } + self.jobj_from = self.jobj_to.copy() + self.jobj_from['status'] = 'invalid' + self.jobj_from['error'] = { + 'type': 'urn:ietf:params:acme:error:serverInternal', + 'detail': 'Unable to communicate with DNS server', + } + + def test_encode(self): + self.assertEqual(self.challb.encode('uri'), self.challb.uri) + + def test_to_partial_json(self): + self.assertEqual(self.jobj_to, self.challb.to_partial_json()) + + def test_from_json(self): + from acme.messages import ChallengeBody + self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from)) + + def test_from_json_hashable(self): + from acme.messages import ChallengeBody + hash(ChallengeBody.from_json(self.jobj_from)) + + def test_proxy(self): + self.assertEqual(jose.b64decode( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'), self.challb.token) + + +class AuthorizationTest(unittest.TestCase): + """Tests for acme.messages.Authorization.""" + + def setUp(self): + from acme.messages import ChallengeBody + from acme.messages import STATUS_VALID + + self.challbs = ( + ChallengeBody( + uri='http://challb1', status=STATUS_VALID, + chall=challenges.HTTP01(token=b'IlirfxKKXAsHtmzK29Pj8A')), + ChallengeBody(uri='http://challb2', status=STATUS_VALID, + chall=challenges.DNS( + token=b'DGyRejmCefe7v4NfDGDKfA')), + ) + combinations = ((0,), (1,)) + + from acme.messages import Authorization + from acme.messages import Identifier + from acme.messages import IDENTIFIER_FQDN + identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com') + self.authz = Authorization( + identifier=identifier, combinations=combinations, + challenges=self.challbs) + + self.jobj_from = { + 'identifier': identifier.to_json(), + 'challenges': [challb.to_json() for challb in self.challbs], + 'combinations': combinations, + } + + def test_from_json(self): + from acme.messages import Authorization + Authorization.from_json(self.jobj_from) + + def test_from_json_hashable(self): + from acme.messages import Authorization + hash(Authorization.from_json(self.jobj_from)) + + def test_resolved_combinations(self): + self.assertEqual(self.authz.resolved_combinations, ( + (self.challbs[0],), + (self.challbs[1],), + )) + + +class AuthorizationResourceTest(unittest.TestCase): + """Tests for acme.messages.AuthorizationResource.""" + + def test_json_de_serializable(self): + from acme.messages import AuthorizationResource + authzr = AuthorizationResource( + uri=mock.sentinel.uri, + body=mock.sentinel.body) + self.assertTrue(isinstance(authzr, jose.JSONDeSerializable)) + + +class CertificateRequestTest(unittest.TestCase): + """Tests for acme.messages.CertificateRequest.""" + + def setUp(self): + from acme.messages import CertificateRequest + self.req = CertificateRequest(csr=CSR) + + def test_json_de_serializable(self): + self.assertTrue(isinstance(self.req, jose.JSONDeSerializable)) + from acme.messages import CertificateRequest + self.assertEqual( + self.req, CertificateRequest.from_json(self.req.to_json())) + + +class CertificateResourceTest(unittest.TestCase): + """Tests for acme.messages.CertificateResourceTest.""" + + def setUp(self): + from acme.messages import CertificateResource + self.certr = CertificateResource( + body=CERT, uri=mock.sentinel.uri, authzrs=(), + cert_chain_uri=mock.sentinel.cert_chain_uri) + + def test_json_de_serializable(self): + self.assertTrue(isinstance(self.certr, jose.JSONDeSerializable)) + from acme.messages import CertificateResource + self.assertEqual( + self.certr, CertificateResource.from_json(self.certr.to_json())) + + +class RevocationTest(unittest.TestCase): + """Tests for acme.messages.RevocationTest.""" + + def setUp(self): + from acme.messages import Revocation + self.rev = Revocation(certificate=CERT) + + def test_from_json_hashable(self): + from acme.messages import Revocation + hash(Revocation.from_json(self.rev.to_json())) + + +class OrderResourceTest(unittest.TestCase): + """Tests for acme.messages.OrderResource.""" + + def setUp(self): + from acme.messages import OrderResource + self.regr = OrderResource( + body=mock.sentinel.body, uri=mock.sentinel.uri) + + def test_to_partial_json(self): + self.assertEqual(self.regr.to_json(), { + 'body': mock.sentinel.body, + 'uri': mock.sentinel.uri, + 'authorizations': None, + }) + +class NewOrderTest(unittest.TestCase): + """Tests for acme.messages.NewOrder.""" + + def setUp(self): + from acme.messages import NewOrder + self.reg = NewOrder( + identifiers=mock.sentinel.identifiers) + + def test_to_partial_json(self): + self.assertEqual(self.reg.to_json(), { + 'identifiers': mock.sentinel.identifiers, + }) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/standalone_test.py b/acme/tests/standalone_test.py new file mode 100644 index 000000000..0be57bad4 --- /dev/null +++ b/acme/tests/standalone_test.py @@ -0,0 +1,190 @@ +"""Tests for acme.standalone.""" +import socket +import threading +import unittest + +from six.moves import http_client # pylint: disable=import-error +from six.moves import socketserver # type: ignore # pylint: disable=import-error + +import josepy as jose +import mock +import requests + +from acme import challenges +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module + +import test_util + +class TLSServerTest(unittest.TestCase): + """Tests for acme.standalone.TLSServer.""" + + + def test_bind(self): # pylint: disable=no-self-use + from acme.standalone import TLSServer + server = TLSServer( + ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True) + server.server_close() + + def test_ipv6(self): + if socket.has_ipv6: + from acme.standalone import TLSServer + server = TLSServer( + ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True, ipv6=True) + server.server_close() + + +class HTTP01ServerTest(unittest.TestCase): + """Tests for acme.standalone.HTTP01Server.""" + + + def setUp(self): + self.account_key = jose.JWK.load( + test_util.load_vector('rsa1024_key.pem')) + self.resources = set() # type: Set + + from acme.standalone import HTTP01Server + self.server = HTTP01Server(('', 0), resources=self.resources) + + self.port = self.server.socket.getsockname()[1] + self.thread = threading.Thread(target=self.server.serve_forever) + self.thread.start() + + def tearDown(self): + self.server.shutdown() + self.thread.join() + + def test_index(self): + response = requests.get( + 'http://localhost:{0}'.format(self.port), verify=False) + self.assertEqual( + response.text, 'ACME client standalone challenge solver') + self.assertTrue(response.ok) + + def test_404(self): + response = requests.get( + 'http://localhost:{0}/foo'.format(self.port), verify=False) + self.assertEqual(response.status_code, http_client.NOT_FOUND) + + def _test_http01(self, add): + chall = challenges.HTTP01(token=(b'x' * 16)) + response, validation = chall.response_and_validation(self.account_key) + + from acme.standalone import HTTP01RequestHandler + resource = HTTP01RequestHandler.HTTP01Resource( + chall=chall, response=response, validation=validation) + if add: + self.resources.add(resource) + return resource.response.simple_verify( + resource.chall, 'localhost', self.account_key.public_key(), + port=self.port) + + def test_http01_found(self): + self.assertTrue(self._test_http01(add=True)) + + def test_http01_not_found(self): + self.assertFalse(self._test_http01(add=False)) + + +class BaseDualNetworkedServersTest(unittest.TestCase): + """Test for acme.standalone.BaseDualNetworkedServers.""" + + + class SingleProtocolServer(socketserver.TCPServer): + """Server that only serves on a single protocol. FreeBSD has this behavior for AF_INET6.""" + def __init__(self, *args, **kwargs): + ipv6 = kwargs.pop("ipv6", False) + if ipv6: + self.address_family = socket.AF_INET6 + kwargs["bind_and_activate"] = False + else: + self.address_family = socket.AF_INET + socketserver.TCPServer.__init__(self, *args, **kwargs) + if ipv6: + # NB: On Windows, socket.IPPROTO_IPV6 constant may be missing. + # We use the corresponding value (41) instead. + level = getattr(socket, "IPPROTO_IPV6", 41) + self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1) + try: + self.server_bind() + self.server_activate() + except: + self.server_close() + raise + + @mock.patch("socket.socket.bind") + def test_fail_to_bind(self, mock_bind): + mock_bind.side_effect = socket.error + from acme.standalone import BaseDualNetworkedServers + self.assertRaises(socket.error, BaseDualNetworkedServers, + BaseDualNetworkedServersTest.SingleProtocolServer, + ('', 0), + socketserver.BaseRequestHandler) + + def test_ports_equal(self): + from acme.standalone import BaseDualNetworkedServers + servers = BaseDualNetworkedServers( + BaseDualNetworkedServersTest.SingleProtocolServer, + ('', 0), + socketserver.BaseRequestHandler) + socknames = servers.getsocknames() + prev_port = None + # assert ports are equal + for sockname in socknames: + port = sockname[1] + if prev_port: + self.assertEqual(prev_port, port) + prev_port = port + + +class HTTP01DualNetworkedServersTest(unittest.TestCase): + """Tests for acme.standalone.HTTP01DualNetworkedServers.""" + + + def setUp(self): + self.account_key = jose.JWK.load( + test_util.load_vector('rsa1024_key.pem')) + self.resources = set() # type: Set + + from acme.standalone import HTTP01DualNetworkedServers + self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources) + + self.port = self.servers.getsocknames()[0][1] + self.servers.serve_forever() + + def tearDown(self): + self.servers.shutdown_and_server_close() + + def test_index(self): + response = requests.get( + 'http://localhost:{0}'.format(self.port), verify=False) + self.assertEqual( + response.text, 'ACME client standalone challenge solver') + self.assertTrue(response.ok) + + def test_404(self): + response = requests.get( + 'http://localhost:{0}/foo'.format(self.port), verify=False) + self.assertEqual(response.status_code, http_client.NOT_FOUND) + + def _test_http01(self, add): + chall = challenges.HTTP01(token=(b'x' * 16)) + response, validation = chall.response_and_validation(self.account_key) + + from acme.standalone import HTTP01RequestHandler + resource = HTTP01RequestHandler.HTTP01Resource( + chall=chall, response=response, validation=validation) + if add: + self.resources.add(resource) + return resource.response.simple_verify( + resource.chall, 'localhost', self.account_key.public_key(), + port=self.port) + + def test_http01_found(self): + self.assertTrue(self._test_http01(add=True)) + + def test_http01_not_found(self): + self.assertFalse(self._test_http01(add=False)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/acme/tests/test_util.py b/acme/tests/test_util.py new file mode 100644 index 000000000..6737bff4e --- /dev/null +++ b/acme/tests/test_util.py @@ -0,0 +1,68 @@ +"""Test utilities. + +.. warning:: This module is not part of the public API. + +""" +import os +import pkg_resources + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +import josepy as jose +from OpenSSL import crypto + + +def load_vector(*names): + """Load contents of a test vector.""" + # luckily, resource_string opens file in binary mode + return pkg_resources.resource_string( + __name__, os.path.join('testdata', *names)) + + +def _guess_loader(filename, loader_pem, loader_der): + _, ext = os.path.splitext(filename) + if ext.lower() == '.pem': + return loader_pem + elif ext.lower() == '.der': + return loader_der + else: # pragma: no cover + raise ValueError("Loader could not be recognized based on extension") + + +def load_cert(*names): + """Load certificate.""" + loader = _guess_loader( + names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) + return crypto.load_certificate(loader, load_vector(*names)) + + +def load_comparable_cert(*names): + """Load ComparableX509 cert.""" + return jose.ComparableX509(load_cert(*names)) + + +def load_csr(*names): + """Load certificate request.""" + loader = _guess_loader( + names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) + return crypto.load_certificate_request(loader, load_vector(*names)) + + +def load_comparable_csr(*names): + """Load ComparableX509 certificate request.""" + return jose.ComparableX509(load_csr(*names)) + + +def load_rsa_private_key(*names): + """Load RSA private key.""" + loader = _guess_loader(names[-1], serialization.load_pem_private_key, + serialization.load_der_private_key) + return jose.ComparableRSAKey(loader( + load_vector(*names), password=None, backend=default_backend())) + + +def load_pyopenssl_private_key(*names): + """Load pyOpenSSL private key.""" + loader = _guess_loader( + names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) + return crypto.load_privatekey(loader, load_vector(*names)) diff --git a/acme/tests/testdata/README b/acme/tests/testdata/README new file mode 100644 index 000000000..dfe3f5405 --- /dev/null +++ b/acme/tests/testdata/README @@ -0,0 +1,15 @@ +In order for acme.test_util._guess_loader to work properly, make sure +to use appropriate extension for vector filenames: .pem for PEM and +.der for DER. + +The following command has been used to generate test keys: + + for x in 256 512 1024 2048; do openssl genrsa -out rsa${k}_key.pem $k; done + +and for the CSR: + + openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der + +and for the certificate: + + openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der diff --git a/acme/tests/testdata/cert-100sans.pem b/acme/tests/testdata/cert-100sans.pem new file mode 100644 index 000000000..3fdc9404f --- /dev/null +++ b/acme/tests/testdata/cert-100sans.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV +BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv +bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X +DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU +BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp +ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B +AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 +rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T +BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t +ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt +cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j +b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN +ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh +bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs +ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx +LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv +bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN +ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh +bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs +ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3 +LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv +bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN +ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh +bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs +ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz +LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv +bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN +ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh +bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs +ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5 +LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv +bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN +ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh +bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs +ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1 +LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv +bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN +ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh +bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs +ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN +AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ +XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/cert-idnsans.pem b/acme/tests/testdata/cert-idnsans.pem new file mode 100644 index 000000000..932649692 --- /dev/null +++ b/acme/tests/testdata/cert-idnsans.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV +BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv +bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X +DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU +BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp +ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B +AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 +rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T +BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I +z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g +z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z +z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM +2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf +2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3 +2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi +2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi +2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs +aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN +247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p +bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4 +27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt +4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh +oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh +oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh +oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm +4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2 +LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT +TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/cert-nocn.der b/acme/tests/testdata/cert-nocn.der new file mode 100644 index 000000000..59da83ccc Binary files /dev/null and b/acme/tests/testdata/cert-nocn.der differ diff --git a/acme/tests/testdata/cert-san.pem b/acme/tests/testdata/cert-san.pem new file mode 100644 index 000000000..dcb835994 --- /dev/null +++ b/acme/tests/testdata/cert-san.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt +cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF +nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 +RDjyGMKy5ZgM2w== +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/cert.der b/acme/tests/testdata/cert.der new file mode 100644 index 000000000..ab231982f Binary files /dev/null and b/acme/tests/testdata/cert.der differ diff --git a/acme/tests/testdata/cert.pem b/acme/tests/testdata/cert.pem new file mode 100644 index 000000000..96c55cbf4 --- /dev/null +++ b/acme/tests/testdata/cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll +vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn +B/o= +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/critical-san.pem b/acme/tests/testdata/critical-san.pem new file mode 100644 index 000000000..7aec8ab1c --- /dev/null +++ b/acme/tests/testdata/critical-san.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIErTCCA5WgAwIBAgIKETb7VQAAAAAdGTANBgkqhkiG9w0BAQsFADCBkTELMAkG +A1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5 +MRUwEwYDVQQKEwxWZW5hZmksIEluYy4xHzAdBgNVBAsTFkRlbW9uc3RyYXRpb24g +U2VydmljZXMxIjAgBgNVBAMTGVZlbmFmaSBFeGFtcGxlIElzc3VpbmcgQ0EwHhcN +MTcwNzEwMjMxNjA1WhcNMTcwODA5MjMxNjA1WjAAMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA7CU5qRIzCs9hCRiSUvLZ8r81l4zIYbx1V1vZz6x1cS4M +0keNfFJ1wB+zuvx80KaMYkWPYlg4Rsm9Ok3ZapakXDlaWtrfg78lxtHuPw1o7AYV +EXDwwPkNugLMJfYw5hWYSr8PCLcOJoY00YQ0fJ44L+kVsUyGjN4UTRRZmOh/yNVU +0W12dTCz4X7BAW01OuY6SxxwewnW3sBEep+APfr2jd/oIx7fgZmVB8aRCDPj4AFl +XINWIwxmptOwnKPbwLN/vhCvJRUkO6rA8lpYwQkedFf6fHhqi2Sq/NCEOg4RvMCF +fKbMpncOXxz+f4/i43SVLrPz/UyhjNbKGJZ+zFrQowIDAQABo4IBlTCCAZEwPgYD +VR0RAQH/BDQwMoIbY2hpY2Fnby1jdWJzLnZlbmFmaS5leGFtcGxlghNjdWJzLnZl +bmFmaS5leGFtcGxlMB0GA1UdDgQWBBTgKZXVSFNyPHHtO/phtIALPcCF5DAfBgNV +HSMEGDAWgBT/JJ6Wei/pzf+9DRHuv6Wgdk2HsjBSBgNVHR8ESzBJMEegRaBDhkFo +dHRwOi8vcGtpLnZlbmFmaS5leGFtcGxlL2NybC9WZW5hZmklMjBFeGFtcGxlJTIw +SXNzdWluZyUyMENBLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0 +dHA6Ly9wa2kudmVuYWZpLmV4YW1wbGUvb2NzcDAOBgNVHQ8BAf8EBAMCBaAwPQYJ +KwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhIDLGYTvsSSEnZ8ehvD5UofP4hMEgobv +DIGy4mcCAWQCAQIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGwYJKwYBBAGCNxUKBA4w +DDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEA3YW4t1AzxEn384OqdU6L +ny8XkMhWpRM0W0Z9ZC3gRZKbVUu49nG/KB5hbVn/de33zdX9HOZJKc0vXzkGZQUs +OUCCsKX4VKzV5naGXOuGRbvV4CJh5P0kPlDzyb5t312S49nJdcdBf0Y/uL5Qzhst +bXy8qNfFNG3SIKKRAUpqE9OVIl+F+JBwexa+v/4dFtUOqMipfXxB3TaxnDqvU1dS +yO34ZTvIMGXJIZ5nn/d/LNc3N3vBg2SHkMpladqw0Hr7mL0bFOe0b+lJgkDP06Be +n08fikhz1j2AW4/ZHa9w4DUz7J21+RtHMhh+Vd1On0EAeZ563svDe7Z+yrg6zOVv +KA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/acme/tests/testdata/csr-100sans.pem b/acme/tests/testdata/csr-100sans.pem new file mode 100644 index 000000000..199814126 --- /dev/null +++ b/acme/tests/testdata/csr-100sans.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz +Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt +H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 +lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv +bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh +bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu +Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C +DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4 +YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w +bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy +MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j +b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C +DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4 +YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w +bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz +Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j +b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C +DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4 +YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w +bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1 +My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j +b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C +DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4 +YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w +bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2 +OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j +b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C +DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4 +YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w +bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4 +NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j +b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C +DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4 +YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w +bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3 +DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo +duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg== +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-6sans.pem b/acme/tests/testdata/csr-6sans.pem new file mode 100644 index 000000000..8f6b52bd7 --- /dev/null +++ b/acme/tests/testdata/csr-6sans.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw +EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0 +9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG +9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL +ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t +ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd +k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv +IvzVBz/nD11drfz/RNuX +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-idnsans.pem b/acme/tests/testdata/csr-idnsans.pem new file mode 100644 index 000000000..d6e91a420 --- /dev/null +++ b/acme/tests/testdata/csr-idnsans.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz +Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt +H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 +lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP +iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P +oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP +s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ +jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z +n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ +t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC +YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa +otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh +bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb +jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu +aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb +uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg +reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5 +4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ +4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ +4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh +puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh +ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr +dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng== +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-nosans.pem b/acme/tests/testdata/csr-nosans.pem new file mode 100644 index 000000000..813db67b0 --- /dev/null +++ b/acme/tests/testdata/csr-nosans.pem @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt +cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn +BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz +AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo +wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA= +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-san.pem b/acme/tests/testdata/csr-san.pem new file mode 100644 index 000000000..a7128e35c --- /dev/null +++ b/acme/tests/testdata/csr-san.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw +EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f +p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN +AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t +MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy +tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A== +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr.der b/acme/tests/testdata/csr.der new file mode 100644 index 000000000..d43ac85a1 Binary files /dev/null and b/acme/tests/testdata/csr.der differ diff --git a/acme/tests/testdata/csr.pem b/acme/tests/testdata/csr.pem new file mode 100644 index 000000000..b6818e39d --- /dev/null +++ b/acme/tests/testdata/csr.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw +EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f +p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoCkwJwYJKoZIhvcN +AQkOMRowGDAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAANB +AHJH/O6BtC9aGzEVCMGOZ7z9iIRHWSzr9x/bOzn7hLwsbXPAgO1QxEwL+X+4g20G +n9XBE1N9W6HCIEut2d8wACg= +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/dsa512_key.pem b/acme/tests/testdata/dsa512_key.pem new file mode 100644 index 000000000..78e164712 --- /dev/null +++ b/acme/tests/testdata/dsa512_key.pem @@ -0,0 +1,14 @@ +-----BEGIN DSA PARAMETERS----- +MIGdAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqfn6GC +OixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSPAkEA +qfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xmrfvl +41pgNJpgu99YOYqPpS0g7A== +-----END DSA PARAMETERS----- +-----BEGIN DSA PRIVATE KEY----- +MIH5AgEAAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqf +n6GCOixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSP +AkEAqfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xm +rfvl41pgNJpgu99YOYqPpS0g7AJATQ2LUzjGQSM6UljcPY5I2OD9THkUR9kH2tth +zZd70UoI9btrVaTizgqYShuok94glSQNK0H92JgUk3scJPaAkAIVAMDn61h6vrCE +mNv063So6E+eYaIN +-----END DSA PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa1024_key.pem b/acme/tests/testdata/rsa1024_key.pem new file mode 100644 index 000000000..de5339d03 --- /dev/null +++ b/acme/tests/testdata/rsa1024_key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCaifO0fGlcAcjjcYEAPYcIL0Hf0KiNa9VCJ14RBdlZxLWRrVFi +4tdNCKSKqzKuKrrA8DWd4PHFD7UpLyRrPPXY6GozAyCT+5UFBClGJ2KyNKu+eU6/ +w4C1kpO4lpeXs8ptFc1lA9P8V1M/MkWzTE402nPNK0uUmZNo2tsFpGJUSQIDAQAB +AoGAFjLWxQhSAhtnhfRZ+XTdHrnbFpFchOQGgDgzdPKIJDLzefeRh0jacIBbUmgB +Ia+Vn/1hVkpnsEzvUvkonBbnoYWlYVQdpNTmrrew7SOztf8/1fYCsSkyDAvqGTXc +TmHM0PaLS+junoWcKOvQRVb0N3k+43OnBkr2b393Sx30qGECQQDNO2IBWOsYs8cB +CZQAZs8zBlbwBFZibqovqpLwXt9adBIsT9XzgagGbJMpzSuoHTUn3QqqJd9uHD8X +UTmmoh4NAkEAwMRauo+PlNj8W1lusflko52KL17+E5cmeOERM2xvhZNpO7d3/1ak +Co9dxVMicrYSh7jXbcXFNt3xNDTv6Dg8LQJAPuJwMDt/pc0IMCAwMkNOP7M0lkyt +73E7QmnAplhblcq0+tDnnLpgsr84BHnyY4u3iuRm7SW3pXSQPGPOB2nrTQJANBXa +HgakWSe4KEal7ljgpITwzZPxOwHgV1EZALgP+hu2l3gfaFLUyDWstKCd8jjYEOwU +6YhCnWyiu+SB3lEzkQJBAJapJpfypFyY8kQNYlYILLBcPu5fmy3QUZKHJ4L3rIVJ +c2UTLMeBBgGFHT04CtWntmjwzSv+V6lwiCxKXsIUySc= +-----END RSA PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa2048_cert.pem b/acme/tests/testdata/rsa2048_cert.pem new file mode 100644 index 000000000..3944cd1db --- /dev/null +++ b/acme/tests/testdata/rsa2048_cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIJALVG/VbBb5U7MA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNV +BAYTAkFVMQswCQYDVQQIDAJXQTEeMBwGA1UEBwwVVGhlIG1pZGRsZSBvZiBub3do +ZXJlMR8wHQYDVQQKDBZDZXJ0Ym90IFRlc3QgQ2VydHMgSW5jMCAXDTE2MTEyODIx +MzUzN1oYDzIyOTAwOTEzMjEzNTM3WjBbMQswCQYDVQQGEwJBVTELMAkGA1UECAwC +V0ExHjAcBgNVBAcMFVRoZSBtaWRkbGUgb2Ygbm93aGVyZTEfMB0GA1UECgwWQ2Vy +dGJvdCBUZXN0IENlcnRzIEluYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANoVT1pdvRUUBOqvm7M2ebLEHV7higUH7qAGUZEkfP6W4YriYVY+IHrH1svN +PSa+oPTK7weDNmT11ehWnGyECIM9z2r2Hi9yVV0ycxh4hWQ4Nt8BAKZwCwaXpyWm +7Gj6m2EzpSN5Dd67g5YAQBrUUh1+RRbFi9c0Ls/6ZOExMvfg8kqt4c2sXCgH1IFn +xvvOjBYop7xh0x3L1Akyax0tw8qgQp/z5mkupmVDNJYPFmbzFPMNyDR61ed6QUTD +g7P4UAuFkejLLzFvz5YaO7vC+huaTuPhInAhpzqpr4yU97KIjos2/83Itu/Cv8U1 +RAeEeRTkh0WjUfltoem/5f8bIdsCAwEAAaNTMFEwHQYDVR0OBBYEFHy+bEYqwvFU +uQLTkIfQ36AM2DQiMB8GA1UdIwQYMBaAFHy+bEYqwvFUuQLTkIfQ36AM2DQiMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH3ANVzB59FcunZV/F8T +RiCD6/gV7Jc3CswU8N8tVjzMCg2jOdTFF9iYZzNNKQvG13o/n5LkQr/lkKRQkWTx +nkE5WZbR7vNqlzXgPa9NBiK5rPjgSt8azPW+Skct3Bj4B3PhTMSpoQ7PsUJ8UeV8 +kTNR5xrRLt6/mLfRJTXWXBM43GEZi8lL5q0nqz0tPGISADshHMo6ZlUu5Hvfp5v+ +aonpO4sVS9hGOVxjGNMXYApEUy4jid9jjAfEk6jeELJMbXGLy/botFgIJK/QPe6P +AfbdFgtg/qzG7Uy0A1iXXfWdgwmVrhCoGYYWCn4yWCAm894QKtdim87CHSDP0WUf +Esg= +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/rsa2048_key.pem b/acme/tests/testdata/rsa2048_key.pem new file mode 100644 index 000000000..5847aed55 --- /dev/null +++ b/acme/tests/testdata/rsa2048_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDaFU9aXb0VFATq +r5uzNnmyxB1e4YoFB+6gBlGRJHz+luGK4mFWPiB6x9bLzT0mvqD0yu8HgzZk9dXo +VpxshAiDPc9q9h4vclVdMnMYeIVkODbfAQCmcAsGl6clpuxo+pthM6UjeQ3eu4OW +AEAa1FIdfkUWxYvXNC7P+mThMTL34PJKreHNrFwoB9SBZ8b7zowWKKe8YdMdy9QJ +MmsdLcPKoEKf8+ZpLqZlQzSWDxZm8xTzDcg0etXnekFEw4Oz+FALhZHoyy8xb8+W +Gju7wvobmk7j4SJwIac6qa+MlPeyiI6LNv/NyLbvwr/FNUQHhHkU5IdFo1H5baHp +v+X/GyHbAgMBAAECggEAURFe4C68XRuGAF+rN2Fmt+djK6QXlGswb1gp9hRkSpd3 +3BLvMAoENOAYnsX6l26Bkr3lQRurmrgv/iBEIaqrJ25QrmgzLFwKE4zvcAdNPsYO +z7MltLktwBOb1MlKVHPkUqvKFXeoikWWUqphKhgHNmN7900UALmrNTDVU0jgs3fB +o35o8d5SjoC52K4wCTjhPyjt4cdbfbziRs2qFhfGdawidRO1xLlDM4tTTW+5yWGK +lt0SwyvDVC6XWeNoT3nXyKjXWP7hcYqm0iS7ffL9YzEC2RXNGQUqeR50i9Y0rDdH +Vqcr+Rqio2ww68zbDWBpC/jU133BSoHuSE1wstxIkQKBgQDxlEr42WJfgdajbZ1a +hUIeLEgvhezLmD1hcYwZuQCLgizmY2ovvmeAH74koCDEsUUQunPYHsRla7wT3q1/ +IkR1KgJPwESpkQaKuAqxeEAkv7Gn8Lzcn22jCoRCfGA68wKJz2ECFZDc0RDvRrT/ +9GhiiGUoO47jv9ezrSDO1eu5/QKBgQDnGfYVMNLiA0fy4AxSyY2vdo7vruOFGpRP +n94gwxZ+0dQDWHzn3J4rHivxtcyd/MOZv4I8PtYK7tmmjYv1ngQ6sGl4p8bpUtwj +9++/B1CyB1W5/VPqMkd+Sj0dbejycME55+F6/r4basPXxBFFCfknjAlVvyvbBhUy +ftNpHxZGtwKBgChJM4t2LPqCW3nbgL8ks9b2SX9rVQbKt4m1dsifWmDpb3VoJMAb +f4UVRg8ziONkMIFOppzm3JeRNMcXflVSMJpdTA9in9CrN60QbfAUfpXiRc0cz1H3 +YEAtM8smlKGf/s9efu3rDMJWNv3AC9UXPAUae8wOypBeYKk8+NilQe89AoGAXEA3 +xFO+CqyGnwQixzVf0qf//NuSRQLMK1DEyc02gJ9gA4niKmgd11Zu8kjBClvo9MnG +wifPJ4Qa6+pa8UwHoinjoF9Q/rit2cnSMS5JXxegd+MRCU7SzS3zYXkLYSPzbhsL +Hh7sYmNnFA1XW3jUtZ2n6EusxPyTn5mS6MaZDNcCgYBelFKFjNIQ50NbOnm8DewK +jUd5OFKowKodlQVcHiF9CVbjvpN8ZPRcBSmqDU4kpT/rmcybVoL6Zfa/zWkw8+Oh +QxKb3BYf5vRUMd/RA+/t5KG4ZOIIYB3qoltAYlhVaINukL6cGVG1qvV/ntcsfsn6 +kmf1UgGFcKrJuXgwEtTVxw== +-----END PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa256_key.pem b/acme/tests/testdata/rsa256_key.pem new file mode 100644 index 000000000..659274d1d --- /dev/null +++ b/acme/tests/testdata/rsa256_key.pem @@ -0,0 +1,6 @@ +-----BEGIN RSA PRIVATE KEY----- +MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh +AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N +E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 +rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt +-----END RSA PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa512_key.pem b/acme/tests/testdata/rsa512_key.pem new file mode 100644 index 000000000..610c8d315 --- /dev/null +++ b/acme/tests/testdata/rsa512_key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 +vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn +elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc +mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp +Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj +8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq +6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ +-----END RSA PRIVATE KEY----- diff --git a/acme/tests/util_test.py b/acme/tests/util_test.py new file mode 100644 index 000000000..00aa8b02d --- /dev/null +++ b/acme/tests/util_test.py @@ -0,0 +1,16 @@ +"""Tests for acme.util.""" +import unittest + + +class MapKeysTest(unittest.TestCase): + """Tests for acme.util.map_keys.""" + + def test_it(self): + from acme.util import map_keys + self.assertEqual({'a': 'b', 'c': 'd'}, + map_keys({'a': 'b', 'c': 'd'}, lambda key: key)) + self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1)) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover -- cgit v1.2.3