mirror of
https://github.com/certbot/certbot.git
synced 2026-07-30 18:04:31 +02:00
ACME Signature: JWK with pubkey only
This commit is contained in:
@@ -17,7 +17,7 @@ def _leading_zeros(arg):
|
|||||||
class JWK(object):
|
class JWK(object):
|
||||||
"""JSON Web Key.
|
"""JSON Web Key.
|
||||||
|
|
||||||
.. todo:: Currently works for RSA keys only.
|
.. todo:: Currently works for RSA public keys only.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
zope.interface.implements(interfaces.IJSONSerializable)
|
zope.interface.implements(interfaces.IJSONSerializable)
|
||||||
@@ -32,14 +32,6 @@ class JWK(object):
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
'Unable to compare JWK object with: {0}'.format(other))
|
'Unable to compare JWK object with: {0}'.format(other))
|
||||||
|
|
||||||
def same_public_key(self, other):
|
|
||||||
"""Does ``other`` have the same public key?"""
|
|
||||||
if isinstance(other, JWK):
|
|
||||||
return self.key.publickey() == other.key.publickey()
|
|
||||||
else:
|
|
||||||
raise TypeError(
|
|
||||||
'Unable to compare JWK object with: {0}'.format(other))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _encode_param(cls, param):
|
def _encode_param(cls, param):
|
||||||
"""Encode numeric key parameter."""
|
"""Encode numeric key parameter."""
|
||||||
|
|||||||
@@ -5,26 +5,31 @@ import unittest
|
|||||||
import Crypto.PublicKey.RSA
|
import Crypto.PublicKey.RSA
|
||||||
|
|
||||||
|
|
||||||
RSA256_KEY_PATH = pkg_resources.resource_string(
|
RSA256_KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string(
|
||||||
'letsencrypt.client.tests', 'testdata/rsa256_key.pem')
|
'letsencrypt.client.tests', 'testdata/rsa256_key.pem'))
|
||||||
RSA256_KEY = Crypto.PublicKey.RSA.importKey(RSA256_KEY_PATH)
|
RSA512_KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string(
|
||||||
RSA512_KEY_PATH = pkg_resources.resource_string(
|
'letsencrypt.client.tests', 'testdata/rsa512_key.pem'))
|
||||||
'letsencrypt.client.tests', 'testdata/rsa512_key.pem')
|
|
||||||
RSA512_KEY = Crypto.PublicKey.RSA.importKey(RSA512_KEY_PATH)
|
|
||||||
|
|
||||||
|
|
||||||
class JWKTest(unittest.TestCase):
|
class JWKTest(unittest.TestCase):
|
||||||
|
"""Tests fro letsencrypt.acme.jose.JWK."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.acme.jose import JWK
|
from letsencrypt.acme.jose import JWK
|
||||||
self.jwk256 = JWK(RSA256_KEY)
|
self.jwk256 = JWK(RSA256_KEY.publickey())
|
||||||
self.jwk256json = {
|
self.jwk256json = {
|
||||||
'kty': 'RSA',
|
'kty': 'RSA',
|
||||||
'e': 'AQAB',
|
'e': 'AQAB',
|
||||||
'n': 'rHVztFHtH92ucFJD_N_HW9AsdRsUuHUBBBDlHwNlRd3fp5'
|
'n': 'rHVztFHtH92ucFJD_N_HW9AsdRsUuHUBBBDlHwNlRd3fp5'
|
||||||
'80rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3C5Q',
|
'80rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3C5Q',
|
||||||
}
|
}
|
||||||
self.jwk512 = JWK(RSA512_KEY)
|
self.jwk512 = JWK(RSA512_KEY.publickey())
|
||||||
|
self.jwk512json = {
|
||||||
|
'kty': 'RSA',
|
||||||
|
'e': 'AQAB',
|
||||||
|
'n': '9LYRcVE3Nr-qleecEcX8JwVDnjeG1X7ucsCasuuZM0e09c'
|
||||||
|
'mYuUzxIkMjO_9x4AVcvXXRXPEV-LzWWkfkTlzRMw',
|
||||||
|
}
|
||||||
|
|
||||||
def test_equals(self):
|
def test_equals(self):
|
||||||
self.assertEqual(self.jwk256, self.jwk256)
|
self.assertEqual(self.jwk256, self.jwk256)
|
||||||
@@ -37,24 +42,14 @@ class JWKTest(unittest.TestCase):
|
|||||||
def test_equals_raises_type_error(self):
|
def test_equals_raises_type_error(self):
|
||||||
self.assertRaises(TypeError, self.jwk256.__eq__, 123)
|
self.assertRaises(TypeError, self.jwk256.__eq__, 123)
|
||||||
|
|
||||||
def test_same_public_key(self):
|
|
||||||
from letsencrypt.acme.jose import JWK
|
|
||||||
self.assertTrue(self.jwk256.same_public_key(
|
|
||||||
JWK(Crypto.PublicKey.RSA.importKey(RSA256_KEY_PATH))))
|
|
||||||
|
|
||||||
def test_not_same_public_key(self):
|
|
||||||
self.assertFalse(self.jwk256.same_public_key(self.jwk512))
|
|
||||||
|
|
||||||
def test_same_public_key_raises_type_error(self):
|
|
||||||
self.assertRaises(TypeError, self.jwk256.same_public_key, 5)
|
|
||||||
|
|
||||||
def test_to_json(self):
|
def test_to_json(self):
|
||||||
self.assertEqual(self.jwk256.to_json(), self.jwk256json)
|
self.assertEqual(self.jwk256.to_json(), self.jwk256json)
|
||||||
|
self.assertEqual(self.jwk512.to_json(), self.jwk512json)
|
||||||
|
|
||||||
def test_from_json(self):
|
def test_from_json(self):
|
||||||
from letsencrypt.acme.jose import JWK
|
from letsencrypt.acme.jose import JWK
|
||||||
self.assertTrue(self.jwk256.same_public_key(
|
self.assertEqual(self.jwk256, JWK.from_json(self.jwk256json))
|
||||||
JWK.from_json(self.jwk256json)))
|
self.assertEqual(self.jwk512, JWK.from_json(self.jwk512json))
|
||||||
|
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Base64#Examples
|
# https://en.wikipedia.org/wiki/Base64#Examples
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class Signature(object):
|
|||||||
|
|
||||||
logging.debug('%s signed as %s', msg_with_nonce, sig)
|
logging.debug('%s signed as %s', msg_with_nonce, sig)
|
||||||
|
|
||||||
return cls('RS256', sig, nonce, jose.JWK(key))
|
return cls('RS256', sig, nonce, jose.JWK(key.publickey()))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, Signature):
|
if isinstance(other, Signature):
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class SigatureTest(unittest.TestCase):
|
|||||||
'\xb9X\xc3w\xaa\xc0_\xd0\x05$y>l#\x10<\x96\xd2\xcdr\xa3'
|
'\xb9X\xc3w\xaa\xc0_\xd0\x05$y>l#\x10<\x96\xd2\xcdr\xa3'
|
||||||
'\x1b\xa1\xf5!f\xef\xc64\xb6\x13')
|
'\x1b\xa1\xf5!f\xef\xc64\xb6\x13')
|
||||||
self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9'
|
self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9'
|
||||||
self.jwk = jose.JWK(RSA256_KEY)
|
self.jwk = jose.JWK(RSA256_KEY.publickey())
|
||||||
|
|
||||||
b64sig = ('SUPYKucUnhlTt8_sMxLiigOYdf_wlOLXPI-o7aRLTsOquVjDd6r'
|
b64sig = ('SUPYKucUnhlTt8_sMxLiigOYdf_wlOLXPI-o7aRLTsOquVjDd6r'
|
||||||
'AX9AFJHk-bCMQPJbSzXKjG6H1IWbvxjS2Ew')
|
'AX9AFJHk-bCMQPJbSzXKjG6H1IWbvxjS2Ew')
|
||||||
@@ -39,60 +39,53 @@ class SigatureTest(unittest.TestCase):
|
|||||||
'sig': b64sig,
|
'sig': b64sig,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pub_jwk = jose.JWK(RSA256_KEY.publickey())
|
|
||||||
self.jsig_from = {
|
self.jsig_from = {
|
||||||
'nonce': b64nonce,
|
'nonce': b64nonce,
|
||||||
'alg': self.alg,
|
'alg': self.alg,
|
||||||
'jwk': self.pub_jwk.to_json(),
|
'jwk': self.jwk.to_json(),
|
||||||
'sig': b64sig,
|
'sig': b64sig,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.signature = self._from_msg(self.msg, RSA256_KEY, self.nonce)
|
|
||||||
from letsencrypt.acme.other import Signature
|
from letsencrypt.acme.other import Signature
|
||||||
self.pub_signature = Signature(
|
self.signature = Signature(self.alg, self.sig, self.nonce, self.jwk)
|
||||||
self.alg, self.sig, self.nonce, self.pub_jwk)
|
|
||||||
|
def test_attributes(self):
|
||||||
|
self.assertEqual(self.signature.nonce, self.nonce)
|
||||||
|
self.assertEqual(self.signature.alg, self.alg)
|
||||||
|
self.assertEqual(self.signature.sig, self.sig)
|
||||||
|
self.assertEqual(self.signature.jwk, self.jwk)
|
||||||
|
|
||||||
|
def test_verify_good_succeeds(self):
|
||||||
|
self.assertTrue(self.signature.verify(self.msg))
|
||||||
|
|
||||||
|
def test_verify_bad_fails(self):
|
||||||
|
self.assertFalse(self.signature.verify(self.msg + 'x'))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_msg(cls, *args, **kwargs):
|
def _from_msg(cls, *args, **kwargs):
|
||||||
from letsencrypt.acme.other import Signature
|
from letsencrypt.acme.other import Signature
|
||||||
return Signature.from_msg(*args, **kwargs)
|
return Signature.from_msg(*args, **kwargs)
|
||||||
|
|
||||||
def test_verify_with_private_key(self):
|
|
||||||
self.assertTrue(self.signature.verify(self.msg))
|
|
||||||
|
|
||||||
def test_verify_without_private_key(self):
|
|
||||||
self.assertTrue(self.pub_signature.verify(self.msg))
|
|
||||||
|
|
||||||
def test_verify_bad_fails(self):
|
|
||||||
self.signature.sig = self.sig + "foo"
|
|
||||||
self.assertFalse(self.signature.verify(self.msg))
|
|
||||||
|
|
||||||
def test_create_from_msg(self):
|
def test_create_from_msg(self):
|
||||||
self.assertEqual(self.signature.nonce, self.nonce)
|
signature = self._from_msg(self.msg, RSA256_KEY, self.nonce)
|
||||||
self.assertEqual(self.signature.alg, self.alg)
|
self.assertEqual(self.signature, signature)
|
||||||
self.assertEqual(self.signature.sig, self.sig)
|
|
||||||
self.assertEqual(self.signature.jwk, self.jwk)
|
|
||||||
|
|
||||||
def test_create_from_msg_random_nonce(self):
|
def test_create_from_msg_random_nonce(self):
|
||||||
sig = self._from_msg(self.msg, RSA256_KEY)
|
signature = self._from_msg(self.msg, RSA256_KEY)
|
||||||
self.assertEqual(sig.alg, self.alg)
|
self.assertEqual(signature.alg, self.alg)
|
||||||
self.assertEqual(sig.jwk, self.jwk)
|
self.assertEqual(signature.jwk, self.jwk)
|
||||||
self.assertTrue(sig.verify(self.msg))
|
self.assertTrue(signature.verify(self.msg))
|
||||||
|
|
||||||
def test_to_json(self):
|
def test_to_json(self):
|
||||||
self.assertEqual(self.signature.to_json(), self.jsig_to)
|
self.assertEqual(self.signature.to_json(), self.jsig_to)
|
||||||
|
|
||||||
def test_from_json(self):
|
def test_from_json(self):
|
||||||
from letsencrypt.acme.other import Signature
|
from letsencrypt.acme.other import Signature
|
||||||
signature = Signature.from_json(self.jsig_from)
|
self.assertEqual(self.signature, Signature.from_json(self.jsig_from))
|
||||||
self.assertEqual(self.pub_signature, signature)
|
|
||||||
|
|
||||||
def test_sig_and_pub_sig_not_equal(self):
|
|
||||||
self.assertNotEqual(self.pub_signature, self.signature)
|
|
||||||
|
|
||||||
def test_eq_raises_type_error(self):
|
def test_eq_raises_type_error(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
TypeError, functools.partial(operator.eq, self.signature), "foo")
|
TypeError, functools.partial(operator.eq, self.signature), 'foo')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user