From 4441c3e3b5c3b0fdc97e5097db708421d90814a6 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 21 Aug 2021 12:42:56 +0200 Subject: [PATCH] crypto: fix JWK RSA-PSS SubtleCrypto.exportKey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/39828 Reviewed-By: Tobias Nießen Reviewed-By: James M Snell --- lib/internal/crypto/keys.js | 6 +-- lib/internal/crypto/webcrypto.js | 2 +- src/crypto/crypto_keys.cc | 20 ++++++--- .../test-webcrypto-export-import-rsa.js | 43 +++++++++++++++++++ 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 1127217d6b9dee..4d5545ab1aae1a 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -141,7 +141,7 @@ const { validateOneOf( options.format, 'options.format', [undefined, 'buffer', 'jwk']); if (options.format === 'jwk') { - return this[kHandle].exportJwk({}); + return this[kHandle].exportJwk({}, false); } } return this[kHandle].export(); @@ -196,7 +196,7 @@ const { export(options) { if (options && options.format === 'jwk') { - return this[kHandle].exportJwk({}); + return this[kHandle].exportJwk({}, false); } const { format, @@ -217,7 +217,7 @@ const { throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( 'jwk', 'does not support encryption'); } - return this[kHandle].exportJwk({}); + return this[kHandle].exportJwk({}, false); } const { format, diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 7cb32ec702767d..0edd5c2cd3f131 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -328,7 +328,7 @@ async function exportKeyJWK(key) { const jwk = key[kKeyObject][kHandle].exportJwk({ key_ops: key.usages, ext: key.extractable, - }); + }, true); switch (key.algorithm.name) { case 'RSASSA-PKCS1-v1_5': jwk.alg = normalizeHashName( diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 821a06b1bd1417..3243ece8670ce2 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -489,8 +489,13 @@ std::shared_ptr ImportJWKSecretKey( Maybe ExportJWKAsymmetricKey( Environment* env, std::shared_ptr key, - Local target) { + Local target, + bool handleRsaPss) { switch (EVP_PKEY_id(key->GetAsymmetricKey().get())) { + case EVP_PKEY_RSA_PSS: { + if (handleRsaPss) return ExportJWKRsaKey(env, key, target); + break; + } case EVP_PKEY_RSA: return ExportJWKRsaKey(env, key, target); case EVP_PKEY_EC: return ExportJWKEcKey(env, key, target); case EVP_PKEY_ED25519: @@ -609,14 +614,16 @@ static inline Maybe Tristate(bool b) { Maybe ExportJWKInner(Environment* env, std::shared_ptr key, - Local result) { + Local result, + bool handleRsaPss) { switch (key->GetKeyType()) { case kKeyTypeSecret: return ExportJWKSecretKey(env, key, result.As()); case kKeyTypePublic: // Fall through case kKeyTypePrivate: - return ExportJWKAsymmetricKey(env, key, result.As()); + return ExportJWKAsymmetricKey( + env, key, result.As(), handleRsaPss); default: UNREACHABLE(); } @@ -638,7 +645,7 @@ Maybe ManagedEVPPKey::ToEncodedPublicKey( std::shared_ptr data = KeyObjectData::CreateAsymmetric(kKeyTypePublic, std::move(key)); *out = Object::New(env->isolate()); - return ExportJWKInner(env, data, *out); + return ExportJWKInner(env, data, *out, false); } return Tristate(WritePublicKey(env, key.get(), config).ToLocal(out)); @@ -658,7 +665,7 @@ Maybe ManagedEVPPKey::ToEncodedPrivateKey( std::shared_ptr data = KeyObjectData::CreateAsymmetric(kKeyTypePrivate, std::move(key)); *out = Object::New(env->isolate()); - return ExportJWKInner(env, data, *out); + return ExportJWKInner(env, data, *out, false); } return Tristate(WritePrivateKey(env, key.get(), config).ToLocal(out)); @@ -1237,8 +1244,9 @@ void KeyObjectHandle::ExportJWK( ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder()); CHECK(args[0]->IsObject()); + CHECK(args[1]->IsBoolean()); - ExportJWKInner(env, key->Data(), args[0]); + ExportJWKInner(env, key->Data(), args[0], args[1]->IsTrue()); args.GetReturnValue().Set(args[0]); } diff --git a/test/parallel/test-webcrypto-export-import-rsa.js b/test/parallel/test-webcrypto-export-import-rsa.js index f43259fd22faea..04cf6388fc739d 100644 --- a/test/parallel/test-webcrypto-export-import-rsa.js +++ b/test/parallel/test-webcrypto-export-import-rsa.js @@ -1,6 +1,7 @@ 'use strict'; const common = require('../common'); +const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); @@ -478,3 +479,45 @@ const testVectors = [ }); await Promise.all(variations); })().then(common.mustCall()); + +{ + const publicPem = fixtures.readKey('rsa_pss_public_2048.pem', 'ascii'); + const privatePem = fixtures.readKey('rsa_pss_private_2048.pem', 'ascii'); + + const publicDer = Buffer.from( + publicPem.replace( + /(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, + '' + ), + 'base64' + ); + const privateDer = Buffer.from( + privatePem.replace( + /(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, + '' + ), + 'base64' + ); + + (async () => { + const key = await subtle.importKey( + 'spki', + publicDer, + { name: 'RSA-PSS', hash: 'SHA-256' }, + true, + ['verify']); + const jwk = await subtle.exportKey('jwk', key); + assert.strictEqual(jwk.alg, 'PS256'); + })().then(common.mustCall()); + + (async () => { + const key = await subtle.importKey( + 'pkcs8', + privateDer, + { name: 'RSA-PSS', hash: 'SHA-256' }, + true, + ['sign']); + const jwk = await subtle.exportKey('jwk', key); + assert.strictEqual(jwk.alg, 'PS256'); + })().then(common.mustCall()); +}