diff --git a/jwcrypto/jwa.py b/jwcrypto/jwa.py index b6d20d7..21dd8d2 100644 --- a/jwcrypto/jwa.py +++ b/jwcrypto/jwa.py @@ -2,6 +2,7 @@ import os import struct +import warnings from abc import ABCMeta, abstractmethod from binascii import hexlify, unhexlify @@ -41,6 +42,15 @@ actors providing a very high iteration count. """ +default_warn_deprecated_algorithms = False +"""When True, emit a DeprecationWarning when instantiating an algorithm +whose status is 'deprecated'. + +Disabled by default so that consumers are not disrupted by warnings for +algorithms that remain in wide use (e.g. EdDSA). Set to True to audit +your code for deprecated algorithm usage. +""" + default_enforce_hmac_key_length = True """Enforces that the HMAC key length is at least the size of the hash function's output, as recommended by RFC 7518. @@ -77,6 +87,12 @@ def algorithm_usage_location(self): def algorithm_use(self): """One of 'sig', 'kex', 'enc'""" + status = 'active' + """Algorithm status: 'active', 'deprecated', or 'prohibited'""" + + deprecated_by = None + """Replacement algorithm(s) for deprecated algorithms""" + @property def input_keysize(self): """The input key size""" @@ -883,6 +899,8 @@ class _EdDsa(_RawJWS, JWAAlgorithm): algorithm_usage_location = 'alg' algorithm_use = 'sig' keysize = None + status = 'deprecated' + deprecated_by = 'Ed25519 or Ed448' def sign(self, key, payload): if key['crv'] in ['Ed25519', 'Ed448']: @@ -1297,6 +1315,14 @@ def instantiate_alg(cls, name, use=None): alg = cls.algorithms_registry[name] if use is not None and alg.algorithm_use != use: raise KeyError + if alg.status == 'prohibited': + raise InvalidJWAAlgorithm( + '%s is prohibited and must not be used' % name) + if alg.status == 'deprecated' and default_warn_deprecated_algorithms: + msg = '%s is deprecated' % name + if alg.deprecated_by: + msg += '; use %s instead' % alg.deprecated_by + warnings.warn(msg, DeprecationWarning, stacklevel=4) return alg() @classmethod diff --git a/jwcrypto/tests.py b/jwcrypto/tests.py index 58f986a..137c96f 100644 --- a/jwcrypto/tests.py +++ b/jwcrypto/tests.py @@ -2642,3 +2642,30 @@ def test_jws_ed448(self): jws_obj.deserialize(serialized_jws, key) self.assertTrue(jws_obj.is_valid) self.assertEqual(jws_obj.payload, payload) + + def test_eddsa_deprecation_warning(self): + import warnings + old = jwa.default_warn_deprecated_algorithms + try: + jwa.default_warn_deprecated_algorithms = True + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + jwa.JWA.signing_alg('EdDSA') + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn('EdDSA', str(w[0].message)) + self.assertIn('Ed25519 or Ed448', str(w[0].message)) + finally: + jwa.default_warn_deprecated_algorithms = old + + def test_eddsa_no_warning_by_default(self): + import warnings + old = jwa.default_warn_deprecated_algorithms + try: + jwa.default_warn_deprecated_algorithms = False + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + jwa.JWA.signing_alg('EdDSA') + self.assertEqual(len(w), 0) + finally: + jwa.default_warn_deprecated_algorithms = old