diff -Nru python-acme-0.5.0/acme/challenges.py python-acme-0.8.1/acme/challenges.py --- python-acme-0.5.0/acme/challenges.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/acme/challenges.py 2016-06-14 23:46:01.000000000 +0000 @@ -500,7 +500,7 @@ """ return DNSResponse(validation=self.gen_validation( - self, account_key, **kwargs)) + account_key, **kwargs)) def validation_domain_name(self, name): """Domain name for TXT validation record. diff -Nru python-acme-0.5.0/acme/client.py python-acme-0.8.1/acme/client.py --- python-acme-0.5.0/acme/client.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/acme/client.py 2016-06-14 23:46:01.000000000 +0000 @@ -512,6 +512,10 @@ self.verify_ssl = verify_ssl self._nonces = set() self.user_agent = user_agent + self.session = requests.Session() + + def __del__(self): + self.session.close() def _wrap_in_jws(self, obj, nonce): """Wrap `JSONDeSerializable` object in JWS. @@ -606,7 +610,7 @@ kwargs['verify'] = self.verify_ssl kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('User-Agent', self.user_agent) - response = requests.request(method, url, *args, **kwargs) + response = self.session.request(method, url, *args, **kwargs) logging.debug('Received %s. Headers: %s. Content: %r', response, response.headers, response.content) return response diff -Nru python-acme-0.5.0/acme/client_test.py python-acme-0.8.1/acme/client_test.py --- python-acme-0.5.0/acme/client_test.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/acme/client_test.py 2016-06-14 23:46:01.000000000 +0000 @@ -484,9 +484,11 @@ def test_check_response_not_ok_jobj_no_error(self): self.response.ok = False self.response.json.return_value = {} - # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response) + 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 @@ -528,40 +530,49 @@ self.assertEqual( self.response, self.net._check_response(self.response)) - @mock.patch('acme.client.requests') - def test_send_request(self, mock_requests): - mock_requests.request.return_value = 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', 'url', 'foo', bar='baz')) - mock_requests.request.assert_called_once_with( - 'HEAD', 'url', 'foo', verify=mock.ANY, bar='baz', headers=mock.ANY) + '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, bar='baz') - @mock.patch('acme.client.requests') - def test_send_request_verify_ssl(self, mock_requests): + def test_send_request_verify_ssl(self): # pylint: disable=protected-access for verify in True, False: - mock_requests.request.reset_mock() - mock_requests.request.return_value = self.response + 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', 'url')) - mock_requests.request.assert_called_once_with( - 'GET', 'url', verify=verify, headers=mock.ANY) - - @mock.patch('acme.client.requests') - def test_send_request_user_agent(self, mock_requests): - mock_requests.request.return_value = self.response - # pylint: disable=protected-access - self.net._send_request('GET', 'url', headers={'bar': 'baz'}) - mock_requests.request.assert_called_once_with( - 'GET', 'url', verify=mock.ANY, + self.response, + self.net._send_request('GET', 'http://example.com/')) + self.net.session.request.assert_called_once_with( + 'GET', 'http://example.com/', verify=verify, 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, headers={'User-Agent': 'acme-python-test', 'bar': 'baz'}) - self.net._send_request('GET', 'url', headers={'User-Agent': 'foo2'}) - mock_requests.request.assert_called_with( - 'GET', 'url', verify=mock.ANY, headers={'User-Agent': 'foo2'}) + 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, headers={'User-Agent': 'foo2'}) + + def test_del(self): + sess = mock.MagicMock() + self.net.session = sess + del self.net + sess.close.assert_called_once_with() @mock.patch('acme.client.requests') def test_requests_error_passthrough(self, mock_requests): @@ -614,14 +625,16 @@ return self.checked_response def test_head(self): - self.assertEqual(self.response, self.net.head('url', 'foo', bar='baz')) + self.assertEqual(self.response, self.net.head( + 'http://example.com/', 'foo', bar='baz')) self.send_request.assert_called_once_with( - 'HEAD', 'url', 'foo', bar='baz') + 'HEAD', 'http://example.com/', 'foo', bar='baz') def test_get(self): self.assertEqual(self.checked_response, self.net.get( - 'url', content_type=self.content_type, bar='baz')) - self.send_request.assert_called_once_with('GET', 'url', bar='baz') + 'http://example.com/', content_type=self.content_type, bar='baz')) + self.send_request.assert_called_once_with( + 'GET', 'http://example.com/', bar='baz') def test_post(self): # pylint: disable=protected-access diff -Nru python-acme-0.5.0/acme/crypto_util.py python-acme-0.8.1/acme/crypto_util.py --- python-acme-0.5.0/acme/crypto_util.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/acme/crypto_util.py 2016-06-14 23:46:01.000000000 +0000 @@ -1,4 +1,5 @@ """Crypto utilities.""" +import binascii import contextlib import logging import re @@ -203,7 +204,7 @@ """ assert domains, "Must provide one or more hostnames for the cert." cert = OpenSSL.crypto.X509() - cert.set_serial_number(1337) + cert.set_serial_number(int(binascii.hexlify(OpenSSL.rand.bytes(16)), 16)) cert.set_version(2) extensions = [ diff -Nru python-acme-0.5.0/acme/crypto_util_test.py python-acme-0.8.1/acme/crypto_util_test.py --- python-acme-0.5.0/acme/crypto_util_test.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/acme/crypto_util_test.py 2016-06-14 23:46:01.000000000 +0000 @@ -8,6 +8,8 @@ import six from six.moves import socketserver # pylint: disable=import-error +import OpenSSL + from acme import errors from acme import jose from acme import test_util @@ -126,5 +128,23 @@ self._get_idn_names()) +class RandomSnTest(unittest.TestCase): + """Test for random certificate serial numbers.""" + + def setUp(self): + self.cert_count = 5 + self.serial_num = [] + 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) + + if __name__ == '__main__': unittest.main() # pragma: no cover diff -Nru python-acme-0.5.0/acme/messages.py python-acme-0.8.1/acme/messages.py --- python-acme-0.5.0/acme/messages.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/acme/messages.py 2016-06-14 23:46:01.000000000 +0000 @@ -37,9 +37,9 @@ ) ) - typ = jose.Field('type') + typ = jose.Field('type', omitempty=True, default='about:blank') title = jose.Field('title', omitempty=True) - detail = jose.Field('detail') + detail = jose.Field('detail', omitempty=True) @property def description(self): @@ -123,6 +123,12 @@ _REGISTERED_TYPES = {} + class Meta(jose.JSONObjectWithFields): + """Directory Meta.""" + terms_of_service = jose.Field('terms-of-service', omitempty=True) + website = jose.Field('website', omitempty=True) + caa_identities = jose.Field('caa-identities', omitempty=True) + @classmethod def _canon_key(cls, key): return getattr(key, 'resource_type', key) @@ -137,11 +143,6 @@ def __init__(self, jobj): canon_jobj = util.map_keys(jobj, self._canon_key) - if not set(canon_jobj).issubset(self._REGISTERED_TYPES): - # TODO: acme-spec is not clear about this: 'It is a JSON - # dictionary, whose keys are the "resource" values listed - # in {{https-requests}}'z - raise ValueError('Wrong directory fields') # TODO: check that everything is an absolute URL; acme-spec is # not clear on that self._jobj = canon_jobj @@ -163,10 +164,8 @@ @classmethod def from_json(cls, jobj): - try: - return cls(jobj) - except ValueError as error: - raise jose.DeserializationError(str(error)) + jobj['meta'] = cls.Meta.from_json(jobj.pop('meta', {})) + return cls(jobj) class Resource(jose.JSONObjectWithFields): diff -Nru python-acme-0.5.0/acme/messages_test.py python-acme-0.8.1/acme/messages_test.py --- python-acme-0.5.0/acme/messages_test.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/acme/messages_test.py 2016-06-14 23:46:01.000000000 +0000 @@ -28,6 +28,14 @@ self.error_custom = Error(typ='custom', detail='bar') self.jobj_cusom = {'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())) @@ -90,11 +98,16 @@ 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_error(self): + def test_init_wrong_key_value_success(self): # pylint: disable=no-self-use from acme.messages import Directory - self.assertRaises(ValueError, Directory, {'foo': 'bar'}) + Directory({'foo': 'bar'}) def test_getitem(self): self.assertEqual('reg', self.dir['new-reg']) @@ -111,14 +124,20 @@ def test_getattr_fails_with_attribute_error(self): self.assertRaises(AttributeError, self.dir.__getattr__, 'foo') - def test_to_partial_json(self): - self.assertEqual( - self.dir.to_partial_json(), {'new-reg': 'reg', 'new-cert': 'cert'}) + 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/', + 'caa-identities': ['example.com'], + }, + }) - def test_from_json_deserialization_error_on_wrong_key(self): + def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use from acme.messages import Directory - self.assertRaises( - jose.DeserializationError, Directory.from_json, {'foo': 'bar'}) + Directory.from_json({'foo': 'bar'}) class RegistrationTest(unittest.TestCase): diff -Nru python-acme-0.5.0/acme.egg-info/PKG-INFO python-acme-0.8.1/acme.egg-info/PKG-INFO --- python-acme-0.5.0/acme.egg-info/PKG-INFO 2016-04-06 00:44:57.000000000 +0000 +++ python-acme-0.8.1/acme.egg-info/PKG-INFO 2016-06-14 23:46:46.000000000 +0000 @@ -1,9 +1,9 @@ Metadata-Version: 1.1 Name: acme -Version: 0.5.0 +Version: 0.8.1 Summary: ACME protocol implementation in Python Home-page: https://github.com/letsencrypt/letsencrypt -Author: Let's Encrypt Project +Author: Certbot Project Author-email: client-dev@letsencrypt.org License: Apache License 2.0 Description: UNKNOWN diff -Nru python-acme-0.5.0/debian/changelog python-acme-0.8.1/debian/changelog --- python-acme-0.5.0/debian/changelog 2016-04-08 01:56:08.000000000 +0000 +++ python-acme-0.8.1/debian/changelog 2016-06-30 22:54:58.000000000 +0000 @@ -1,3 +1,26 @@ +python-acme (0.8.1-1) unstable; urgency=medium + + * New upstream release. + * Specify python-setuptools version. (Closes: #825619) + + -- Harlan Lieberman-Berg Thu, 30 Jun 2016 18:54:04 -0400 + +python-acme (0.8.0-1) unstable; urgency=high + + * New upstream release. + * Add version dep on python-setuptools. + * Add Suggests for -doc. + + -- Harlan Lieberman-Berg Thu, 02 Jun 2016 19:22:18 -0400 + +python-acme (0.6.0-1) unstable; urgency=medium + + * New upstream release. + * Add Breaks on python-certbot in rename prep. + * Bump S-V; no changes needed. + + -- Harlan Lieberman-Berg Thu, 12 May 2016 18:38:11 -0400 + python-acme (0.5.0-1) unstable; urgency=medium * New upstream release. diff -Nru python-acme-0.5.0/debian/control python-acme-0.8.1/debian/control --- python-acme-0.5.0/debian/control 2016-04-08 01:59:03.000000000 +0000 +++ python-acme-0.8.1/debian/control 2016-06-25 22:24:15.000000000 +0000 @@ -11,13 +11,14 @@ python-docutils, python-mock, python-ndg-httpsclient, - python-openssl (>= 0.13), + python-openssl (>= 0.15), python-pyasn1, python-requests, python-rfc3339, - python-setuptools, + python-setuptools (>= 11.3), python-six, python-sphinx (>= 1.3.1-1~), + python-sphinx-rtd-theme, python-sphinxcontrib.programoutput, python-tz, python3, @@ -29,9 +30,9 @@ python3-pyasn1, python3-requests, python3-rfc3339, - python3-setuptools, + python3-setuptools (>= 11.3), python3-tz, -Standards-Version: 3.9.7 +Standards-Version: 3.9.8 Homepage: https://letsencrypt.org/ Vcs-Git: https://anonscm.debian.org/git/letsencrypt/python-acme.git Vcs-Browser: https://anonscm.debian.org/cgit/letsencrypt/python-acme.git/ @@ -43,7 +44,9 @@ python-openssl (>= 0.15), ${misc:Depends}, ${python:Depends} -Breaks: python-letsencrypt (<< ${source:Upstream-Version}) +Breaks: python-letsencrypt (<< ${source:Upstream-Version}), + python-certbot (<< ${source:Upstream-Version}) +Suggests: python-acme-doc Description: ACME protocol library for Python 2 This is a library used by the Let's Encrypt client for the ACME (Automated Certificate Management Environment). The ACME protocol is @@ -59,6 +62,7 @@ python3-openssl (>= 0.15), ${misc:Depends}, ${python3:Depends} +Suggests: python-acme-doc Description: ACME protocol library for Python 3 This is a library used by the Let's Encrypt client for the ACME (Automated Certificate Management Environment). The ACME protocol is @@ -79,6 +83,6 @@ setup an HTTPS server and have it automatically obtain a browser-trusted certificate, without any human intervention. This library implements the protocol used for proving the control of a - domain. It currently supports only Python 2. + domain. . This package provides the documentation. diff -Nru python-acme-0.5.0/debian/pydist-overrides python-acme-0.8.1/debian/pydist-overrides --- python-acme-0.5.0/debian/pydist-overrides 1970-01-01 00:00:00.000000000 +0000 +++ python-acme-0.8.1/debian/pydist-overrides 2016-06-30 22:53:55.000000000 +0000 @@ -0,0 +1 @@ +setuptools python-setuptools (>= 11.3~) diff -Nru python-acme-0.5.0/PKG-INFO python-acme-0.8.1/PKG-INFO --- python-acme-0.5.0/PKG-INFO 2016-04-06 00:44:57.000000000 +0000 +++ python-acme-0.8.1/PKG-INFO 2016-06-14 23:46:46.000000000 +0000 @@ -1,9 +1,9 @@ Metadata-Version: 1.1 Name: acme -Version: 0.5.0 +Version: 0.8.1 Summary: ACME protocol implementation in Python Home-page: https://github.com/letsencrypt/letsencrypt -Author: Let's Encrypt Project +Author: Certbot Project Author-email: client-dev@letsencrypt.org License: Apache License 2.0 Description: UNKNOWN diff -Nru python-acme-0.5.0/setup.py python-acme-0.8.1/setup.py --- python-acme-0.5.0/setup.py 2016-04-06 00:44:38.000000000 +0000 +++ python-acme-0.8.1/setup.py 2016-06-14 23:46:01.000000000 +0000 @@ -4,7 +4,7 @@ from setuptools import find_packages -version = '0.5.0' +version = '0.8.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ @@ -53,7 +53,7 @@ version=version, description='ACME protocol implementation in Python', url='https://github.com/letsencrypt/letsencrypt', - author="Let's Encrypt Project", + author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', classifiers=[