Remove keyAuthorization fallback dump in challenges response (#6975)

Fixes #6974.

This PR removes the fallback that consists in retrying to send the keyAuthorization field during a challenge request in case of malformed request.

* Remove keyAuthorization fallback dump in challenges response

* Correct import

* Add changelog entry
This commit is contained in:
Adrien Ferrand 2019-04-24 00:10:15 +02:00 committed by Brad Warren
parent 618e0562a0
commit 9dd2990e59
5 changed files with 6 additions and 70 deletions

View file

@ -17,6 +17,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
* Updated Certbot and its plugins to improve the handling of file system permissions
on Windows as a step towards adding proper Windows support to Certbot.
* Updated urllib3 to 1.24.2 in certbot-auto.
* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response
with a `keyAuthorization` if sending the response without this field caused a
`malformed` error to be received from the ACME server.
### Fixed
@ -26,6 +29,7 @@ Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
* certbot
* certbot-apache
* certbot-dns-cloudflare

View file

@ -107,10 +107,6 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
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.
@ -143,20 +139,9 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
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

View file

@ -95,8 +95,6 @@ class DNS01ResponseTest(unittest.TestCase):
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
@ -169,8 +167,6 @@ class HTTP01ResponseTest(unittest.TestCase):
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
@ -292,8 +288,6 @@ class TLSSNI01ResponseTest(unittest.TestCase):
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
@ -428,8 +422,6 @@ class TLSALPN01ResponseTest(unittest.TestCase):
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

View file

@ -17,7 +17,6 @@ import requests
from requests.adapters import HTTPAdapter
from requests_toolbelt.adapters.source import SourceAddressAdapter
from acme import challenges
from acme import crypto_util
from acme import errors
from acme import jws
@ -156,23 +155,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
: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:

View file

@ -461,34 +461,6 @@ class ClientTest(ClientTestBase):
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(