From 28e7b9d7e91a71d03793507f2a74e6ae6fa8e7ba Mon Sep 17 00:00:00 2001 From: Drew Hubl Date: Fri, 6 Jul 2018 16:52:57 -0700 Subject: [PATCH 1/5] Add backend and tests for Ed25519 keys (RFC8037) --- jose/backends/nacl_backend.py | 103 ++++++++++++++++++++++ jose/constants.py | 17 +++- requirements.txt | 1 + setup.py | 2 +- tests/algorithms/test_Ed25519.py | 146 +++++++++++++++++++++++++++++++ 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 jose/backends/nacl_backend.py create mode 100644 tests/algorithms/test_Ed25519.py diff --git a/jose/backends/nacl_backend.py b/jose/backends/nacl_backend.py new file mode 100644 index 00000000..ca304fa2 --- /dev/null +++ b/jose/backends/nacl_backend.py @@ -0,0 +1,103 @@ +import base64 + +import six + +from jose.backends.base import Key +from jose.constants import ALGORITHMS, USAGES +from jose.exceptions import JWKError + +from nacl.encoding import RawEncoder, URLSafeBase64Encoder +from nacl.exceptions import BadSignatureError +from nacl.signing import SigningKey, VerifyKey + + +class Ed25519Key(Key): + def __init__(self, key, algorithm, use=None): + if algorithm not in ALGORITHMS.ED: + raise JWKError('hash_alg: %s is not a valid Ed25519 hash algorithm' % algorithm) + + # TODO: Validate Ed25519 hash algorithms + self._algorithm = algorithm + + if isinstance(key, dict): + self._prepared_key = self._process_jwk(key) + return + + if isinstance(key, (SigningKey, VerifyKey)): + self._prepared_key = key + return + + if isinstance(key, six.string_types): + key = key.encode('utf-8') + b'==' + + if isinstance(key, six.binary_type): + if use is None: + raise JWKError("The 'use' parameter is required when deserializing an Ed25519 key " + "from a string or bytes") + + if use == USAGES.PUBLIC: + self._prepared_key = VerifyKey(key, encoder=URLSafeBase64Encoder) + elif use == USAGES.PRIVATE: + self._prepared_key = SigningKey(key, encoder=URLSafeBase64Encoder) + else: + raise JWKError("The 'use' parameter must either be 'public' or 'private', not %s" % use) + return + + raise JWKError('Unable to parse an Ed25519_JWK from key: %s' % key) + + def _process_jwk(self, jwk_dict): + if not jwk_dict.get('kty') == 'OKP': + raise JWKError("Incorrect key type. Expected: 'OKP', Received: %s" % jwk_dict.get('kty')) + + if not jwk_dict.get('crv') == 'Ed25519': + raise JWKError("Incorrect key subtype. Expected 'Ed25519', Received %s" % jwk_dict.get('crv')) + + if 'd' in jwk_dict: + # d indicates private key + d = jwk_dict.get('d').encode('utf-8') + b'==' + return SigningKey(d, encoder=URLSafeBase64Encoder) + else: + # no d indicates public key + x = jwk_dict.get('x').encode('utf-8') + b'==' + return VerifyKey(x, encoder=URLSafeBase64Encoder) + + def sign(self, msg): + if isinstance(msg, six.string_types): + msg = msg.encode('utf-8') + return self._prepared_key.sign(msg, encoder=RawEncoder) + + def verify(self, msg, sig=None): + try: + self._prepared_key.verify(msg, sig, encoder=RawEncoder) + return True + except BadSignatureError: + return False + + def is_public(self): + return isinstance(self._prepared_key, VerifyKey) + + def public_key(self): + if isinstance(self._prepared_key, VerifyKey): + return self + return self.__class__(self._prepared_key.verify_key, self._algorithm) + + def to_pem(self, *args, **kwargs): + # Serializing Ed25519 keys is not yet supported anywhere in Python + # AFAICT, so instead of creating our own format, we simply prevent + # anybody from serializing to PEM. + raise NotImplementedError("Cannot serialize Ed25519 keys yet") + + def to_dict(self): + public_key = self.public_key() + + data = { + 'alg': self._algorithm, + 'kty': 'OKP', + 'crv': 'Ed25519', + 'x': base64.urlsafe_b64encode(bytes(public_key._prepared_key)).decode('utf-8'), + } + + if not self.is_public(): + data.update({'d': base64.urlsafe_b64encode(bytes(self._prepared_key)).decode('utf-8')}) + + return data diff --git a/jose/constants.py b/jose/constants.py index eb146549..35a45e22 100644 --- a/jose/constants.py +++ b/jose/constants.py @@ -12,12 +12,15 @@ class Algorithms(object): ES256 = 'ES256' ES384 = 'ES384' ES512 = 'ES512' + # RFC8037 - https://tools.ietf.org/html/rfc8037 + EdDSA = 'EdDSA' HMAC = {HS256, HS384, HS512} RSA = {RS256, RS384, RS512} EC = {ES256, ES384, ES512} + ED = {EdDSA} - SUPPORTED = HMAC.union(RSA).union(EC) + SUPPORTED = HMAC.union(RSA).union(EC).union(ED) ALL = SUPPORTED.union([NONE]) @@ -37,3 +40,15 @@ class Algorithms(object): ALGORITHMS = Algorithms() + + +class Usages(object): + PUBLIC = 'public' + PRIVATE = 'private' + + SUPPORTED = {PUBLIC, PRIVATE} + + ALL = SUPPORTED + + +USAGES = Usages() diff --git a/requirements.txt b/requirements.txt index 04dd5d1b..131e5877 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ six rsa ecdsa pyasn1 +pynacl diff --git a/setup.py b/setup.py index 01281158..414d9392 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ def _cryptography_version(): 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'] + pyasn1, } legacy_backend_requires = ['ecdsa <1.0', 'rsa'] + pyasn1 -install_requires = ['six <2.0'] +install_requires = ['six <2.0', 'pynacl'] # TODO: work this into the extras selection instead. install_requires += legacy_backend_requires diff --git a/tests/algorithms/test_Ed25519.py b/tests/algorithms/test_Ed25519.py new file mode 100644 index 00000000..5efcb7c4 --- /dev/null +++ b/tests/algorithms/test_Ed25519.py @@ -0,0 +1,146 @@ + +import base64 + +from jose.backends.nacl_backend import Ed25519Key +from jose.constants import ALGORITHMS, USAGES +from jose.exceptions import JWKError + +from nacl.encoding import URLSafeBase64Encoder +from nacl.signing import SigningKey, VerifyKey + +import pytest + + +SIGNING_KEY = "npAVhmIfq2byvIzcmgS5cguKCv2Nw8Seqa1Fku00LoE=" +nacl_signing_key = SigningKey(SIGNING_KEY.encode('utf-8'), encoder=URLSafeBase64Encoder) +nacl_verify_key = nacl_signing_key.verify_key +VERIFY_KEY = base64.urlsafe_b64encode(bytes(nacl_verify_key)) + + +class TestEd25519Algorithm: + + @pytest.mark.parametrize("alg", ALGORITHMS.ED) + @pytest.mark.parametrize("use", USAGES.ALL) + def test_Ed25519_key(self, alg, use): + assert Ed25519Key(SIGNING_KEY, algorithm=alg, use=use)._prepared_key + assert Ed25519Key(SIGNING_KEY.encode('utf-8'), algorithm=alg, use=use)._prepared_key + # With Ed25519, there is no difference between seeds for private and public keys, and ALL + # 256-bit values are valid seeds, so since we have already tested with private key seeds, + # we do not need to also test for public key seeds + + @pytest.mark.parametrize("alg", ALGORITHMS.ED) + def test_Ed25519_signing_key(self, alg): + assert Ed25519Key(nacl_signing_key, algorithm=alg)._prepared_key + assert not Ed25519Key(nacl_signing_key, algorithm=alg).is_public() + + @pytest.mark.parametrize("alg", ALGORITHMS.ED) + def test_Ed25519_verify_key(self, alg): + assert Ed25519Key(nacl_verify_key, algorithm=alg)._prepared_key + assert Ed25519Key(nacl_verify_key, algorithm=alg).is_public() + + def test_Ed25519_key_unknown_object(self): + with pytest.raises(JWKError): + Ed25519Key(object(), algorithm=ALGORITHMS.EdDSA) + + @pytest.mark.parametrize("use", USAGES.ALL) + def test_Ed25519_key_bad_alg(self, use): + with pytest.raises(JWKError): + Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.ES256, use=use) + + @pytest.mark.parametrize("alg", ALGORITHMS.ED) + def test_Ed25519_key_bad_use(self, alg): + with pytest.raises(JWKError): + Ed25519Key(SIGNING_KEY, algorithm=alg) + + with pytest.raises(JWKError): + Ed25519Key(SIGNING_KEY, algorithm=alg, use=None) + + with pytest.raises(JWKError): + Ed25519Key(SIGNING_KEY, algorithm=alg, use='bad_usage') + + def test_get_verify_key(self): + signing_key = Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PRIVATE) + + assert not signing_key.is_public() + + verify_key = signing_key.public_key() # public_key is part of the Key API + verify_key2 = verify_key.public_key() + + assert verify_key.is_public() + assert verify_key is verify_key2 + + def test_to_pem(self): + signing_key = Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PRIVATE) + + with pytest.raises(NotImplementedError): + signing_key.to_pem() + + def test_verify_key_to_pem(self): + signing_key = Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PRIVATE) + verify_key = signing_key.public_key() + + with pytest.raises(NotImplementedError): + verify_key.to_pem() + + def assert_parameters(self, as_dict, private): + assert isinstance(as_dict, dict) + + # Public parameters should always be there + assert 'x' in as_dict + + if private: + # Private parameters as well + assert 'd' in as_dict + else: + # Private parameters should be absent + assert 'd' not in as_dict + + def assert_roundtrip(self, key, use): + assert Ed25519Key(key.to_dict(), ALGORITHMS.EdDSA, use=use).to_dict() == key.to_dict() + + def test_signing_key_to_dict(self): + key = Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PRIVATE) + + self.assert_parameters(key.to_dict(), private=True) + self.assert_roundtrip(key, use=USAGES.PRIVATE) + + def test_verify_key_to_dict(self): + key = Ed25519Key(VERIFY_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PUBLIC) + + self.assert_parameters(key.to_dict(), private=False) + self.assert_roundtrip(key, use=USAGES.PUBLIC) + + def test_verify_key_from_bad_dict(self): + key = Ed25519Key(VERIFY_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PUBLIC) + key_dict = key.to_dict() + + bad_key_dict = key_dict.copy() + bad_key_dict['kty'] = "SOMETHING_ELSE" + + with pytest.raises(JWKError): + Ed25519Key(bad_key_dict, algorithm=ALGORITHMS.EdDSA) + + bad_key_dict = key_dict.copy() + bad_key_dict['crv'] = "SOMETHING_ELSE" + + with pytest.raises(JWKError): + Ed25519Key(bad_key_dict, algorithm=ALGORITHMS.EdDSA) + + def test_signing_parity(self): + signing_key = Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PRIVATE) + verify_key = signing_key.public_key() + + msg = b'test' + smsg = signing_key.sign(msg) # -> signature + cleartext message + bad_smsg_message = bytes([(b + 1) if b < 255 else 0 for b in bytearray(smsg.message)]) + bad_smsg_signature = bytes([(b + 1) if b < 255 else 0 for b in bytearray(smsg.signature)]) + bad_smsg = bad_smsg_signature + bad_smsg_message + + assert verify_key.verify(smsg) + assert verify_key.verify(smsg.signature + smsg.message) + assert verify_key.verify(smsg.message, smsg.signature) + + assert not verify_key.verify(smsg.message, bad_smsg_signature) + assert not verify_key.verify(bad_smsg_message, smsg.signature) + assert not verify_key.verify(bad_smsg_message, bad_smsg_signature) + assert not verify_key.verify(bad_smsg) From 181781f8e7c7c823cdf64ed55c5279b8eb7892d4 Mon Sep 17 00:00:00 2001 From: Drew Hubl Date: Fri, 6 Jul 2018 16:58:29 -0700 Subject: [PATCH 2/5] Integrate Ed25519 key backend into JWK --- jose/backends/__init__.py | 2 ++ jose/jwk.py | 13 +++++++++++-- tests/test_jwk.py | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/jose/backends/__init__.py b/jose/backends/__init__.py index d1b9fa1a..6c209a67 100644 --- a/jose/backends/__init__.py +++ b/jose/backends/__init__.py @@ -11,3 +11,5 @@ from jose.backends.cryptography_backend import CryptographyECKey as ECKey # noqa: F401 except ImportError: from jose.backends.ecdsa_backend import ECDSAECKey as ECKey # noqa: F401 + +from jose.backends.nacl_backend import Ed25519Key diff --git a/jose/jwk.py b/jose/jwk.py index 87f30b41..cde13d83 100644 --- a/jose/jwk.py +++ b/jose/jwk.py @@ -20,6 +20,9 @@ pass +from jose.backends import Ed25519Key + + def get_key(algorithm): if algorithm in ALGORITHMS.KEYS: return ALGORITHMS.KEYS[algorithm] @@ -31,6 +34,9 @@ def get_key(algorithm): elif algorithm in ALGORITHMS.EC: from jose.backends import ECKey # noqa: F811 return ECKey + elif algorithm in ALGORITHMS.ED: + from jose.backends import Ed25519Key + return Ed25519Key return None @@ -42,7 +48,7 @@ def register_key(algorithm, key_class): return True -def construct(key_data, algorithm=None): +def construct(key_data, algorithm=None, use=None): """ Construct a Key object for the given algorithm with the given key_data. @@ -58,7 +64,10 @@ def construct(key_data, algorithm=None): key_class = get_key(algorithm) if not key_class: raise JWKError('Unable to find a algorithm for key: %s' % key_data) - return key_class(key_data, algorithm) + if use is None: + return key_class(key_data, algorithm) + else: + return key_class(key_data, algorithm, use) def get_algorithm_object(algorithm): diff --git a/tests/test_jwk.py b/tests/test_jwk.py index 6ea5a1b8..d062d4f5 100644 --- a/tests/test_jwk.py +++ b/tests/test_jwk.py @@ -30,6 +30,13 @@ "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" } +ed25519_key = { + "kty": "OKP", + "crv": "Ed25519", + "d": "npAVhmIfq2byvIzcmgS5cguKCv2Nw8Seqa1Fku00LoE", + "x": "th-Fe1Whyvy0vdexhMwSybtIyMh-WiYgUTogOKXfVnI", +} + class TestJWK: @@ -53,6 +60,9 @@ def test_invalid_hash_alg(self): with pytest.raises(JWKError): key = ECKey(ec_key, 'RS512') # noqa: F841 + with pytest.raises(JWKError): + key = Ed25519Key(ed25519_key, 'RS512') + def test_invalid_jwk(self): with pytest.raises(JWKError): @@ -64,6 +74,9 @@ def test_invalid_jwk(self): with pytest.raises(JWKError): key = ECKey(rsa_key, 'ES256') # noqa: F841 + with pytest.raises(JWKError): + key = Ed25519Key(rsa_key, 'EdDSA') + def test_RSAKey_errors(self): rsa_key = { @@ -101,6 +114,9 @@ def test_construct_from_jwk(self): key = jwk.construct(hmac_key) assert isinstance(key, jwk.Key) + key = jwk.construct(ed25519_key, algorithm='EdDSA', use='private') + assert isinstance(key, jwk.Key) + def test_construct_EC_from_jwk(self): key = ECKey(ec_key, algorithm='ES512') assert isinstance(key, jwk.Key) @@ -126,6 +142,7 @@ def test_get_key(self): assert issubclass(hs_key, Key) assert issubclass(jwk.get_key("RS256"), Key) assert issubclass(jwk.get_key("ES256"), Key) + assert issubclass(jwk.get_key("EdDSA"), Key) assert jwk.get_key("NONEXISTENT") is None From 16b4948d77736008d8ffaa14d6b842889f00b8e9 Mon Sep 17 00:00:00 2001 From: blag Date: Tue, 27 Nov 2018 02:53:24 -0700 Subject: [PATCH 3/5] Refactor to make PyNaCl an optional dependency --- jose/backends/__init__.py | 15 ++++++++++++++- jose/constants.py | 3 ++- jose/jwk.py | 8 +++++--- setup.py | 1 + 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/jose/backends/__init__.py b/jose/backends/__init__.py index 6c209a67..a17814df 100644 --- a/jose/backends/__init__.py +++ b/jose/backends/__init__.py @@ -12,4 +12,17 @@ except ImportError: from jose.backends.ecdsa_backend import ECDSAECKey as ECKey # noqa: F401 -from jose.backends.nacl_backend import Ed25519Key +try: + from jose.backends.nacl_backend import Ed25519Key # noqa: F401 +except ImportError: + pass +else: + # Since PyNaCl is an optional dependency, we do not add EdDSA to the set + # of supported algorithms in the jose.constants module. + # As a result, when we successfully import Ed25519Key, we need to manually + # register that algorithm, but we cannot do it in the jose.constants module + # because that would create a circular import. Instead, we do it here. + # TODO: Refactor to use __init_subclass__ hook on jose.backends.base.Key + import jose.constants as j_c + j_c.ALGORITHMS.SUPPORTED = j_c.ALGORITHMS.SUPPORTED.union(j_c.ALGORITHMS.ED) + j_c.ALGORITHMS.ALL = j_c.ALGORITHMS.SUPPORTED.union([j_c.ALGORITHMS.NONE]) diff --git a/jose/constants.py b/jose/constants.py index 35a45e22..3f202d6d 100644 --- a/jose/constants.py +++ b/jose/constants.py @@ -20,7 +20,8 @@ class Algorithms(object): EC = {ES256, ES384, ES512} ED = {EdDSA} - SUPPORTED = HMAC.union(RSA).union(EC).union(ED) + # This is modified in jose.backends.nacl_backend to register EdDSA + SUPPORTED = HMAC.union(RSA).union(EC) ALL = SUPPORTED.union([NONE]) diff --git a/jose/jwk.py b/jose/jwk.py index cde13d83..e4fb25ca 100644 --- a/jose/jwk.py +++ b/jose/jwk.py @@ -19,8 +19,10 @@ except ImportError: pass - -from jose.backends import Ed25519Key +try: + from jose.backends import Ed25519Key # noqa: F401 +except ImportError: + pass def get_key(algorithm): @@ -35,7 +37,7 @@ def get_key(algorithm): from jose.backends import ECKey # noqa: F811 return ECKey elif algorithm in ALGORITHMS.ED: - from jose.backends import Ed25519Key + from jose.backends import Ed25519Key # noqa: F811 return Ed25519Key return None diff --git a/setup.py b/setup.py index 414d9392..3744b9b9 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ def _cryptography_version(): 'cryptography': [_cryptography_version()], 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'] + pyasn1, 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'] + pyasn1, + 'ed25519': ['pynacl'], } legacy_backend_requires = ['ecdsa <1.0', 'rsa'] + pyasn1 install_requires = ['six <2.0', 'pynacl'] From 888323c6ad000941e2000a09495f84462c514e0d Mon Sep 17 00:00:00 2001 From: blag Date: Tue, 27 Nov 2018 02:54:21 -0700 Subject: [PATCH 4/5] One more test --- tests/algorithms/test_Ed25519.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/algorithms/test_Ed25519.py b/tests/algorithms/test_Ed25519.py index 5efcb7c4..8a8f9328 100644 --- a/tests/algorithms/test_Ed25519.py +++ b/tests/algorithms/test_Ed25519.py @@ -126,7 +126,7 @@ def test_verify_key_from_bad_dict(self): with pytest.raises(JWKError): Ed25519Key(bad_key_dict, algorithm=ALGORITHMS.EdDSA) - def test_signing_parity(self): + def test_signing_bytes_parity(self): signing_key = Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PRIVATE) verify_key = signing_key.public_key() @@ -144,3 +144,22 @@ def test_signing_parity(self): assert not verify_key.verify(bad_smsg_message, smsg.signature) assert not verify_key.verify(bad_smsg_message, bad_smsg_signature) assert not verify_key.verify(bad_smsg) + + def test_signing_string_parity(self): + signing_key = Ed25519Key(SIGNING_KEY, algorithm=ALGORITHMS.EdDSA, use=USAGES.PRIVATE) + verify_key = signing_key.public_key() + + msg = 'test' + smsg = signing_key.sign(msg) # -> signature + cleartext message + bad_smsg_message = bytes([(b + 1) if b < 255 else 0 for b in bytearray(smsg.message)]) + bad_smsg_signature = bytes([(b + 1) if b < 255 else 0 for b in bytearray(smsg.signature)]) + bad_smsg = bad_smsg_signature + bad_smsg_message + + assert verify_key.verify(smsg) + assert verify_key.verify(smsg.signature + smsg.message) + assert verify_key.verify(smsg.message, smsg.signature) + + assert not verify_key.verify(smsg.message, bad_smsg_signature) + assert not verify_key.verify(bad_smsg_message, smsg.signature) + assert not verify_key.verify(bad_smsg_message, bad_smsg_signature) + assert not verify_key.verify(bad_smsg) From af5ed9783de7be04375494a06167a42c37554af4 Mon Sep 17 00:00:00 2001 From: blag Date: Wed, 3 Apr 2019 02:24:19 -0700 Subject: [PATCH 5/5] Remove references to nacl.encoding, handle decoding ourselves --- jose/backends/nacl_backend.py | 17 ++++++++++------- tests/algorithms/test_Ed25519.py | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/jose/backends/nacl_backend.py b/jose/backends/nacl_backend.py index ca304fa2..572d8297 100644 --- a/jose/backends/nacl_backend.py +++ b/jose/backends/nacl_backend.py @@ -6,7 +6,6 @@ from jose.constants import ALGORITHMS, USAGES from jose.exceptions import JWKError -from nacl.encoding import RawEncoder, URLSafeBase64Encoder from nacl.exceptions import BadSignatureError from nacl.signing import SigningKey, VerifyKey @@ -36,9 +35,11 @@ def __init__(self, key, algorithm, use=None): "from a string or bytes") if use == USAGES.PUBLIC: - self._prepared_key = VerifyKey(key, encoder=URLSafeBase64Encoder) + decoded_key_bytes = base64.urlsafe_b64decode(key) + self._prepared_key = VerifyKey(decoded_key_bytes) elif use == USAGES.PRIVATE: - self._prepared_key = SigningKey(key, encoder=URLSafeBase64Encoder) + decoded_key_bytes = base64.urlsafe_b64decode(key) + self._prepared_key = SigningKey(decoded_key_bytes) else: raise JWKError("The 'use' parameter must either be 'public' or 'private', not %s" % use) return @@ -55,20 +56,22 @@ def _process_jwk(self, jwk_dict): if 'd' in jwk_dict: # d indicates private key d = jwk_dict.get('d').encode('utf-8') + b'==' - return SigningKey(d, encoder=URLSafeBase64Encoder) + decoded_d_bytes = base64.urlsafe_b64decode(d) + return SigningKey(decoded_d_bytes) else: # no d indicates public key x = jwk_dict.get('x').encode('utf-8') + b'==' - return VerifyKey(x, encoder=URLSafeBase64Encoder) + decoded_x_bytes = base64.urlsafe_b64decode(x) + return VerifyKey(decoded_x_bytes) def sign(self, msg): if isinstance(msg, six.string_types): msg = msg.encode('utf-8') - return self._prepared_key.sign(msg, encoder=RawEncoder) + return self._prepared_key.sign(msg) def verify(self, msg, sig=None): try: - self._prepared_key.verify(msg, sig, encoder=RawEncoder) + self._prepared_key.verify(msg, sig) return True except BadSignatureError: return False diff --git a/tests/algorithms/test_Ed25519.py b/tests/algorithms/test_Ed25519.py index 8a8f9328..2cdd0bf1 100644 --- a/tests/algorithms/test_Ed25519.py +++ b/tests/algorithms/test_Ed25519.py @@ -5,14 +5,14 @@ from jose.constants import ALGORITHMS, USAGES from jose.exceptions import JWKError -from nacl.encoding import URLSafeBase64Encoder from nacl.signing import SigningKey, VerifyKey import pytest SIGNING_KEY = "npAVhmIfq2byvIzcmgS5cguKCv2Nw8Seqa1Fku00LoE=" -nacl_signing_key = SigningKey(SIGNING_KEY.encode('utf-8'), encoder=URLSafeBase64Encoder) +signing_key_bytes = base64.urlsafe_b64decode(SIGNING_KEY.encode('utf-8')) +nacl_signing_key = SigningKey(signing_key_bytes) nacl_verify_key = nacl_signing_key.verify_key VERIFY_KEY = base64.urlsafe_b64encode(bytes(nacl_verify_key))