From 593b04d8dca78a84433e137c8c0823fd4ec666b7 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 11 Sep 2024 13:38:42 +0200 Subject: [PATCH] feat: add support for Ed25519 and Ed448 (EdDSA) --- lib/oneShotAlgs.js | 5 +++++ lib/validateAsymmetricKey.js | 2 ++ sign.js | 1 + test/ed25519-private.pem | 3 +++ test/ed25519-public-invalid.pem | 3 +++ test/ed25519-public.pem | 3 +++ test/jwt.asymmetric_signing.tests.js | 5 +++++ test/roundtrip.test.js | 2 ++ test/schema.tests.js | 2 ++ verify.js | 5 +++++ 10 files changed, 31 insertions(+) create mode 100644 test/ed25519-private.pem create mode 100644 test/ed25519-public-invalid.pem create mode 100644 test/ed25519-public.pem diff --git a/lib/oneShotAlgs.js b/lib/oneShotAlgs.js index 8e3550c..f976f5c 100644 --- a/lib/oneShotAlgs.js +++ b/lib/oneShotAlgs.js @@ -52,6 +52,11 @@ module.exports = function(alg, key) { digest: 'sha512', key: { key, dsaEncoding: 'ieee-p1363' }, }; + case 'EdDSA': + return { + digest: undefined, + key: { key }, + }; default: throw new Error('unreachable'); } diff --git a/lib/validateAsymmetricKey.js b/lib/validateAsymmetricKey.js index 77d5bf3..ab0c7a6 100644 --- a/lib/validateAsymmetricKey.js +++ b/lib/validateAsymmetricKey.js @@ -2,6 +2,8 @@ const { ASYMMETRIC_KEY_DETAILS_SUPPORTED, RSA_PSS_KEY_DETAILS_SUPPORTED } = requ const allowedAlgorithmsForKeys = { 'ec': ['ES256', 'ES256K', 'ES384', 'ES512'], + 'ed25519': ['EdDSA'], + 'ed448': ['EdDSA'], 'rsa': ['RS256', 'PS256', 'RS384', 'PS384', 'RS512', 'PS512'], 'rsa-pss': ['PS256', 'PS384', 'PS512'] }; diff --git a/sign.js b/sign.js index a4d6284..9dfa6f3 100644 --- a/sign.js +++ b/sign.js @@ -15,6 +15,7 @@ const SUPPORTED_ALGS = [ 'PS256', 'PS384', 'PS512', 'ES256', 'ES256K', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', + 'EdDSA', 'none', ]; diff --git a/test/ed25519-private.pem b/test/ed25519-private.pem new file mode 100644 index 0000000..770ffcc --- /dev/null +++ b/test/ed25519-private.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEINm0OEjPHWFVPXX+RWO48diNrzeWvhxLYT0UfBHb6ZBA +-----END PRIVATE KEY----- diff --git a/test/ed25519-public-invalid.pem b/test/ed25519-public-invalid.pem new file mode 100644 index 0000000..54ff487 --- /dev/null +++ b/test/ed25519-public-invalid.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAnbt7ZRTDvGWNmgiJQ+oOodLqvFS0fl1mlRHTaetHI0Q= +-----END PUBLIC KEY----- diff --git a/test/ed25519-public.pem b/test/ed25519-public.pem new file mode 100644 index 0000000..67669f1 --- /dev/null +++ b/test/ed25519-public.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAbelG8IgnkVHYUdI5CN54QDdYkvgJkeDc7V8EVBN6zVg= +-----END PUBLIC KEY----- diff --git a/test/jwt.asymmetric_signing.tests.js b/test/jwt.asymmetric_signing.tests.js index a0f8415..28eb282 100644 --- a/test/jwt.asymmetric_signing.tests.js +++ b/test/jwt.asymmetric_signing.tests.js @@ -26,6 +26,11 @@ const algorithms = { pub_key: loadKey('secp256k1-public.pem'), invalid_pub_key: loadKey('secp256k1-public-invalid.pem') }, + EdDSA: { + priv_key: loadKey('ed25519-private.pem'), + pub_key: loadKey('ed25519-public.pem'), + invalid_pub_key: loadKey('ed25519-public-invalid.pem') + }, PS256: { pub_key: loadKey('pub.pem'), priv_key: loadKey('priv.pem'), diff --git a/test/roundtrip.test.js b/test/roundtrip.test.js index 2b3c263..1748fff 100644 --- a/test/roundtrip.test.js +++ b/test/roundtrip.test.js @@ -14,6 +14,8 @@ for (const [alg, opts] of [ ["ES256K"], ["ES384"], ["ES512"], + ["EdDSA", { crv: "Ed25519" }], + ["EdDSA", { crv: "Ed448" }], ]) { const conditionalDescribe = parseInt(process.versions.node, 10) >= 18 ? describe : describe.skip; diff --git a/test/schema.tests.js b/test/schema.tests.js index 6def721..4e55856 100644 --- a/test/schema.tests.js +++ b/test/schema.tests.js @@ -10,6 +10,7 @@ describe('schema', function() { var cert_secp256k1_priv = fs.readFileSync(__dirname + '/secp256k1-private.pem'); var cert_secp384r1_priv = fs.readFileSync(__dirname + '/secp384r1-private.pem'); var cert_secp521r1_priv = fs.readFileSync(__dirname + '/secp521r1-private.pem'); + var cert_ed25519_priv = fs.readFileSync(__dirname + '/ed25519-private.pem'); function sign(options, secretOrPrivateKey) { jwt.sign({foo: 123}, secretOrPrivateKey, options); @@ -30,6 +31,7 @@ describe('schema', function() { sign({algorithm: 'ES256K'}, cert_secp256k1_priv); sign({algorithm: 'ES384'}, cert_secp384r1_priv); sign({algorithm: 'ES512'}, cert_secp521r1_priv); + sign({algorithm: 'EdDSA'}, cert_ed25519_priv); sign({algorithm: 'HS256'}, 'superSecret'); sign({algorithm: 'HS384'}, 'superSecret'); sign({algorithm: 'HS512'}, 'superSecret'); diff --git a/verify.js b/verify.js index de3f3e8..c153249 100644 --- a/verify.js +++ b/verify.js @@ -11,6 +11,7 @@ const EC_KEY_ALGS = ['ES256', 'ES256K', 'ES384', 'ES512']; const RSA_KEY_ALGS = ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']; const PUB_KEY_ALGS = [].concat(RSA_KEY_ALGS, EC_KEY_ALGS); const HS_ALGS = ['HS256', 'HS384', 'HS512']; +const EdDSA_ALGS = ['EdDSA']; function processPayload(header, payload, signature, options, done) { const clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1000); @@ -222,6 +223,10 @@ module.exports = function(jwtString, secretOrPublicKey, options, callback) { case 'ec': options.algorithms = EC_KEY_ALGS break; + case 'ed25519': + case 'ed448': + options.algorithms = EdDSA_ALGS; + break; default: options.algorithms = PUB_KEY_ALGS; }