Hello community, here is the log from the commit of package python-acme for openSUSE:Factory checked in at 2019-06-03 18:49:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-acme (Old) and /work/SRC/openSUSE:Factory/.python-acme.new.5148 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-acme" Mon Jun 3 18:49:56 2019 rev:29 rq:705621 version:0.34.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-acme/python-acme.changes 2019-03-19 09:59:26.960077495 +0100 +++ /work/SRC/openSUSE:Factory/.python-acme.new.5148/python-acme.changes 2019-06-03 18:49:58.996545150 +0200 @@ -1,0 +2,6 @@ +Sat May 18 23:13:13 UTC 2019 - Dirk Mueller <[email protected]> + +- update to 0.34.2: + * sync with main certbot package + +------------------------------------------------------------------- Old: ---- acme-0.32.0.tar.gz acme-0.32.0.tar.gz.asc New: ---- acme-0.34.2.tar.gz acme-0.34.2.tar.gz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-acme.spec ++++++ --- /var/tmp/diff_new_pack.BZfc92/_old 2019-06-03 18:49:59.960544819 +0200 +++ /var/tmp/diff_new_pack.BZfc92/_new 2019-06-03 18:49:59.960544819 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define libname acme Name: python-%{libname} -Version: 0.32.0 +Version: 0.34.2 Release: 0 Summary: Python library for the ACME protocol License: Apache-2.0 ++++++ acme-0.32.0.tar.gz -> acme-0.34.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/PKG-INFO new/acme-0.34.2/PKG-INFO --- old/acme-0.32.0/PKG-INFO 2019-03-06 21:18:19.000000000 +0100 +++ new/acme-0.34.2/PKG-INFO 2019-05-07 21:17:38.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: acme -Version: 0.32.0 +Version: 0.34.2 Summary: ACME protocol implementation in Python Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/__init__.py new/acme-0.34.2/acme/__init__.py --- old/acme-0.32.0/acme/__init__.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/__init__.py 2019-05-07 21:17:32.000000000 +0200 @@ -6,6 +6,7 @@ """ import sys +import warnings # This code exists to keep backwards compatibility with people using acme.jose # before it became the standalone josepy package. @@ -20,3 +21,30 @@ # preserved (acme.jose.* is josepy.*) if mod == 'josepy' or mod.startswith('josepy.'): sys.modules['acme.' + mod.replace('josepy', 'jose', 1)] = sys.modules[mod] + + +# This class takes a similar approach to the cryptography project to deprecate attributes +# in public modules. See the _ModuleWithDeprecation class here: +# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 +class _TLSSNI01DeprecationModule(object): + """ + Internal class delegating to a module, and displaying warnings when + attributes related to TLS-SNI-01 are accessed. + """ + def __init__(self, module): + self.__dict__['_module'] = module + + def __getattr__(self, attr): + if 'TLSSNI01' in attr: + warnings.warn('{0} attribute is deprecated, and will be removed soon.'.format(attr), + DeprecationWarning, stacklevel=2) + return getattr(self._module, attr) + + def __setattr__(self, attr, value): # pragma: no cover + setattr(self._module, attr, value) + + def __delattr__(self, attr): # pragma: no cover + delattr(self._module, attr) + + def __dir__(self): # pragma: no cover + return ['_module'] + dir(self._module) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/challenges.py new/acme-0.34.2/acme/challenges.py --- old/acme-0.32.0/acme/challenges.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/challenges.py 2019-05-07 21:17:32.000000000 +0200 @@ -4,7 +4,7 @@ import hashlib import logging import socket -import warnings +import sys from cryptography.hazmat.primitives import hashes # type: ignore import josepy as jose @@ -15,15 +15,13 @@ from acme import errors from acme import crypto_util from acme import fields +from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) -# pylint: disable=too-few-public-methods - - class Challenge(jose.TypedJSONObjectWithFields): - # _fields_to_partial_json | pylint: disable=abstract-method + # _fields_to_partial_json """ACME challenge.""" TYPES = {} # type: dict @@ -37,7 +35,7 @@ class ChallengeResponse(jose.TypedJSONObjectWithFields): - # _fields_to_partial_json | pylint: disable=abstract-method + # _fields_to_partial_json """ACME challenge response.""" TYPES = {} # type: dict resource_type = 'challenge' @@ -96,6 +94,7 @@ """ # TODO: check that path combined with uri does not go above # URI_ROOT_PATH! + # pylint: disable=unsupported-membership-test return b'..' not in self.token and b'/' not in self.token @@ -108,10 +107,6 @@ key_authorization = jose.Field("keyAuthorization") thumbprint_hash_function = hashes.SHA256 - def __init__(self, *args, **kwargs): - super(KeyAuthorizationChallengeResponse, self).__init__(*args, **kwargs) - self._dump_authorization_key(False) - def verify(self, chall, account_public_key): """Verify the key authorization. @@ -144,26 +139,14 @@ return True - def _dump_authorization_key(self, dump): - # type: (bool) -> None - """ - Set if keyAuthorization is dumped in the JSON representation of this ChallengeResponse. - NB: This method is declared as private because it will eventually be removed. - :param bool dump: True to dump the keyAuthorization, False otherwise - """ - object.__setattr__(self, '_dump_auth_key', dump) - def to_partial_json(self): jobj = super(KeyAuthorizationChallengeResponse, self).to_partial_json() - if not self._dump_auth_key: # pylint: disable=no-member - jobj.pop('keyAuthorization', None) - + jobj.pop('keyAuthorization', None) return jobj @six.add_metaclass(abc.ABCMeta) class KeyAuthorizationChallenge(_TokenChallenge): - # pylint: disable=abstract-class-little-used,too-many-ancestors """Challenge based on Key Authorization. :param response_cls: Subclass of `KeyAuthorizationChallengeResponse` @@ -195,7 +178,7 @@ :rtype: KeyAuthorizationChallengeResponse """ - return self.response_cls( + return self.response_cls( # pylint: disable=not-callable key_authorization=self.key_authorization(account_key)) @abc.abstractmethod @@ -232,7 +215,7 @@ """ACME dns-01 challenge response.""" typ = "dns-01" - def simple_verify(self, chall, domain, account_public_key): + def simple_verify(self, chall, domain, account_public_key): # pylint: disable=unused-argument """Simple verify. This method no longer checks DNS records and is a simple wrapper @@ -248,7 +231,6 @@ :rtype: bool """ - # pylint: disable=unused-argument verified = self.verify(chall, account_public_key) if not verified: logger.debug("Verification of key authorization in response failed") @@ -453,7 +435,6 @@ kwargs.setdefault("port", self.PORT) kwargs["name"] = self.z_domain # TODO: try different methods? - # pylint: disable=protected-access return crypto_util.probe_sni(**kwargs) def verify_cert(self, cert): @@ -514,11 +495,6 @@ # boulder#962, ietf-wg-acme#22 #n = jose.Field("n", encoder=int, decoder=int) - def __init__(self, *args, **kwargs): - warnings.warn("TLS-SNI-01 is deprecated, and will stop working soon.", - DeprecationWarning, stacklevel=2) - super(TLSSNI01, self).__init__(*args, **kwargs) - def validation(self, account_key, **kwargs): """Generate validation. @@ -560,7 +536,7 @@ raise NotImplementedError() [email protected] # pylint: disable=too-many-ancestors [email protected] class DNS(_TokenChallenge): """ACME "dns" challenge.""" typ = "dns" @@ -641,3 +617,7 @@ """ return chall.check_validation(self.validation, account_public_key) + + +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/challenges_test.py new/acme-0.34.2/acme/challenges_test.py --- old/acme-0.32.0/acme/challenges_test.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/challenges_test.py 2019-05-07 21:17:32.000000000 +0200 @@ -1,13 +1,12 @@ """Tests for acme.challenges.""" import unittest -import warnings import josepy as jose import mock import OpenSSL import requests -from six.moves.urllib import parse as urllib_parse # pylint: disable=import-error +from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import from acme import errors from acme import test_util @@ -96,8 +95,6 @@ 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()) - self.msg._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import DNS01Response @@ -170,8 +167,6 @@ 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()) - self.msg._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import HTTP01Response @@ -293,8 +288,6 @@ def test_to_partial_json(self): self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, self.response.to_partial_json()) - self.response._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.response.to_partial_json()) def test_from_json(self): from acme.challenges import TLSSNI01Response @@ -374,25 +367,16 @@ 'type': 'tls-sni-01', 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', } - - def _msg(self): from acme.challenges import TLSSNI01 - with warnings.catch_warnings(record=True) as warn: - warnings.simplefilter("always") - msg = TLSSNI01( - token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) - assert warn is not None # using a raw assert for mypy - self.assertTrue(len(warn) == 1) - self.assertTrue(issubclass(warn[-1].category, DeprecationWarning)) - self.assertTrue('deprecated' in str(warn[-1].message)) - return msg + self.msg = TLSSNI01( + token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) def test_to_partial_json(self): - self.assertEqual(self.jmsg, self._msg().to_partial_json()) + self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import TLSSNI01 - self.assertEqual(self._msg(), TLSSNI01.from_json(self.jmsg)) + self.assertEqual(self.msg, TLSSNI01.from_json(self.jmsg)) def test_from_json_hashable(self): from acme.challenges import TLSSNI01 @@ -407,10 +391,18 @@ @mock.patch('acme.challenges.TLSSNI01Response.gen_cert') def test_validation(self, mock_gen_cert): mock_gen_cert.return_value = ('cert', 'key') - self.assertEqual(('cert', 'key'), self._msg().validation( + self.assertEqual(('cert', 'key'), self.msg.validation( KEY, cert_key=mock.sentinel.cert_key)) mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key) + def test_deprecation_message(self): + with mock.patch('acme.warnings.warn') as mock_warn: + from acme.challenges import TLSSNI01 + assert TLSSNI01 + self.assertEqual(mock_warn.call_count, 1) + self.assertTrue('deprecated' in mock_warn.call_args[0][0]) + + class TLSALPN01ResponseTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes @@ -430,8 +422,6 @@ 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()) - self.msg._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import TLSALPN01Response diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/client.py new/acme-0.34.2/acme/client.py --- old/acme-0.32.0/acme/client.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/client.py 2019-05-07 21:17:32.000000000 +0200 @@ -6,18 +6,17 @@ import heapq import logging import time +import re +import sys import six from six.moves import http_client # pylint: disable=import-error import josepy as jose import OpenSSL -import re -from requests_toolbelt.adapters.source import SourceAddressAdapter import requests from requests.adapters import HTTPAdapter -import sys +from requests_toolbelt.adapters.source import SourceAddressAdapter -from acme import challenges from acme import crypto_util from acme import errors from acme import jws @@ -124,15 +123,6 @@ """ return self.update_registration(regr, update={'status': 'deactivated'}) - def query_registration(self, regr): - """Query server about registration. - - :param messages.RegistrationResource: Existing Registration - Resource. - - """ - return self._send_recv_regr(regr, messages.UpdateRegistration()) - def _authzr_from_response(self, response, identifier=None, uri=None): authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), @@ -156,23 +146,7 @@ :raises .UnexpectedUpdate: """ - # Because sending keyAuthorization in a response challenge has been removed from the ACME - # spec, it is not included in the KeyAuthorizationResponseChallenge JSON by default. - # However as a migration path, we temporarily expect a malformed error from the server, - # and fallback by resending the challenge response with the keyAuthorization field. - # TODO: Remove this fallback for Certbot 0.34.0 - try: - response = self._post(challb.uri, response) - except messages.Error as error: - if (error.code == 'malformed' - and isinstance(response, challenges.KeyAuthorizationChallengeResponse)): - logger.debug('Error while responding to a challenge without keyAuthorization ' - 'in the JWS, your ACME CA server may not support it:\n%s', error) - logger.debug('Retrying request with keyAuthorization set.') - response._dump_authorization_key(True) # pylint: disable=protected-access - response = self._post(challb.uri, response) - else: - raise + response = self._post(challb.uri, response) try: authzr_uri = response.links['up']['url'] except KeyError: @@ -293,6 +267,15 @@ # pylint: disable=no-member return self._regr_from_response(response) + def query_registration(self, regr): + """Query server about registration. + + :param messages.RegistrationResource: Existing Registration + Resource. + + """ + return self._send_recv_regr(regr, messages.UpdateRegistration()) + def agree_to_tos(self, regr): """Agree to the terms-of-service. @@ -620,10 +603,13 @@ Resource. """ - self.net.account = regr - updated_regr = super(ClientV2, self).query_registration(regr) - self.net.account = updated_regr - return updated_regr + self.net.account = regr # See certbot/certbot#6258 + # ACME v2 requires to use a POST-as-GET request (POST an empty JWS) here. + # This is done by passing None instead of an empty UpdateRegistration to _post(). + response = self._post(regr.uri, None) + self.net.account = self._regr_from_response(response, uri=regr.uri, + terms_of_service=regr.terms_of_service) + return self.net.account def update_registration(self, regr, update=None): """Update registration. @@ -669,7 +655,7 @@ response = self._post(self.directory['newOrder'], order) body = messages.Order.from_json(response.json()) authorizations = [] - for url in body.authorizations: + for url in body.authorizations: # pylint: disable=not-an-iterable authorizations.append(self._authzr_from_response(self._post_as_get(url), uri=url)) return messages.OrderResource( body=body, @@ -731,7 +717,7 @@ for chall in authzr.body.challenges: if chall.error != None: failed.append(authzr) - if len(failed) > 0: + if failed: raise errors.ValidationError(failed) return orderr.update(authorizations=responses) @@ -775,10 +761,7 @@ def external_account_required(self): """Checks if ACME server requires External Account Binding authentication.""" - if hasattr(self.directory, 'meta') and self.directory.meta.external_account_required: - return True - else: - return False + return hasattr(self.directory, 'meta') and self.directory.meta.external_account_required def _post_as_get(self, *args, **kwargs): """ @@ -794,7 +777,7 @@ # We add an empty payload for POST-as-GET requests new_args = args[:1] + (None,) + args[1:] try: - return self._post(*new_args, **kwargs) # pylint: disable=star-args + return self._post(*new_args, **kwargs) except messages.Error as error: if error.code == 'malformed': logger.debug('Error during a POST-as-GET request, ' @@ -882,8 +865,7 @@ for domain in dnsNames: authorizations.append(self.client.request_domain_challenges(domain)) return messages.OrderResource(authorizations=authorizations, csr_pem=csr_pem) - else: - return self.client.new_order(csr_pem) + return self.client.new_order(csr_pem) def finalize_order(self, orderr, deadline): """Finalize an order and obtain a certificate. @@ -920,8 +902,7 @@ chain = crypto_util.dump_pyopenssl_chain(chain).decode() return orderr.update(fullchain_pem=(cert + chain)) - else: - return self.client.finalize_order(orderr, deadline) + return self.client.finalize_order(orderr, deadline) def revoke(self, cert, rsn): """Revoke certificate. @@ -939,8 +920,7 @@ def _acme_version_from_directory(self, directory): if hasattr(directory, 'newNonce'): return 2 - else: - return 1 + return 1 def external_account_required(self): """Checks if the server requires an external account for ACMEv2 servers. @@ -948,8 +928,7 @@ Always return False for ACMEv1 servers, as it doesn't use External Account Binding.""" if self.acme_version == 1: return False - else: - return self.client.external_account_required() + return self.client.external_account_required() class ClientNetwork(object): # pylint: disable=too-many-instance-attributes @@ -1027,7 +1006,6 @@ if self.account is not None: kwargs["kid"] = self.account["uri"] kwargs["key"] = self.key - # pylint: disable=star-args return jws.JWS.sign(jobj, **kwargs).json_dumps(indent=2) @classmethod diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/client_test.py new/acme-0.34.2/acme/client_test.py --- old/acme-0.32.0/acme/client_test.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/client_test.py 2019-05-07 21:17:32.000000000 +0200 @@ -64,7 +64,7 @@ reg = messages.Registration( contact=self.contact, key=KEY.public_key()) the_arg = dict(reg) # type: Dict - self.new_reg = messages.NewRegistration(**the_arg) # pylint: disable=star-args + self.new_reg = messages.NewRegistration(**the_arg) self.regr = messages.RegistrationResource( body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') @@ -358,7 +358,6 @@ def test_register(self): # "Instance of 'Field' has no to_json/update member" bug: - # pylint: disable=no-member self.response.status_code = http_client.CREATED self.response.json.return_value = self.regr.body.to_json() self.response.headers['Location'] = self.regr.uri @@ -371,7 +370,6 @@ def test_update_registration(self): # "Instance of 'Field' has no to_json/update member" bug: - # pylint: disable=no-member 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)) @@ -463,34 +461,6 @@ errors.ClientError, self.client.answer_challenge, self.challr.body, challenges.DNSResponse(validation=None)) - def test_answer_challenge_key_authorization_fallback(self): - self.response.links['up'] = {'url': self.challr.authzr_uri} - self.response.json.return_value = self.challr.body.to_json() - - def _wrapper_post(url, obj, *args, **kwargs): # pylint: disable=unused-argument - """ - Simulate an old ACME CA server, that would respond a 'malformed' - error if keyAuthorization is missing. - """ - jobj = obj.to_partial_json() - if 'keyAuthorization' not in jobj: - raise messages.Error.with_code('malformed') - return self.response - self.net.post.side_effect = _wrapper_post - - # This challenge response is of type KeyAuthorizationChallengeResponse, so the fallback - # should be triggered, and avoid an exception. - http_chall_response = challenges.HTTP01Response(key_authorization='test', - resource=mock.MagicMock()) - self.client.answer_challenge(self.challr.body, http_chall_response) - - # This challenge response is not of type KeyAuthorizationChallengeResponse, so the fallback - # should not be triggered, leading to an exception. - dns_chall_response = challenges.DNSResponse(validation=None) - self.assertRaises( - errors.Error, self.client.answer_challenge, - self.challr.body, dns_chall_response) - def test_retry_after_date(self): self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' self.assertEqual( @@ -872,7 +842,6 @@ def test_update_registration(self): # "Instance of 'Field' has no to_json/update member" bug: - # pylint: disable=no-member 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)) @@ -934,7 +903,7 @@ return {'foo': self.value} @classmethod - def from_json(cls, value): + def from_json(cls, jobj): pass # pragma: no cover diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/crypto_util.py new/acme-0.34.2/acme/crypto_util.py --- old/acme-0.32.0/acme/crypto_util.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/crypto_util.py 2019-05-07 21:17:32.000000000 +0200 @@ -18,17 +18,14 @@ logger = logging.getLogger(__name__) -# TLSSNI01 certificate serving and probing is not affected by SSL -# vulnerabilities: prober needs to check certificate for expected -# contents anyway. Working SNI is the only thing that's necessary for -# the challenge and thus scoping down SSL/TLS method (version) would -# cause interoperability issues: TLSv1_METHOD is only compatible with +# Default SSL method selected here is the most compatible, while secure +# SSL method: TLSv1_METHOD is only compatible with # TLSv1_METHOD, while SSLv23_METHOD is compatible with all other # methods, including TLSv2_METHOD (read more at # https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni # should be changed to use "set_options" to disable SSLv2 and SSLv3, # in case it's used for things other than probing/serving! -_DEFAULT_TLSSNI01_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore +_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore class SSLSocket(object): # pylint: disable=too-few-public-methods @@ -40,7 +37,7 @@ :ivar method: See `OpenSSL.SSL.Context` for allowed values. """ - def __init__(self, sock, certs, method=_DEFAULT_TLSSNI01_SSL_METHOD): + def __init__(self, sock, certs, method=_DEFAULT_SSL_METHOD): self.sock = sock self.certs = certs self.method = method @@ -112,7 +109,7 @@ def probe_sni(name, host, port=443, timeout=300, - method=_DEFAULT_TLSSNI01_SSL_METHOD, source_address=('', 0)): + method=_DEFAULT_SSL_METHOD, source_address=('', 0)): """Probe SNI server for SSL certificate. :param bytes name: Byte string to send as the server name in the @@ -137,7 +134,6 @@ socket_kwargs = {'source_address': source_address} try: - # pylint: disable=star-args logger.debug( "Attempting to connect to %s:%d%s.", host, port, " from {0}:{1}".format( @@ -198,8 +194,7 @@ if common_name is None: return sans - else: - return [common_name] + [d for d in sans if d != common_name] + return [common_name] + [d for d in sans if d != common_name] def _pyopenssl_cert_or_req_san(cert_or_req): """Get Subject Alternative Names from certificate or CSR using pyOpenSSL. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/messages.py new/acme-0.34.2/acme/messages.py --- old/acme-0.32.0/acme/messages.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/messages.py 2019-05-07 21:17:32.000000000 +0200 @@ -1,9 +1,9 @@ """ACME protocol messages.""" -import six import json +import six try: from collections.abc import Hashable # pylint: disable=no-name-in-module -except ImportError: +except ImportError: # pragma: no cover from collections import Hashable import josepy as jose @@ -46,8 +46,7 @@ """Check if argument is an ACME error.""" if isinstance(err, Error) and (err.typ is not None): return (ERROR_PREFIX in err.typ) or (OLD_ERROR_PREFIX in err.typ) - else: - return False + return False @six.python_2_unicode_compatible @@ -102,6 +101,7 @@ code = str(self.typ).split(':')[-1] if code in ERROR_CODES: return code + return None def __str__(self): return b' :: '.join( @@ -116,18 +116,19 @@ POSSIBLE_NAMES = NotImplemented def __init__(self, name): - self.POSSIBLE_NAMES[name] = self + super(_Constant, self).__init__() + self.POSSIBLE_NAMES[name] = self # pylint: disable=unsupported-assignment-operation self.name = name def to_partial_json(self): return self.name @classmethod - def from_json(cls, value): - if value not in cls.POSSIBLE_NAMES: + def from_json(cls, jobj): + if jobj not in cls.POSSIBLE_NAMES: # pylint: disable=unsupported-membership-test raise jose.DeserializationError( '{0} not recognized'.format(cls.__name__)) - return cls.POSSIBLE_NAMES[value] + return cls.POSSIBLE_NAMES[jobj] # pylint: disable=unsubscriptable-object def __repr__(self): return '{0}({1})'.format(self.__class__.__name__, self.name) @@ -186,7 +187,6 @@ def __init__(self, **kwargs): kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items()) - # pylint: disable=star-args super(Directory.Meta, self).__init__(**kwargs) @property @@ -322,7 +322,7 @@ def _filter_contact(self, prefix): return tuple( - detail[len(prefix):] for detail in self.contact + detail[len(prefix):] for detail in self.contact # pylint: disable=not-an-iterable if detail.startswith(prefix)) @property @@ -394,7 +394,6 @@ def __init__(self, **kwargs): kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items()) - # pylint: disable=star-args super(ChallengeBody, self).__init__(**kwargs) def encode(self, name): @@ -477,7 +476,7 @@ def resolved_combinations(self): """Combinations with challenges instead of indices.""" return tuple(tuple(self.challenges[idx] for idx in combo) - for combo in self.combinations) + for combo in self.combinations) # pylint: disable=not-an-iterable @Directory.register diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/standalone.py new/acme-0.34.2/acme/standalone.py --- old/acme-0.32.0/acme/standalone.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/standalone.py 2019-05-07 21:17:32.000000000 +0200 @@ -17,6 +17,7 @@ from acme import challenges from acme import crypto_util from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) @@ -37,7 +38,7 @@ self.certs = kwargs.pop("certs", {}) self.method = kwargs.pop( # pylint: disable=protected-access - "method", crypto_util._DEFAULT_TLSSNI01_SSL_METHOD) + "method", crypto_util._DEFAULT_SSL_METHOD) self.allow_reuse_address = kwargs.pop("allow_reuse_address", True) socketserver.TCPServer.__init__(self, *args, **kwargs) @@ -82,7 +83,7 @@ kwargs["ipv6"] = ip_version new_address = (server_address[0],) + (port,) + server_address[2:] new_args = (new_address,) + remaining_args - server = ServerClass(*new_args, **kwargs) # pylint: disable=star-args + server = ServerClass(*new_args, **kwargs) logger.debug( "Successfully bound to %s:%s using %s", new_address[0], new_address[1], "IPv6" if ip_version else "IPv4") @@ -90,8 +91,8 @@ if self.servers: # Already bound using IPv6. logger.debug( - "Certbot wasn't able to bind to %s:%s using %s, this " + - "is often expected due to the dual stack nature of " + + "Certbot wasn't able to bind to %s:%s using %s, this " + "is often expected due to the dual stack nature of " "IPv6 socket implementations.", new_address[0], new_address[1], "IPv6" if ip_version else "IPv4") @@ -104,7 +105,7 @@ # If two servers are set up and port 0 was passed in, ensure we always # bind to the same port for both servers. port = server.socket.getsockname()[1] - if len(self.servers) == 0: + if not self.servers: raise socket.error("Could not bind to IPv4 or IPv6.") def serve_forever(self): @@ -296,5 +297,9 @@ server.handle_request() +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) + + if __name__ == "__main__": sys.exit(simple_tls_sni_01_server(sys.argv)) # pragma: no cover diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/standalone_test.py new/acme-0.34.2/acme/standalone_test.py --- old/acme-0.32.0/acme/standalone_test.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/standalone_test.py 2019-05-07 21:17:32.000000000 +0200 @@ -1,13 +1,15 @@ """Tests for acme.standalone.""" +import multiprocessing import os import shutil import socket import threading import tempfile import unittest +import time +from contextlib import closing from six.moves import http_client # pylint: disable=import-error -from six.moves import queue # pylint: disable=import-error from six.moves import socketserver # type: ignore # pylint: disable=import-error import josepy as jose @@ -16,6 +18,7 @@ from acme import challenges from acme import crypto_util +from acme import errors from acme import test_util from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module @@ -28,14 +31,14 @@ from acme.standalone import TLSServer server = TLSServer( ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True) - server.server_close() # pylint: disable=no-member + 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() # pylint: disable=no-member + server.server_close() class TLSSNI01ServerTest(unittest.TestCase): @@ -49,12 +52,11 @@ )} from acme.standalone import TLSSNI01Server self.server = TLSSNI01Server(('localhost', 0), certs=self.certs) - # pylint: disable=no-member self.thread = threading.Thread(target=self.server.serve_forever) self.thread.start() def tearDown(self): - self.server.shutdown() # pylint: disable=no-member + self.server.shutdown() self.thread.join() def test_it(self): @@ -77,13 +79,12 @@ from acme.standalone import HTTP01Server self.server = HTTP01Server(('', 0), resources=self.resources) - # pylint: disable=no-member 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() # pylint: disable=no-member + self.server.shutdown() self.thread.join() def test_index(self): @@ -136,7 +137,6 @@ # NB: On Windows, socket.IPPROTO_IPV6 constant may be missing. # We use the corresponding value (41) instead. level = getattr(socket, "IPPROTO_IPV6", 41) - # pylint: disable=no-member self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1) try: self.server_bind() @@ -209,7 +209,6 @@ from acme.standalone import HTTP01DualNetworkedServers self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources) - # pylint: disable=no-member self.port = self.servers.getsocknames()[0][1] self.servers.serve_forever() @@ -248,7 +247,6 @@ self.assertFalse(self._test_http01(add=False)) -@test_util.broken_on_windows class TestSimpleTLSSNI01Server(unittest.TestCase): """Tests for acme.standalone.simple_tls_sni_01_server.""" @@ -263,35 +261,45 @@ shutil.copy(test_util.vector_path('rsa2048_key.pem'), os.path.join(localhost_dir, 'key.pem')) + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + sock.bind(('', 0)) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.port = sock.getsockname()[1] + from acme.standalone import simple_tls_sni_01_server - self.thread = threading.Thread( - target=simple_tls_sni_01_server, kwargs={ - 'cli_args': ('filename',), - 'forever': False, - }, - ) + self.process = multiprocessing.Process(target=simple_tls_sni_01_server, + args=(['path', '-p', str(self.port)],)) self.old_cwd = os.getcwd() os.chdir(self.test_cwd) def tearDown(self): os.chdir(self.old_cwd) - self.thread.join() + if self.process.is_alive(): + self.process.terminate() + self.process.join(timeout=5) + # Check that we didn't timeout waiting for the process to + # terminate. + self.assertNotEqual(self.process.exitcode, None) shutil.rmtree(self.test_cwd) - @mock.patch('acme.standalone.logger') - def test_it(self, mock_logger): - # Use a Queue because mock objects aren't thread safe. - q = queue.Queue() # type: queue.Queue[int] - # Add port number to the queue. - mock_logger.info.side_effect = lambda *args: q.put(args[-1]) - self.thread.start() + @mock.patch('acme.standalone.TLSSNI01Server.handle_request') + def test_mock(self, handle): + from acme.standalone import simple_tls_sni_01_server + simple_tls_sni_01_server(cli_args=['path', '-p', str(self.port)], forever=False) + self.assertEqual(handle.call_count, 1) - # After the timeout, an exception is raised if the queue is empty. - port = q.get(timeout=5) - cert = crypto_util.probe_sni(b'localhost', b'0.0.0.0', port) + def test_live(self): + self.process.start() + cert = None + for _ in range(50): + time.sleep(0.1) + try: + cert = crypto_util.probe_sni(b'localhost', b'127.0.0.1', self.port) + break + except errors.Error: # pragma: no cover + pass self.assertEqual(jose.ComparableX509(cert), - test_util.load_comparable_cert( - 'rsa2048_cert.pem')) + test_util.load_comparable_cert('rsa2048_cert.pem')) if __name__ == "__main__": diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme/test_util.py new/acme-0.34.2/acme/test_util.py --- old/acme-0.32.0/acme/test_util.py 2019-03-06 21:18:08.000000000 +0100 +++ new/acme-0.34.2/acme/test_util.py 2019-05-07 21:17:32.000000000 +0200 @@ -4,9 +4,8 @@ """ import os -import sys -import pkg_resources import unittest +import pkg_resources from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -93,13 +92,4 @@ return unittest.skipUnless(condition, reason) elif condition: return lambda cls: cls - else: - return lambda cls: None - -def broken_on_windows(function): - """Decorator to skip temporarily a broken test on Windows.""" - reason = 'Test is broken and ignored on windows but should be fixed.' - return unittest.skipIf( - sys.platform == 'win32' - and os.environ.get('SKIP_BROKEN_TESTS_ON_WINDOWS', 'true') == 'true', - reason)(function) + return lambda cls: None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/acme.egg-info/PKG-INFO new/acme-0.34.2/acme.egg-info/PKG-INFO --- old/acme-0.32.0/acme.egg-info/PKG-INFO 2019-03-06 21:18:19.000000000 +0100 +++ new/acme-0.34.2/acme.egg-info/PKG-INFO 2019-05-07 21:17:38.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: acme -Version: 0.32.0 +Version: 0.34.2 Summary: ACME protocol implementation in Python Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acme-0.32.0/setup.py new/acme-0.34.2/setup.py --- old/acme-0.32.0/setup.py 2019-03-06 21:18:09.000000000 +0100 +++ new/acme-0.34.2/setup.py 2019-05-07 21:17:33.000000000 +0200 @@ -3,7 +3,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.32.0' +version = '0.34.2' # Please update tox.ini when modifying dependency version requirements install_requires = [
