From ffc2b1ee7864a848c0f2a22ca873ac970bb911bf Mon Sep 17 00:00:00 2001 From: Wilfried Teiken Date: Sat, 2 Jan 2016 01:42:47 -0500 Subject: [PATCH] - Lint fixes - Add test for multiple TXT records returned - Add extra parameter in DNS01.validation to select hexdigit vs. bas64 encoded validation --- acme/acme/challenges.py | 23 +++++++++++------------ acme/acme/challenges_test.py | 36 +++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index f48c20443..6c13ec906 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -242,17 +242,15 @@ class DNS01Response(KeyAuthorizationChallengeResponse): logger.debug("Verification of key authorization in response failed") return False - validation_name = chall.validation_domain_name(domain) + validation_domain_name = chall.validation_domain_name(domain) validation = chall.validation(account_public_key) - logger.debug("Verifying %s at %s...", chall.typ, validation_name) - txt_records = [] + logger.debug("Verifying %s at %s...", chall.typ, validation_domain_name) try: - dns_response = dns.resolver.query(validation_name, 'TXT') - for rdata in dns_response: - for txt_record in rdata.strings: - txt_records.append(txt_record) + dns_response = dns.resolver.query(validation_domain_name, 'TXT') + txt_records = sum([rdata.strings for rdata in dns_response], []) except dns.exception.DNSException as error: - logger.error("Unable to resolve %s: %s", validation_name, error) + logger.error("Unable to resolve %s: %s", validation_domain_name, + error) return False for txt_record in txt_records: @@ -273,7 +271,8 @@ class DNS01(KeyAuthorizationChallenge): LABEL = "_acme-challenge" """Label clients prepend to the domain name being validated.""" - def validation(self, account_key, **unused_kwargs): + # FIXME: Remove extra parameter once #2052 is integrated + def validation(self, account_key, dns01_hexdigit_response=True, **unused_kwargs): """Generate validation. :param JWK account_key: @@ -281,9 +280,9 @@ class DNS01(KeyAuthorizationChallenge): """ key_authorization = self.key_authorization(account_key) - # FIXME Once boulder response according to the spec this needs to be fixed - # return base64.b64encode(hashlib.sha256(key_authorization).digest()) - return hashlib.sha256(key_authorization).hexdigest() + if dns01_hexdigit_response: + return hashlib.sha256(key_authorization).hexdigest() + return base64.urlsafe_b64encode(hashlib.sha256(key_authorization).digest()) def validation_domain_name(self, name): """Domain name for TXT validation record. diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index a587d3f61..c2a629bb3 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -93,10 +93,15 @@ class DNS01ResponseTest(unittest.TestCase): self.chall = DNS01(token=(b'x' * 16)) self.response = self.chall.response(KEY) - # This takes advantage of the fact that an answer object mostly behaves like - # an RRset - def create_txt_response(self, name, txt_record): - return dns.rrset.from_text(name, 60, "IN", "TXT", txt_record) + def create_txt_response(self, name, txt_records): + """ + Returns an RRSet containing the 'txt_records' as the result of a DNS + query for 'name'. + + This takes advantage of the fact that an Answer object mostly behaves + like an RRset. + """ + return dns.rrset.from_text_list(name, 60, "IN", "TXT", txt_records) def test_to_partial_json(self): self.assertEqual(self.jmsg, self.msg.to_partial_json()) @@ -118,7 +123,17 @@ class DNS01ResponseTest(unittest.TestCase): def test_simple_verify_good_validation(self, mock_dns): mock_dns.return_value = self.create_txt_response( self.chall.validation_domain_name("local"), - self.chall.validation(KEY.public_key())) + [self.chall.validation(KEY.public_key())]) + self.assertTrue(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + mock_dns.assert_called_once_with( + self.chall.validation_domain_name("local"), "TXT") + + @mock.patch("acme.challenges.dns.resolver.query") + def test_simple_verify_good_validation_multiple_txts(self, mock_dns): + mock_dns.return_value = self.create_txt_response( + self.chall.validation_domain_name("local"), + ["!", self.chall.validation(KEY.public_key())]) self.assertTrue(self.response.simple_verify( self.chall, "local", KEY.public_key())) mock_dns.assert_called_once_with( @@ -127,7 +142,7 @@ class DNS01ResponseTest(unittest.TestCase): @mock.patch("acme.challenges.dns.resolver.query") def test_simple_verify_bad_validation(self, mock_dns): mock_dns.return_value = self.create_txt_response( - self.chall.validation_domain_name("local"), "!") + self.chall.validation_domain_name("local"), ["!"]) self.assertFalse(self.response.simple_verify( self.chall, "local", KEY.public_key())) @@ -153,10 +168,17 @@ class DNS01Test(unittest.TestCase): self.assertEqual('_acme-challenge.www.example.com', self.msg.validation_domain_name('www.example.com')) + # FIXME: Remove extra parameter once #2052 is integrated def test_validation(self): + self.assertEqual( + "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk=", + self.msg.validation(KEY, dns01_hexdigit_response=False)) + + # FIXME: Remove this once #2052 is integrated + def test_validation_for_server_with_hexdigit_response(self): self.assertEqual( "ac06bb8888382b6cbaddfbd48427f2f1d3f55e5ef0121990ab4a02853704dd99", - self.msg.validation(KEY)) + self.msg.validation(KEY, dns01_hexdigit_response=True)) def test_to_partial_json(self): self.assertEqual(self.jmsg, self.msg.to_partial_json())