mirror of
https://github.com/certbot/certbot.git
synced 2026-07-27 16:30:31 +02:00
Rewrite JWK.load, JWKRSA autowraps ComparableRSAKey.
This commit is contained in:
+9
-14
@@ -7,16 +7,9 @@ from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
from acme.jose import errors
|
||||
from acme.jose import jwk_test
|
||||
|
||||
|
||||
RSA256_KEY = serialization.load_pem_private_key(
|
||||
pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'rsa256_key.pem')),
|
||||
password=None, backend=default_backend())
|
||||
RSA512_KEY = serialization.load_pem_private_key(
|
||||
pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'rsa512_key.pem')),
|
||||
password=None, backend=default_backend())
|
||||
RSA1024_KEY = serialization.load_pem_private_key(
|
||||
pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'rsa1024_key.pem')),
|
||||
@@ -83,13 +76,13 @@ class JWARSTest(unittest.TestCase):
|
||||
def test_sign_no_private_part(self):
|
||||
from acme.jose.jwa import RS256
|
||||
self.assertRaises(
|
||||
errors.Error, RS256.sign, RSA512_KEY.public_key(), 'foo')
|
||||
errors.Error, RS256.sign, jwk_test.RSA512_KEY.public_key(), 'foo')
|
||||
|
||||
def test_sign_key_too_small(self):
|
||||
from acme.jose.jwa import RS256
|
||||
from acme.jose.jwa import PS256
|
||||
self.assertRaises(errors.Error, RS256.sign, RSA256_KEY, 'foo')
|
||||
self.assertRaises(errors.Error, PS256.sign, RSA256_KEY, 'foo')
|
||||
self.assertRaises(errors.Error, RS256.sign, jwk_test.RSA256_KEY, 'foo')
|
||||
self.assertRaises(errors.Error, PS256.sign, jwk_test.RSA256_KEY, 'foo')
|
||||
|
||||
def test_rs(self):
|
||||
from acme.jose.jwa import RS256
|
||||
@@ -99,9 +92,11 @@ class JWARSTest(unittest.TestCase):
|
||||
'\xa4\x99\x1e\x19&\xd8\xc7\x99S\x97\xfc\x85\x0cOV\xe6\x07\x99'
|
||||
'\xd2\xb9.>}\xfd'
|
||||
)
|
||||
self.assertEqual(RS256.sign(RSA512_KEY, 'foo'), sig)
|
||||
self.assertTrue(RS256.verify(RSA512_KEY.public_key(), 'foo', sig))
|
||||
self.assertFalse(RS256.verify(RSA512_KEY.public_key(), 'foo', sig + '!'))
|
||||
self.assertEqual(RS256.sign(jwk_test.RSA512_KEY, 'foo'), sig)
|
||||
self.assertTrue(RS256.verify(
|
||||
jwk_test.RSA512_KEY.public_key(), 'foo', sig))
|
||||
self.assertFalse(RS256.verify(
|
||||
jwk_test.RSA512_KEY.public_key(), 'foo', sig + '!'))
|
||||
|
||||
def test_ps(self):
|
||||
from acme.jose.jwa import PS256
|
||||
|
||||
+74
-31
@@ -1,9 +1,12 @@
|
||||
"""JSON Web Key."""
|
||||
import abc
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
import cryptography.exceptions
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
from acme.jose import b64
|
||||
@@ -12,16 +15,16 @@ from acme.jose import json_util
|
||||
from acme.jose import util
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JWK(json_util.TypedJSONObjectWithFields):
|
||||
# pylint: disable=too-few-public-methods
|
||||
"""JSON Web Key."""
|
||||
type_field_name = 'kty'
|
||||
TYPES = {}
|
||||
|
||||
@util.abstractclassmethod
|
||||
def load(cls, string): # pragma: no cover
|
||||
"""Load key from normalized string form."""
|
||||
raise NotImplementedError()
|
||||
cryptography_key_types = ()
|
||||
"""Subclasses should override."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self): # pragma: no cover
|
||||
@@ -32,6 +35,63 @@ class JWK(json_util.TypedJSONObjectWithFields):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def _load_cryptography_key(cls, data, password=None, backend=None):
|
||||
backend = default_backend() if backend is None else backend
|
||||
exceptions = {}
|
||||
|
||||
# private key?
|
||||
for loader in (serialization.load_pem_private_key,
|
||||
serialization.load_der_private_key):
|
||||
try:
|
||||
return loader(data, password, backend)
|
||||
except (ValueError, TypeError,
|
||||
cryptography.exceptions.UnsupportedAlgorithm) as error:
|
||||
exceptions[loader] = error
|
||||
|
||||
# public key?
|
||||
for loader in (serialization.load_pem_public_key,
|
||||
serialization.load_der_public_key):
|
||||
try:
|
||||
return loader(data, backend)
|
||||
except (ValueError,
|
||||
cryptography.exceptions.UnsupportedAlgorithm) as error:
|
||||
exceptions[loader] = error
|
||||
|
||||
# no luck
|
||||
raise errors.Error("Unable to deserialize key: {0}".format(exceptions))
|
||||
|
||||
@classmethod
|
||||
def load(cls, data, password=None, backend=None):
|
||||
"""Load serialized key as JWK.
|
||||
|
||||
:param str data: Public or private key serialized as PEM or DER.
|
||||
:param str password: Optional password.
|
||||
:param backend: A `.PEMSerializationBackend` and
|
||||
`.DERSerializationBackend` provider.
|
||||
|
||||
:raises errors.Error: if unable to deserialize, or unsupported
|
||||
JWK algorithm
|
||||
|
||||
:returns: JWK of an appropriate type.
|
||||
:rtype: `JWK`
|
||||
|
||||
"""
|
||||
try:
|
||||
key = cls._load_cryptography_key(data, password, backend)
|
||||
except errors.Error as error:
|
||||
logger.debug("Loading symmetric key, assymentric failed: %s", error)
|
||||
return JWKOct(key=data)
|
||||
|
||||
if cls.typ is not NotImplemented and not isinstance(
|
||||
key, cls.cryptography_key_types):
|
||||
raise errors.Error("Unable to deserialize {0} into {1}".format(
|
||||
key.__class__, cls.__class__))
|
||||
for jwk_cls in cls.TYPES.itervalues():
|
||||
if isinstance(key, jwk_cls.cryptography_key_types):
|
||||
return jwk_cls(key=key)
|
||||
raise errors.Error("Unsupported algorithm: {0}".format(key.__class__))
|
||||
|
||||
|
||||
@JWK.register
|
||||
class JWKES(JWK): # pragma: no cover
|
||||
@@ -42,6 +102,8 @@ class JWKES(JWK): # pragma: no cover
|
||||
|
||||
"""
|
||||
typ = 'ES'
|
||||
cryptography_key_types = (
|
||||
ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey)
|
||||
|
||||
def fields_to_partial_json(self):
|
||||
raise NotImplementedError()
|
||||
@@ -50,10 +112,6 @@ class JWKES(JWK): # pragma: no cover
|
||||
def fields_from_json(cls, jobj):
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def load(cls, string):
|
||||
raise NotImplementedError()
|
||||
|
||||
def public_key(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -75,10 +133,6 @@ class JWKOct(JWK):
|
||||
def fields_from_json(cls, jobj):
|
||||
return cls(key=jobj['k'])
|
||||
|
||||
@classmethod
|
||||
def load(cls, string):
|
||||
return cls(key=string)
|
||||
|
||||
def public_key(self):
|
||||
return self
|
||||
|
||||
@@ -93,8 +147,15 @@ class JWKRSA(JWK):
|
||||
|
||||
"""
|
||||
typ = 'RSA'
|
||||
cryptography_key_types = (rsa.RSAPublicKey, rsa.RSAPrivateKey)
|
||||
__slots__ = ('key',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'key' in kwargs and not isinstance(
|
||||
kwargs['key'], util.ComparableRSAKey):
|
||||
kwargs['key'] = util.ComparableRSAKey(kwargs['key'])
|
||||
super(JWKRSA, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _encode_param(cls, data):
|
||||
def _leading_zeros(arg):
|
||||
@@ -112,24 +173,6 @@ class JWKRSA(JWK):
|
||||
except ValueError: # invalid literal for long() with base 16
|
||||
raise errors.DeserializationError()
|
||||
|
||||
@classmethod
|
||||
def load(cls, string):
|
||||
"""Load RSA key from string.
|
||||
|
||||
:param str string: RSA key in string form.
|
||||
|
||||
:returns:
|
||||
:rtype: :class:`JWKRSA`
|
||||
|
||||
"""
|
||||
try:
|
||||
key = serialization.load_pem_public_key(
|
||||
string, backend=default_backend())
|
||||
except ValueError: # ValueError: Could not unserialize key data.
|
||||
key = serialization.load_pem_private_key(
|
||||
string, password=None, backend=default_backend())
|
||||
return cls(key=util.ComparableRSAKey(key))
|
||||
|
||||
def public_key(self):
|
||||
return type(self)(key=self.key.public_key())
|
||||
|
||||
|
||||
+25
-4
@@ -10,14 +10,28 @@ from acme.jose import errors
|
||||
from acme.jose import util
|
||||
|
||||
|
||||
RSA256_KEY = util.ComparableRSAKey(serialization.load_pem_private_key(
|
||||
DSA_PEM = pkg_resources.resource_string(
|
||||
'letsencrypt.tests', os.path.join('testdata', 'dsa512_key.pem'))
|
||||
RSA256_KEY = serialization.load_pem_private_key(
|
||||
pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'rsa256_key.pem')),
|
||||
password=None, backend=default_backend()))
|
||||
RSA512_KEY = util.ComparableRSAKey(serialization.load_pem_private_key(
|
||||
password=None, backend=default_backend())
|
||||
RSA512_KEY = serialization.load_pem_private_key(
|
||||
pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'rsa512_key.pem')),
|
||||
password=None, backend=default_backend()))
|
||||
password=None, backend=default_backend())
|
||||
|
||||
|
||||
class JWKTest(unittest.TestCase):
|
||||
"""Tests for acme.jose.jwk.JWK."""
|
||||
|
||||
def test_load(self):
|
||||
from acme.jose.jwk import JWK
|
||||
self.assertRaises(errors.Error, JWK.load, DSA_PEM)
|
||||
|
||||
def test_load_subclass_wrong_type(self):
|
||||
from acme.jose.jwk import JWKRSA
|
||||
self.assertRaises(errors.Error, JWKRSA.load, DSA_PEM)
|
||||
|
||||
|
||||
class JWKOctTest(unittest.TestCase):
|
||||
@@ -49,6 +63,7 @@ class JWKOctTest(unittest.TestCase):
|
||||
|
||||
class JWKRSATest(unittest.TestCase):
|
||||
"""Tests for acme.jose.jwk.JWKRSA."""
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
def setUp(self):
|
||||
from acme.jose.jwk import JWKRSA
|
||||
@@ -58,6 +73,8 @@ class JWKRSATest(unittest.TestCase):
|
||||
'e': 'AQAB',
|
||||
'n': 'm2Fylv-Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEk',
|
||||
}
|
||||
self.jwk256_comparable = JWKRSA(key=util.ComparableRSAKey(
|
||||
RSA256_KEY.public_key()))
|
||||
self.jwk512 = JWKRSA(key=RSA512_KEY.public_key())
|
||||
self.jwk512json = {
|
||||
'kty': 'RSA',
|
||||
@@ -79,6 +96,10 @@ class JWKRSATest(unittest.TestCase):
|
||||
'qi': 'oi45cEkbVoJjAbnQpFY87Q',
|
||||
})
|
||||
|
||||
def test_init_comparable(self):
|
||||
self.assertTrue(isinstance(self.jwk256.key, util.ComparableRSAKey))
|
||||
self.assertEqual(self.jwk256, self.jwk256_comparable)
|
||||
|
||||
def test_equals(self):
|
||||
self.assertEqual(self.jwk256, self.jwk256)
|
||||
self.assertEqual(self.jwk512, self.jwk512)
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ class Signature(jose.JSONObjectWithFields):
|
||||
|
||||
:param key: Key used for signing.
|
||||
:type key: `cryptography.hazmat.primitives.assymetric.rsa.RSAPrivateKey`
|
||||
wrapped in `.ComparableRSAKey`.
|
||||
(optionally wrapped in `.ComparableRSAKey`).
|
||||
|
||||
:param str nonce: Nonce to be used. If None, nonce of
|
||||
``nonce_size`` will be randomly generated.
|
||||
|
||||
Reference in New Issue
Block a user