From b5a51de16bef77e499a950d47e0103c85cd680b2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 9 Dec 2014 21:55:27 +0100 Subject: [PATCH 1/2] Tests for ACME message factories --- letsencrypt/client/acme.py | 27 ++++---- letsencrypt/client/acme_test.py | 113 +++++++++++++++++++++++++++++--- letsencrypt/client/client.py | 2 +- 3 files changed, 121 insertions(+), 21 deletions(-) diff --git a/letsencrypt/client/acme.py b/letsencrypt/client/acme.py index 138d0d851..bbb39ef83 100644 --- a/letsencrypt/client/acme.py +++ b/letsencrypt/client/acme.py @@ -64,7 +64,7 @@ def pretty(json_string): def challenge_request(name): """Create ACME "challengeRequest message. - :param unicode name: Domain name + :param str name: Domain name :returns: ACME "challengeRequest" message. :rtype: dict @@ -76,15 +76,17 @@ def challenge_request(name): } -def authorization_request(req_id, name, server_nonce, responses, key): +def authorization_request(req_id, name, server_nonce, responses, key, + nonce=None): """Create ACME "authorizationRequest" message. :param str req_id: SessionID from the server challenge - :param unicode name: Hostname + :param str name: Hostname :param str server_nonce: Nonce from the server challenge :param list responses: List of completed challenges :param str key: Key in string form. Accepted formats are the same as for `Crypto.PublicKey.RSA.importKey`. + :param str nonce: Nonce used for signature. Useful for testing. :returns: ACME "authorizationRequest" message. :rtype: dict @@ -96,16 +98,17 @@ def authorization_request(req_id, name, server_nonce, responses, key): "nonce": server_nonce, "responses": responses, "signature": crypto_util.create_sig( - name + le_util.jose_b64decode(server_nonce), key), + name + le_util.jose_b64decode(server_nonce), key, nonce), } -def certificate_request(csr_der, key): +def certificate_request(csr_der, key, nonce=None): """Create ACME "certificateRequest" message. :param str csr_der: DER encoded CSR. :param str key: Key in string form. Accepted formats are the same as for `Crypto.PublicKey.RSA.importKey`. + :param str nonce: Nonce used for signature. Useful for testing. :returns: ACME "certificateRequest" message. :rtype: dict @@ -114,17 +117,17 @@ def certificate_request(csr_der, key): return { "type": "certificateRequest", "csr": le_util.jose_b64encode(csr_der), - "signature": crypto_util.create_sig(csr_der, key), + "signature": crypto_util.create_sig(csr_der, key, nonce), } -def revocation_request(key_file, cert_der): +def revocation_request(cert_der, key, nonce=None): """Create ACME "revocationRequest" message. - :param str key_file: Path to a file containing RSA key. Accepted - formats are the same as for `Crypto.PublicKey.RSA.importKey`. - :param str cert_der: DER encoded certificate. + :param str key: Key in string form. Accepted formats + are the same as for `Crypto.PublicKey.RSA.importKey`. + :param str nonce: Nonce used for signature. Useful for testing. :returns: ACME "revocationRequest" message. :rtype: dict @@ -133,14 +136,14 @@ def revocation_request(key_file, cert_der): return { "type": "revocationRequest", "certificate": le_util.jose_b64encode(cert_der), - "signature": crypto_util.create_sig(cert_der, key_file), + "signature": crypto_util.create_sig(cert_der, key, nonce), } def status_request(token): """Create ACME "statusRequest" message. - :param str token: Token provided in ACME "defer" message. + :param unicode token: Token provided in ACME "defer" message. :returns: ACME "statusRequest" message. :rtype: dict diff --git a/letsencrypt/client/acme_test.py b/letsencrypt/client/acme_test.py index dde50d5ad..df232c75a 100644 --- a/letsencrypt/client/acme_test.py +++ b/letsencrypt/client/acme_test.py @@ -54,19 +54,116 @@ class PrettyTest(unittest.TestCase): '{\n "foo": {\n "bar": "baz"\n }\n}') -class ChallengeRequestTest(unittest.TestCase): - """Tests for letsencrypt.client.acme.challenge_request""" +class MessageFactoriesTest(unittest.TestCase): + """Tests for ACME message factories from letsencrypt.client.acme.""" - def test_supports_unicode(self): - """Test support unicode parameter""" + def setUp(self): + self.privkey = """-----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-----""" + self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' + self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' + + def _validate(self, msg): + from letsencrypt.client.acme import SCHEMATA + jsonschema.validate(msg, SCHEMATA[msg['type']]) + + def _signature(self, sig): + return { + 'nonce': self.b64nonce, + 'alg': 'RS256', + 'jwk': { + 'kty': 'RSA', + 'e': 'AQAB', + 'n': 'rHVztFHtH92ucFJD_N_HW9AsdRsUuHUBBBDlHwNlRd3fp5' + '80rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3C5Q', + }, + 'sig': sig, + } + + def test_challenge_request(self): from letsencrypt.client.acme import challenge_request - self.assertEqual( - challenge_request(u'unicode'), + msg = challenge_request('example.com') + self.assertEqual(msg, { + 'type': 'challengeRequest', + 'identifier': 'example.com', + }) + self._validate(msg) + + def test_authorization_request(self): + from letsencrypt.client.acme import authorization_request + responses = [ { - "type": "challengeRequest", - "identifier": u'unicode', + 'type': 'simpleHttps', + 'path': 'Hf5GrX4Q7EBax9hc2jJnfw', + }, + None, # null + { + 'type': 'recoveryToken', + 'token': '23029d88d9e123e', } + ] + msg = authorization_request( + 'aefoGaavieG9Wihuk2aufai3aeZ5EeW4', + 'example.com', + 'czpsrF0KMH6dgajig3TGHw', + responses, + self.privkey, + self.nonce, ) + self.assertEqual(msg, { + 'type': 'authorizationRequest', + 'sessionID': 'aefoGaavieG9Wihuk2aufai3aeZ5EeW4', + 'nonce': 'czpsrF0KMH6dgajig3TGHw', + 'signature': self._signature( + 'VkpReso87ogwGul2MGck96TkYs4QoblIgNthgrm9O7EBGlzCRCnTHnx' + 'bj6loqaC4f5bn1rgS927Gp1Kvbqnmqg'), + 'responses': responses, + }) + self._validate(msg) + + def test_certificate_request(self): + from letsencrypt.client.acme import certificate_request + msg = certificate_request( + 'TODO: real DER CSR?', self.privkey, self.nonce) + self.assertEqual(msg, { + 'type': 'certificateRequest', + 'csr': 'VE9ETzogcmVhbCBERVIgQ1NSPw', + 'signature': self._signature( + 'HEQVN4MU1yDrArP2T7WZQ12XlHCn5DgTPgb5eWT5_vjRPppLSNe6uWE' + 'x9SFwG9d9umqn49nZCSW7uskA2lcW6Q'), + }) + self._validate(msg) + + def test_revocation_request(self): + from letsencrypt.client.acme import revocation_request + msg = revocation_request( + 'TODO: real DER cert?', self.privkey, self.nonce) + self.assertEqual(msg, { + 'type': 'revocationRequest', + 'certificate': 'VE9ETzogcmVhbCBERVIgY2VydD8', + 'signature': self._signature( + 'ABXA1IsyTalTXIojxmGnIUGyZASmvqEvTQ98jJ5KFs2FTswLEmsoqFX' + 'fU6l5_fous-tsbXOfLN-7PjfZ5XWPvg'), + }) + self._validate(msg) + + def test_status_request(self): + from letsencrypt.client.acme import status_request + msg = status_request(u'O7-s9MNq1siZHlgrMzi9_A') + self.assertEqual(msg, { + 'type': 'statusRequest', + 'token': u'O7-s9MNq1siZHlgrMzi9_A', + }) + self._validate(msg) + + if __name__ == '__main__': unittest.main() diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 006f6289e..88b892c67 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -209,7 +209,7 @@ class Client(object): cert_der = M2Crypto.X509.load_cert(cert["backup_cert_file"]).as_der() revocation = self.send_and_receive_expected( - acme.revocation_request(cert["backup_key_file"], cert_der), + acme.revocation_request(cert_der, cert["backup_key_file"]), "revocation") display.generic_notification( From 8bf677ff3b7eeb022b08686baa0723ab7829506e Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 9 Dec 2014 22:06:52 +0100 Subject: [PATCH 2/2] Fix acme_revocation key file bug --- letsencrypt/client/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 88b892c67..ed51752af 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -207,10 +207,11 @@ class Client(object): """ cert_der = M2Crypto.X509.load_cert(cert["backup_cert_file"]).as_der() + with open(cert["backup_key_file"], 'rU') as backup_key_file: + key = backup_key_file.read() revocation = self.send_and_receive_expected( - acme.revocation_request(cert_der, cert["backup_key_file"]), - "revocation") + acme.revocation_request(cert_der, key), "revocation") display.generic_notification( "You have successfully revoked the certificate for "