Rewrite JWK.load, JWKRSA autowraps ComparableRSAKey.

This commit is contained in:
Jakub Warmuz
2015-07-08 12:00:16 +00:00
parent 0955012569
commit a7817de4ab
4 changed files with 109 additions and 50 deletions
+9 -14
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.