From 4a474552047eb13f4fdca4077824f290f7d74d8d Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 10:50:43 +0200 Subject: [PATCH 01/23] crypto: add CFRG curves to Web Crypto API --- lib/internal/crypto/cfrg.js | 371 +++++++++++++++++++++++++++ lib/internal/crypto/diffiehellman.js | 14 +- lib/internal/crypto/util.js | 4 + lib/internal/crypto/webcrypto.js | 79 ++++++ src/crypto/crypto_ec.cc | 10 +- 5 files changed, 474 insertions(+), 4 deletions(-) create mode 100644 lib/internal/crypto/cfrg.js diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js new file mode 100644 index 00000000000000..ee3a336c4f5e8a --- /dev/null +++ b/lib/internal/crypto/cfrg.js @@ -0,0 +1,371 @@ +'use strict'; + +const { + Promise, + SafeSet, +} = primordials; + +const { Buffer } = require('buffer'); + +const { + ECKeyExportJob, + KeyObjectHandle, + SignJob, + kCryptoJobAsync, + kKeyTypePrivate, + kKeyTypePublic, + kSignJobModeSign, + kSignJobModeVerify, +} = internalBinding('crypto'); + +const { + codes: { + ERR_INVALID_ARG_TYPE, + } +} = require('internal/errors'); + +const { + getArrayBufferOrView, + getUsagesUnion, + hasAnyNotIn, + jobPromise, + validateKeyOps, + kHandle, + kKeyObject, +} = require('internal/crypto/util'); + +const { + lazyDOMException, +} = require('internal/util'); + +const { + generateKeyPair, +} = require('internal/crypto/keygen'); + +const { + InternalCryptoKey, + PrivateKeyObject, + PublicKeyObject, + createPrivateKey, + createPublicKey, + isKeyObject, +} = require('internal/crypto/keys'); + +function verifyAcceptableCfrgKeyUse(name, type, usages) { + let checkSet; + switch (name) { + case 'X25519': + // Fall through + case 'X448': + checkSet = ['deriveKey', 'deriveBits']; + break; + case 'Ed25519': + // Fall through + case 'Ed448': + switch (type) { + case 'private': + checkSet = ['sign']; + break; + case 'public': + checkSet = ['verify']; + break; + } + } + if (hasAnyNotIn(usages, checkSet)) { + throw lazyDOMException( + `Unsupported key usage for a ${name} key`, + 'SyntaxError'); + } +} + +function createECPublicKeyRaw(name, keyData) { + const handle = new KeyObjectHandle(); + keyData = getArrayBufferOrView(keyData, 'keyData'); + if (handle.initECRaw(name.toLowerCase(), keyData)) + return new PublicKeyObject(handle); +} + +function createCFRGRawKey(name, keyData, isPublic) { + const handle = new KeyObjectHandle(); + keyData = getArrayBufferOrView(keyData, 'keyData'); + + switch (name) { + case 'Ed25519': + case 'X25519': + if (keyData.byteLength !== 32) { + throw lazyDOMException( + `${name} raw keys must be exactly 32-bytes`); + } + break; + case 'Ed448': + if (keyData.byteLength !== 57) { + throw lazyDOMException( + `${name} raw keys must be exactly 57-bytes`); + } + break; + case 'X448': + if (keyData.byteLength !== 56) { + throw lazyDOMException( + `${name} raw keys must be exactly 56-bytes`); + } + break; + } + + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initEDRaw(name, keyData, keyType)) { + throw lazyDOMException('Failure to generate key object'); + } + + return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); +} + +async function cfrgGenerateKey(algorithm, extractable, keyUsages) { + const { name } = algorithm; + + const usageSet = new SafeSet(keyUsages); + switch (name) { + case 'Ed25519': + // Fall through + case 'Ed448': + if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { + throw lazyDOMException( + `Unsupported key usage for an ${name} key`, + 'SyntaxError'); + } + break; + case 'X25519': + // Fall through + case 'X448': + if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) { + throw lazyDOMException( + `Unsupported key usage for an ${name} key`, + 'SyntaxError'); + } + break; + } + return new Promise((resolve, reject) => { + let genKeyType; + switch (name) { + case 'Ed25519': + genKeyType = 'ed25519'; + break; + case 'Ed448': + genKeyType = 'ed448'; + break; + case 'X25519': + genKeyType = 'x25519'; + break; + case 'X448': + genKeyType = 'x448'; + break; + } + generateKeyPair(genKeyType, undefined, (err, pubKey, privKey) => { + if (err) { + return reject(lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError')); + } + + const algorithm = { name }; + + let publicUsages; + let privateUsages; + switch (name) { + case 'Ed25519': + // Fall through + case 'Ed448': + publicUsages = getUsagesUnion(usageSet, 'verify'); + privateUsages = getUsagesUnion(usageSet, 'sign'); + break; + case 'X25519': + // Fall through + case 'X448': + publicUsages = []; + privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); + break; + } + + const publicKey = + new InternalCryptoKey( + pubKey, + algorithm, + publicUsages, + true); + + const privateKey = + new InternalCryptoKey( + privKey, + algorithm, + privateUsages, + extractable); + + resolve({ publicKey, privateKey }); + }); + }); +} + +function crfgExportKey(key, format) { + return jobPromise(new ECKeyExportJob( + kCryptoJobAsync, + format, + key[kKeyObject][kHandle])); +} + +async function cfrgImportKey( + format, + keyData, + algorithm, + extractable, + keyUsages) { + + const { name } = algorithm; + let keyObject; + const usagesSet = new SafeSet(keyUsages); + switch (format) { + case 'node.keyObject': { + if (!isKeyObject(keyData)) + throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData); + if (keyData.type === 'secret') + throw lazyDOMException('Invalid key type', 'InvalidAccessException'); + verifyAcceptableCfrgKeyUse(name, keyData.type, usagesSet); + keyObject = keyData; + break; + } + case 'spki': { + verifyAcceptableCfrgKeyUse(name, 'public', usagesSet); + keyObject = createPublicKey({ + key: keyData, + format: 'der', + type: 'spki' + }); + break; + } + case 'pkcs8': { + verifyAcceptableCfrgKeyUse(name, 'private', usagesSet); + keyObject = createPrivateKey({ + key: keyData, + format: 'der', + type: 'pkcs8' + }); + break; + } + case 'jwk': { + if (keyData == null || typeof keyData !== 'object') + throw lazyDOMException('Invalid JWK keyData', 'DataError'); + if (keyData.kty !== 'OKP') + throw lazyDOMException('Invalid key type', 'DataError'); + const isPublic = keyData.d === undefined; + + if (usagesSet.size > 0 && keyData.use !== undefined) { + let checkUse; + switch (name) { + case 'Ed25519': + // Fall through + case 'Ed448': + checkUse = 'sig'; + break; + case 'X25519': + // Fall through + case 'X448': + checkUse = 'enc'; + break; + } + if (keyData.use !== checkUse) + throw lazyDOMException('Invalid use type', 'DataError'); + } + + validateKeyOps(keyData.key_ops, usagesSet); + + if (keyData.ext !== undefined && + keyData.ext === false && + extractable === true) { + throw lazyDOMException('JWK is not extractable', 'DataError'); + } + + if (keyData.alg !== undefined) { + if (typeof keyData.alg !== 'string') + throw lazyDOMException('Invalid alg', 'DataError'); + if ( + (name === 'Ed25519' || name === 'Ed448') && + keyData.alg !== 'EdDSA' + ) { + throw lazyDOMException('Invalid alg', 'DataError'); + } + } + + let type; + switch (name) { + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + type = 'ECDH'; + break; + } + + if (name !== type) + throw lazyDOMException('Invalid algorithm name.', 'DataError'); + + verifyAcceptableCfrgKeyUse( + type, + isPublic ? 'public' : 'private', + usagesSet); + keyObject = createCFRGRawKey( + name, + Buffer.from( + isPublic ? keyData.x : keyData.d, + 'base64'), + isPublic); + break; + } + case 'raw': { + verifyAcceptableCfrgKeyUse(name, 'public', usagesSet); + keyObject = createECPublicKeyRaw(name, keyData); + if (keyObject === undefined) + throw lazyDOMException('Unable to import CFRG key', 'OperationError'); + break; + } + } + + if (keyObject.asymmetricKeyType !== name.toLowerCase()) { + throw lazyDOMException('Invalid key type', 'DataError'); + } + + return new InternalCryptoKey( + keyObject, + { name }, + keyUsages, + extractable); +} + +function eddsaSignVerify(key, data, signature) { + const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; + const type = mode === kSignJobModeSign ? 'private' : 'public'; + + if (key.type !== type) + throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); + + return jobPromise(new SignJob( + kCryptoJobAsync, + mode, + key[kKeyObject][kHandle], + undefined, + undefined, + undefined, + data, + undefined, + undefined, + undefined, + undefined, + signature)); +} + +module.exports = { + crfgExportKey, + cfrgImportKey, + cfrgGenerateKey, + eddsaSignVerify, +}; diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js index 598db4052b976c..bbf49b70b606c7 100644 --- a/lib/internal/crypto/diffiehellman.js +++ b/lib/internal/crypto/diffiehellman.js @@ -445,8 +445,12 @@ async function asyncDeriveBitsECDH(algorithm, baseKey, length) { 'baseKey must be a private key', 'InvalidAccessError'); } - if (key.algorithm.name !== 'ECDH') { - throw lazyDOMException('Keys must be ECDH keys', 'InvalidAccessError'); + if ( + key.algorithm.name !== 'ECDH' && + key.algorithm.name !== 'X25519' && + key.algorithm.name !== 'X448' + ) { + throw lazyDOMException('Keys must be ECDH, X25519, or X448 keys', 'InvalidAccessError'); } if (key.algorithm.name !== baseKey.algorithm.name) { @@ -455,8 +459,12 @@ async function asyncDeriveBitsECDH(algorithm, baseKey, length) { 'InvalidAccessError'); } - if (key.algorithm.namedCurve !== baseKey.algorithm.namedCurve) + if ( + key.algorithm.name === 'ECDH' && + key.algorithm.namedCurve !== baseKey.algorithm.namedCurve + ) { throw lazyDOMException('Named curve mismatch', 'InvalidAccessError'); + } const bits = await new Promise((resolve, reject) => { deriveBitsECDH( diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 4c1590ed9da9d5..be64a071f6cc05 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -177,6 +177,10 @@ const kAlgorithms = { 'node-scrypt': 'NODE-SCRYPT', 'node-ed25519': 'NODE-ED25519', 'node-ed448': 'NODE-ED448', + 'ed25519': 'Ed25519', + 'ed448': 'Ed448', + 'x25519': 'X25519', + 'x448': 'X448', }; const kAlgorithmsKeys = ObjectKeys(kAlgorithms); diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 3b7e4d13d8b1c0..d2bd3a6c0769bb 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -98,6 +98,15 @@ async function generateKey( case 'RSA-OAEP': return lazyRequire('internal/crypto/rsa') .rsaKeyGenerate(algorithm, extractable, keyUsages); + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + return lazyRequire('internal/crypto/cfrg') + .cfrgGenerateKey(algorithm, extractable, keyUsages); case 'NODE-ED25519': // Fall through case 'NODE-ED448': @@ -146,6 +155,10 @@ async function deriveBits(algorithm, baseKey, length) { if (baseKey.algorithm.name !== algorithm.name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); switch (algorithm.name) { + case 'X25519': + // Fall through + case 'X448': + // Fall through case 'ECDH': return lazyRequire('internal/crypto/diffiehellman') .asyncDeriveBitsECDH(algorithm, baseKey, length); @@ -227,6 +240,10 @@ async function deriveKey( const length = getKeyLength(derivedKeyAlgorithm); let bits; switch (algorithm.name) { + case 'X25519': + // Fall through + case 'X448': + // Fall through case 'ECDH': bits = await lazyRequire('internal/crypto/diffiehellman') .asyncDeriveBitsECDH(algorithm, baseKey, length); @@ -282,6 +299,18 @@ async function exportKeySpki(key) { .ecExportKey(key, kWebCryptoKeyFormatSPKI); } break; + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + if (key.type === 'public') { + return lazyRequire('internal/crypto/cfrg') + .crfgExportKey(key, kWebCryptoKeyFormatSPKI); + } + break; case 'NODE-DSA': if (key.type === 'public') { return lazyRequire('internal/crypto/dsa') @@ -325,6 +354,18 @@ async function exportKeyPkcs8(key) { .ecExportKey(key, kWebCryptoKeyFormatPKCS8); } break; + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + if (key.type === 'private') { + return lazyRequire('internal/crypto/cfrg') + .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); + } + break; case 'NODE-DSA': if (key.type === 'private') { return lazyRequire('internal/crypto/dsa') @@ -362,6 +403,18 @@ async function exportKeyRaw(key) { .ecExportKey(key, kWebCryptoKeyFormatRaw); } break; + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + if (key.type === 'public') { + return lazyRequire('internal/crypto/cfrg') + .cfrgExportKey(key, kWebCryptoKeyFormatRaw); + } + break; case 'AES-CTR': // Fall through case 'AES-CBC': @@ -405,6 +458,17 @@ async function exportKeyJWK(key) { case 'ECDH': jwk.crv ||= key.algorithm.namedCurve; return jwk; + case 'X25519': + // Fall through + case 'X448': + jwk.crv ||= key.algorithm.name; + return jwk; + case 'Ed25519': + // Fall through + case 'Ed448': + jwk.crv ||= key.algorithm.name; + jwk.alg = 'EdDSA'; + return jwk; case 'AES-CTR': // Fall through case 'AES-CBC': @@ -539,6 +603,15 @@ async function importKey( case 'ECDH': return lazyRequire('internal/crypto/ec') .ecImportKey(format, keyData, algorithm, extractable, keyUsages); + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + return lazyRequire('internal/crypto/cfrg') + .cfrgImportKey(format, keyData, algorithm, extractable, keyUsages); case 'HMAC': return lazyRequire('internal/crypto/mac') .hmacImportKey(format, keyData, algorithm, extractable, keyUsages); @@ -666,6 +739,12 @@ function signVerify(algorithm, key, data, signature) { case 'ECDSA': return lazyRequire('internal/crypto/ec') .ecdsaSignVerify(key, data, algorithm, signature); + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + return lazyRequire('internal/crypto/cfrg') + .eddsaSignVerify(key, data, signature); case 'HMAC': return lazyRequire('internal/crypto/mac') .hmacSignVerify(key, data, algorithm, signature); diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc index 706affa0c21c03..6825cf0bd1a279 100644 --- a/src/crypto/crypto_ec.cc +++ b/src/crypto/crypto_ec.cc @@ -45,7 +45,15 @@ int GetCurveFromName(const char* name) { int GetOKPCurveFromName(const char* name) { int nid; - if (strcmp(name, "NODE-ED25519") == 0) { + if (strcmp(name, "Ed25519") == 0) { + nid = EVP_PKEY_ED25519; + } else if (strcmp(name, "Ed448") == 0) { + nid = EVP_PKEY_ED448; + } else if (strcmp(name, "X25519") == 0) { + nid = EVP_PKEY_X25519; + } else if (strcmp(name, "X448") == 0) { + nid = EVP_PKEY_X448; + } else if (strcmp(name, "NODE-ED25519") == 0) { nid = EVP_PKEY_ED25519; } else if (strcmp(name, "NODE-ED448") == 0) { nid = EVP_PKEY_ED448; From 712655bba4971b1574194eeb55645c6293790e29 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 12:07:09 +0200 Subject: [PATCH 02/23] add eddsa tests --- test/fixtures/crypto/eddsa.js | 54 ++++ .../test-webcrypto-sign-verify-ecdsa.js | 24 ++ .../test-webcrypto-sign-verify-eddsa.js | 232 ++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 test/fixtures/crypto/eddsa.js create mode 100644 test/parallel/test-webcrypto-sign-verify-eddsa.js diff --git a/test/fixtures/crypto/eddsa.js b/test/fixtures/crypto/eddsa.js new file mode 100644 index 00000000000000..4aeba95dc730c7 --- /dev/null +++ b/test/fixtures/crypto/eddsa.js @@ -0,0 +1,54 @@ +'use strict'; + +module.exports = function() { + const pkcs8 = { + 'Ed25519': Buffer.from( + '302e020100300506032b657004220420f3c8f4c48df878146e8cd3bf6df4e50e389b' + + 'a7074e15c2352dcd5d308d4ca81f', 'hex'), + 'Ed448': Buffer.from( + '3047020100300506032b6571043b04390eff03458c28e0179c521de312c969b78343' + + '48ecab991a60e3b2e9a79e4cd9e480ef291712d2c83d047272d5c9f428664f696d26' + + '70458f1d2e', 'hex') + } + + const spki = { + 'Ed25519': Buffer.from( + '302a300506032b6570032100d8e18963d809d487d9549accaec6742e7eeba24d8a0d' + + '3b14b7e3caea06893dcc', 'hex'), + 'Ed448': Buffer.from( + '3043300506032b6571033a00ab4bb885fd7d2c5af24e83710cffa0c74a57e274801d' + + 'b2057b0bdc5ea032b6fe6bc78b8045365aeb26e86e1f14fd349d07c48495f5a46a5a' + + '80', 'hex') + } + + const data = Buffer.from( + '2b7ed0bc7795694ab4acd35903fe8cd7d80f6a1c8688a6c3414409457514a1457855bb' + + 'b219e30a1beea8fe869082d99fc8282f9050d024e59eaf0730ba9db70a', 'hex'); + + // For verification tests. + const signatures = { + 'Ed25519': Buffer.from( + '3d90de5e5743dfc28225bfadb341b116cbf8a3f1ceedbf4adc350ef5d3471843a418' + + '614dcb6e614862614cf7af1496f9340b3c844ea4dceab1d3d155eb7ecc00', 'hex'), + 'Ed448': Buffer.from( + '76897e8c50ac6b1132735c09c55f506c0149d2677c75664f8bc10b826fbd9df0a03c' + + 'd986bce8339e64c7d1720ea9361784dc73837765ac2980c0dac0814a8bc187d1c9c9' + + '07c5dcc07956f85b70930fe42de764177217cb2d52bab7c1debe0ca89ccecbcd63f7' + + '025a2a5a572b9d23b0642f00', 'hex') + } + + const algorithms = ['Ed25519', 'Ed448']; + + const vectors = []; + algorithms.forEach((algorithm) => { + vectors.push({ + publicKeyBuffer: spki[algorithm], + privateKeyBuffer: pkcs8[algorithm], + name: algorithm, + data, + signature: signatures[algorithm] + }); + }); + + return vectors; +} diff --git a/test/parallel/test-webcrypto-sign-verify-ecdsa.js b/test/parallel/test-webcrypto-sign-verify-ecdsa.js index 2f8f3a2fd229bd..4e79e4d4f846d6 100644 --- a/test/parallel/test-webcrypto-sign-verify-ecdsa.js +++ b/test/parallel/test-webcrypto-sign-verify-ecdsa.js @@ -23,6 +23,7 @@ async function testVerify({ name, privateKey, hmacKey, rsaKeys, + okpKeys, ] = await Promise.all([ subtle.importKey( 'spki', @@ -55,6 +56,12 @@ async function testVerify({ name, }, false, ['sign']), + subtle.generateKey( + { + name: 'Ed25519', + }, + false, + ['sign']), ]); assert(await subtle.verify({ name, hash }, publicKey, signature, plaintext)); @@ -89,6 +96,11 @@ async function testVerify({ name, message: /Unable to use this key to verify/ }); + await assert.rejects( + subtle.verify({ name, hash }, okpKeys.publicKey, signature, plaintext), { + message: /Unable to use this key to verify/ + }); + // Test failure when signature is altered { const copy = Buffer.from(signature); @@ -140,6 +152,7 @@ async function testSign({ name, privateKey, hmacKey, rsaKeys, + okpKeys, ] = await Promise.all([ subtle.importKey( 'spki', @@ -172,6 +185,12 @@ async function testSign({ name, }, false, ['sign']), + subtle.generateKey( + { + name: 'Ed25519', + }, + false, + ['sign']), ]); { @@ -210,6 +229,11 @@ async function testSign({ name, subtle.sign({ name, hash }, rsaKeys.privateKey, plaintext), { message: /Unable to use this key to sign/ }); + + await assert.rejects( + subtle.sign({ name, hash }, okpKeys.privateKey, plaintext), { + message: /Unable to use this key to sign/ + }); } (async function() { diff --git a/test/parallel/test-webcrypto-sign-verify-eddsa.js b/test/parallel/test-webcrypto-sign-verify-eddsa.js new file mode 100644 index 00000000000000..4d23d11eaa28b1 --- /dev/null +++ b/test/parallel/test-webcrypto-sign-verify-eddsa.js @@ -0,0 +1,232 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = require('crypto').webcrypto; + +const vectors = require('../fixtures/crypto/eddsa')(); + +async function testVerify({ name, + publicKeyBuffer, + privateKeyBuffer, + signature, + data }) { + const [ + publicKey, + noVerifyPublicKey, + privateKey, + hmacKey, + rsaKeys, + ecKeys, + ] = await Promise.all([ + subtle.importKey( + 'spki', + publicKeyBuffer, + { name }, + false, + ['verify']), + subtle.importKey( + 'spki', + publicKeyBuffer, + { name }, + false, + [ /* No usages */ ]), + subtle.importKey( + 'pkcs8', + privateKeyBuffer, + { name }, + false, + ['sign']), + subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign']), + subtle.generateKey( + { + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + false, + ['sign']), + subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256' + }, + false, + ['sign']), + ]); + + assert(await subtle.verify({ name }, publicKey, signature, data)); + + // Test verification with altered buffers + const copy = Buffer.from(data); + const sigcopy = Buffer.from(signature); + const p = subtle.verify({ name }, publicKey, sigcopy, copy); + copy[0] = 255 - copy[0]; + sigcopy[0] = 255 - sigcopy[0]; + assert(await p); + + // Test failure when using wrong key + await assert.rejects( + subtle.verify({ name }, privateKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + await assert.rejects( + subtle.verify({ name }, noVerifyPublicKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + // Test failure when using the wrong algorithms + await assert.rejects( + subtle.verify({ name }, hmacKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + await assert.rejects( + subtle.verify({ name }, rsaKeys.publicKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + await assert.rejects( + subtle.verify({ name }, ecKeys.publicKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + // Test failure when signature is altered + { + const copy = Buffer.from(signature); + copy[0] = 255 - copy[0]; + assert(!(await subtle.verify( + { name }, + publicKey, + copy, + data))); + assert(!(await subtle.verify( + { name }, + publicKey, + copy.slice(1), + data))); + } + + // Test failure when data is altered + { + const copy = Buffer.from(data); + copy[0] = 255 - copy[0]; + assert(!(await subtle.verify({ name }, publicKey, signature, copy))); + } +} + +async function testSign({ name, + publicKeyBuffer, + privateKeyBuffer, + signature, + data }) { + const [ + publicKey, + noSignPrivateKey, + privateKey, + hmacKey, + rsaKeys, + ecKeys, + ] = await Promise.all([ + subtle.importKey( + 'spki', + publicKeyBuffer, + { name }, + false, + ['verify']), + subtle.importKey( + 'pkcs8', + privateKeyBuffer, + { name }, + false, + [ /* No usages */ ]), + subtle.importKey( + 'pkcs8', + privateKeyBuffer, + { name }, + false, + ['sign']), + subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign']), + subtle.generateKey( + { + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + false, + ['sign']), + subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256' + }, + false, + ['sign']), + ]); + + { + const sig = await subtle.sign({ name }, privateKey, data); + assert.strictEqual(sig.byteLength, signature.byteLength); + assert(await subtle.verify({ name }, publicKey, sig, data)); + } + + { + const copy = Buffer.from(data); + const p = subtle.sign({ name }, privateKey, copy); + copy[0] = 255 - copy[0]; + const sig = await p; + assert(await subtle.verify({ name }, publicKey, sig, data)); + } + + // Test failure when using wrong key + await assert.rejects( + subtle.sign({ name }, publicKey, data), { + message: /Unable to use this key to sign/ + }); + + // Test failure when no sign usage + await assert.rejects( + subtle.sign({ name }, noSignPrivateKey, data), { + message: /Unable to use this key to sign/ + }); + + // Test failure when using the wrong algorithms + await assert.rejects( + subtle.sign({ name }, hmacKey, data), { + message: /Unable to use this key to sign/ + }); + + await assert.rejects( + subtle.sign({ name }, rsaKeys.privateKey, data), { + message: /Unable to use this key to sign/ + }); + + await assert.rejects( + subtle.sign({ name }, ecKeys.privateKey, data), { + message: /Unable to use this key to sign/ + }); +} + +(async function() { + const variations = []; + + vectors.forEach((vector) => { + variations.push(testVerify(vector)); + variations.push(testSign(vector)); + }); + + await Promise.all(variations); +})().then(common.mustCall()); From 4a831d87849537a97d5aa5b2bb6fa4cc1240999d Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 12:19:29 +0200 Subject: [PATCH 03/23] fix ecdh w/ X* --- lib/internal/crypto/diffiehellman.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js index bbf49b70b606c7..56fa91c66f6886 100644 --- a/lib/internal/crypto/diffiehellman.js +++ b/lib/internal/crypto/diffiehellman.js @@ -468,7 +468,7 @@ async function asyncDeriveBitsECDH(algorithm, baseKey, length) { const bits = await new Promise((resolve, reject) => { deriveBitsECDH( - baseKey.algorithm.namedCurve, + key.algorithm.name === 'ECDH' ? baseKey.algorithm.namedCurve : baseKey.algorithm.name, key[kKeyObject][kHandle], baseKey[kKeyObject][kHandle], (err, bits) => { if (err) return reject(err); From 63bf7544dfabab6aa22ccf5d190867f3ee9ada28 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 12:20:07 +0200 Subject: [PATCH 04/23] add general ecdh and sign/verify tests --- test/parallel/test-webcrypto-derivebits.js | 26 ++++++++++++++ test/parallel/test-webcrypto-sign-verify.js | 40 +++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/test/parallel/test-webcrypto-derivebits.js b/test/parallel/test-webcrypto-derivebits.js index 95c38f454fbb93..d4c0f8cbe239b0 100644 --- a/test/parallel/test-webcrypto-derivebits.js +++ b/test/parallel/test-webcrypto-derivebits.js @@ -132,3 +132,29 @@ if (typeof internalBinding('crypto').ScryptJob === 'function') { tests.then(common.mustCall()); } + +// Test X25519 and X448 bit derivation +{ + async function test(name) { + const [alice, bob] = await Promise.all([ + subtle.generateKey({ name }, true, ['deriveBits']), + subtle.generateKey({ name }, true, ['deriveBits']), + ]); + + const [secret1, secret2] = await Promise.all([ + subtle.deriveBits({ + name, public: alice.publicKey + }, bob.privateKey, 128), + subtle.deriveBits({ + name, public: bob.publicKey + }, alice.privateKey, 128), + ]); + + assert(secret1 instanceof ArrayBuffer); + assert(secret2 instanceof ArrayBuffer); + assert.deepStrictEqual(secret1, secret2); + } + + test('X25519').then(common.mustCall()); + test('X448').then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-sign-verify.js b/test/parallel/test-webcrypto-sign-verify.js index 71e357fedb7fc0..6c6b15781549a4 100644 --- a/test/parallel/test-webcrypto-sign-verify.js +++ b/test/parallel/test-webcrypto-sign-verify.js @@ -104,3 +104,43 @@ const { subtle } = require('crypto').webcrypto; test('hello world').then(common.mustCall()); } + +// Test Sign/Verify Ed25519 +{ + async function test(data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'Ed25519', + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name: 'Ed25519', + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name: 'Ed25519', + }, publicKey, signature, ec.encode(data))); + } + + test('hello world').then(common.mustCall()); +} + +// Test Sign/Verify Ed448 +{ + async function test(data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'Ed448', + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name: 'Ed448', + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name: 'Ed448', + }, publicKey, signature, ec.encode(data))); + } + + test('hello world').then(common.mustCall()); +} From 3a0588597d0875fae74d00b3f5f82eed3702ced3 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 12:23:24 +0200 Subject: [PATCH 05/23] add general derive key tests --- test/parallel/test-webcrypto-derivekey.js | 43 ++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js index 0c11d38af30dc6..67513252545368 100644 --- a/test/parallel/test-webcrypto-derivekey.js +++ b/test/parallel/test-webcrypto-derivekey.js @@ -14,7 +14,7 @@ const { internalBinding } = require('internal/test/binding'); // This is only a partial test. The WebCrypto Web Platform Tests // will provide much greater coverage. -// Test ECDH bit derivation +// Test ECDH key derivation { async function test(namedCurve) { const [alice, bob] = await Promise.all([ @@ -48,7 +48,7 @@ const { internalBinding } = require('internal/test/binding'); test('P-521').then(common.mustCall()); } -// Test HKDF bit derivation +// Test HKDF key derivation { async function test(pass, info, salt, hash, expected) { const ec = new TextEncoder(); @@ -85,7 +85,7 @@ const { internalBinding } = require('internal/test/binding'); tests.then(common.mustCall()); } -// Test PBKDF2 bit derivation +// Test PBKDF2 key derivation { async function test(pass, salt, iterations, hash, expected) { const ec = new TextEncoder(); @@ -121,7 +121,7 @@ const { internalBinding } = require('internal/test/binding'); tests.then(common.mustCall()); } -// Test Scrypt bit derivation +// Test Scrypt key derivation if (typeof internalBinding('crypto').ScryptJob === 'function') { async function test(pass, salt, expected) { const ec = new TextEncoder(); @@ -184,3 +184,38 @@ if (typeof internalBinding('crypto').ScryptJob === 'function') { } })().then(common.mustCall()); } + +// Test X25519 and X448 key derivation +{ + async function test(name) { + const [alice, bob] = await Promise.all([ + subtle.generateKey({ name }, true, ['deriveKey']), + subtle.generateKey({ name }, true, ['deriveKey']), + ]); + + const [secret1, secret2] = await Promise.all([ + subtle.deriveKey({ + name, public: alice.publicKey + }, bob.privateKey, { + name: 'AES-CBC', + length: 256 + }, true, ['encrypt']), + subtle.deriveKey({ + name, public: bob.publicKey + }, alice.privateKey, { + name: 'AES-CBC', + length: 256 + }, true, ['encrypt']), + ]); + + const [raw1, raw2] = await Promise.all([ + subtle.exportKey('raw', secret1), + subtle.exportKey('raw', secret2), + ]); + + assert.deepStrictEqual(raw1, raw2); + } + + test('X25519').then(common.mustCall()); + test('X448').then(common.mustCall()); +} From 75ea892f7503da42263cd7d78a12802e6bc2f749 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 13:02:35 +0200 Subject: [PATCH 06/23] add cfrg ecdh tests --- .../test-webcrypto-derivebits-cfrg.js | 213 ++++++++++++++++++ .../parallel/test-webcrypto-derivekey-cfrg.js | 188 ++++++++++++++++ 2 files changed, 401 insertions(+) create mode 100644 test/parallel/test-webcrypto-derivebits-cfrg.js create mode 100644 test/parallel/test-webcrypto-derivekey-cfrg.js diff --git a/test/parallel/test-webcrypto-derivebits-cfrg.js b/test/parallel/test-webcrypto-derivebits-cfrg.js new file mode 100644 index 00000000000000..ff3e4ef7de5470 --- /dev/null +++ b/test/parallel/test-webcrypto-derivebits-cfrg.js @@ -0,0 +1,213 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { webcrypto } = require('crypto'); +const { subtle } = webcrypto; + +const kTests = [ + { + name: 'X25519', + size: 32, + pkcs8: '302e020100300506032b656e04220420c8838e76d057dfb7d8c95a69e138160ad' + + 'd6373fd71a4d276bb56e3a81b64ff61', + spki: '302a300506032b656e0321001cf2b1e6022ec537371ed7f53e54fa1154d83e98eb' + + '64ea51fae5b3307cfe9706', + result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008' + }, + { + name: 'X448', + size: 56, + pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + + 'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' + + 'd0cc53bf26929e', + spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + + 'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' + + 'ce6f', + result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + + '726e0f6dcf810eb594dca97b4882bd44c43ea7dc67f49a4e', + }, +]; + +async function prepareKeys() { + const keys = {}; + await Promise.all( + kTests.map(async ({ name, size, pkcs8, spki, result }) => { + const [ + privateKey, + publicKey, + ] = await Promise.all([ + subtle.importKey( + 'pkcs8', + Buffer.from(pkcs8, 'hex'), + { name }, + true, + ['deriveKey', 'deriveBits']), + subtle.importKey( + 'spki', + Buffer.from(spki, 'hex'), + { name }, + true, + ['deriveKey', 'deriveBits']), + ]); + keys[name] = { + privateKey, + publicKey, + size, + result, + }; + })); + return keys; +} + +(async function() { + const keys = await prepareKeys(); + + await Promise.all( + Object.keys(keys).map(async (name) => { + const { size, result, privateKey, publicKey } = keys[name]; + + { + // Good parameters + const bits = await subtle.deriveBits({ + name, + public: publicKey + }, privateKey, 8 * size); + + assert(bits instanceof ArrayBuffer); + assert.strictEqual(Buffer.from(bits).toString('hex'), result); + } + + { + // Case insensitivity + const bits = await subtle.deriveBits({ + name: name.toLowerCase(), + public: publicKey + }, privateKey, 8 * size); + + assert.strictEqual(Buffer.from(bits).toString('hex'), result); + } + + { + // Null length + const bits = await subtle.deriveBits({ + name, + public: publicKey + }, privateKey, null); + + assert.strictEqual(Buffer.from(bits).toString('hex'), result); + } + + { + // Short Result + const bits = await subtle.deriveBits({ + name, + public: publicKey + }, privateKey, 8 * size - 32); + + assert.strictEqual( + Buffer.from(bits).toString('hex'), + result.slice(0, -8)); + } + + { + // Too long result + await assert.rejects(subtle.deriveBits({ + name, + public: publicKey + }, privateKey, 8 * size + 8), { + message: /derived bit length is too small/ + }); + } + + { + // Non-multiple of 8 + const bits = await subtle.deriveBits({ + name, + public: publicKey + }, privateKey, 8 * size - 11); + + assert.strictEqual( + Buffer.from(bits).toString('hex'), + result.slice(0, -4)); + } + })); + + // Error tests + { + // Missing public property + await assert.rejects( + subtle.deriveBits( + { name: 'X448' }, + keys.X448.privateKey, + 8 * keys.X448.size), + { code: 'ERR_INVALID_ARG_TYPE' }); + } + + { + // The public property is not a CryptoKey + await assert.rejects( + subtle.deriveBits( + { + name: 'X448', + public: { message: 'Not a CryptoKey' } + }, + keys.X448.privateKey, + 8 * keys.X448.size), + { code: 'ERR_INVALID_ARG_TYPE' }); + } + + { + // Mismatched types + await assert.rejects( + subtle.deriveBits( + { + name: 'X448', + public: keys.X25519.publicKey + }, + keys.X448.privateKey, + 8 * keys.X448.size), + { message: 'The public and private keys must be of the same type' }); + } + + { + // Base key is not a private key + await assert.rejects(subtle.deriveBits({ + name: 'X448', + public: keys.X448.publicKey + }, keys.X448.publicKey, null), { + message: /baseKey must be a private key/ + }); + } + + { + // Base key is not a private key + await assert.rejects(subtle.deriveBits({ + name: 'X448', + public: keys.X448.privateKey + }, keys.X448.publicKey, null), { + message: /algorithm\.public must be a public key/ + }); + } + + { + // Public is a secret key + const keyData = webcrypto.getRandomValues(new Uint8Array(32)); + const key = await subtle.importKey( + 'raw', + keyData, + { name: 'AES-CBC', length: 256 }, + false, ['encrypt']); + + await assert.rejects(subtle.deriveBits({ + name: 'X448', + public: key + }, keys.X448.publicKey, null), { + message: /algorithm\.public must be a public key/ + }); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-derivekey-cfrg.js b/test/parallel/test-webcrypto-derivekey-cfrg.js new file mode 100644 index 00000000000000..ddf51626a89b4b --- /dev/null +++ b/test/parallel/test-webcrypto-derivekey-cfrg.js @@ -0,0 +1,188 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { webcrypto } = require('crypto'); +const { subtle } = webcrypto; + +const kTests = [ + { + name: 'X25519', + size: 32, + pkcs8: '302e020100300506032b656e04220420c8838e76d057dfb7d8c95a69e138160ad' + + 'd6373fd71a4d276bb56e3a81b64ff61', + spki: '302a300506032b656e0321001cf2b1e6022ec537371ed7f53e54fa1154d83e98eb' + + '64ea51fae5b3307cfe9706', + result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008' + }, + { + name: 'X448', + size: 56, + pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + + 'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' + + 'd0cc53bf26929e', + spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + + 'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' + + 'ce6f', + result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + }, +]; + +async function prepareKeys() { + const keys = {}; + await Promise.all( + kTests.map(async ({ name, size, pkcs8, spki, result }) => { + const [ + privateKey, + publicKey, + ] = await Promise.all([ + subtle.importKey( + 'pkcs8', + Buffer.from(pkcs8, 'hex'), + { name }, + true, + ['deriveKey', 'deriveBits']), + subtle.importKey( + 'spki', + Buffer.from(spki, 'hex'), + { name }, + true, + ['deriveKey', 'deriveBits']), + ]); + keys[name] = { + privateKey, + publicKey, + size, + result, + }; + })); + return keys; +} + +(async function() { + const keys = await prepareKeys(); + const otherArgs = [ + { name: 'HMAC', hash: 'SHA-256', length: 256 }, + true, + ['sign', 'verify']]; + + await Promise.all( + Object.keys(keys).map(async (name) => { + const { result, privateKey, publicKey } = keys[name]; + + { + // Good parameters + const key = await subtle.deriveKey({ + name, + public: publicKey + }, privateKey, ...otherArgs); + + const raw = await subtle.exportKey('raw', key); + + assert.strictEqual(Buffer.from(raw).toString('hex'), result); + } + + { + // Case insensitivity + const key = await subtle.deriveKey({ + name: name.toLowerCase(), + public: publicKey + }, privateKey, { + name: 'HmAc', + hash: 'SHA-256', + length: 256 + }, true, ['sign', 'verify']); + + const raw = await subtle.exportKey('raw', key); + + assert.strictEqual(Buffer.from(raw).toString('hex'), result); + } + })); + + // Error tests + { + // Missing public property + await assert.rejects( + subtle.deriveKey( + { name: 'X448' }, + keys.X448.privateKey, + ...otherArgs), + { code: 'ERR_INVALID_ARG_TYPE' }); + } + + { + // The public property is not a CryptoKey + await assert.rejects( + subtle.deriveKey( + { + name: 'X448', + public: { message: 'Not a CryptoKey' } + }, + keys.X448.privateKey, + ...otherArgs), + { code: 'ERR_INVALID_ARG_TYPE' }); + } + + { + // Mismatched named curves + await assert.rejects( + subtle.deriveKey( + { + name: 'X448', + public: keys.X25519.publicKey + }, + keys.X448.privateKey, + ...otherArgs), + { message: 'The public and private keys must be of the same type' }); + } + + { + // Base key is not a private key + await assert.rejects( + subtle.deriveKey( + { + name: 'X448', + public: keys.X448.publicKey + }, + keys.X448.publicKey, + ...otherArgs), + { message: /baseKey must be a private key/ }); + } + + { + // Base key is not a private key + await assert.rejects( + subtle.deriveKey( + { + name: 'X448', + public: keys.X448.privateKey + }, + keys.X448.publicKey, + ...otherArgs), + { message: /algorithm\.public must be a public key/ }); + } + + { + // Public is a secret key + const keyData = webcrypto.getRandomValues(new Uint8Array(32)); + const key = await subtle.importKey( + 'raw', + keyData, + { name: 'AES-CBC', length: 256 }, + false, ['encrypt']); + + await assert.rejects( + subtle.deriveKey( + { + name: 'X448', + public: key + }, + keys.X448.publicKey, + ...otherArgs), + { message: /algorithm\.public must be a public key/ }); + } +})().then(common.mustCall()); From 3a59f36efaa3a84b08858e93c5790c86bef4f8b2 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 13:22:45 +0200 Subject: [PATCH 07/23] add import export tests --- lib/internal/crypto/cfrg.js | 22 +- lib/internal/crypto/webcrypto.js | 2 +- .../test-webcrypto-export-import-cfrg.js | 308 ++++++++++++++++++ 3 files changed, 312 insertions(+), 20 deletions(-) create mode 100644 test/parallel/test-webcrypto-export-import-cfrg.js diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index ee3a336c4f5e8a..d785bfd50185f0 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -204,7 +204,7 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) { }); } -function crfgExportKey(key, format) { +function cfrgExportKey(key, format) { return jobPromise(new ECKeyExportJob( kCryptoJobAsync, format, @@ -293,24 +293,8 @@ async function cfrgImportKey( } } - let type; - switch (name) { - case 'Ed25519': - // Fall through - case 'Ed448': - // Fall through - case 'X25519': - // Fall through - case 'X448': - type = 'ECDH'; - break; - } - - if (name !== type) - throw lazyDOMException('Invalid algorithm name.', 'DataError'); - verifyAcceptableCfrgKeyUse( - type, + name, isPublic ? 'public' : 'private', usagesSet); keyObject = createCFRGRawKey( @@ -364,7 +348,7 @@ function eddsaSignVerify(key, data, signature) { } module.exports = { - crfgExportKey, + cfrgExportKey, cfrgImportKey, cfrgGenerateKey, eddsaSignVerify, diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index d2bd3a6c0769bb..dc50920239344c 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -308,7 +308,7 @@ async function exportKeySpki(key) { case 'X448': if (key.type === 'public') { return lazyRequire('internal/crypto/cfrg') - .crfgExportKey(key, kWebCryptoKeyFormatSPKI); + .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); } break; case 'NODE-DSA': diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js new file mode 100644 index 00000000000000..9f73701ad246e8 --- /dev/null +++ b/test/parallel/test-webcrypto-export-import-cfrg.js @@ -0,0 +1,308 @@ +'use strict'; + +const common = require('../common'); +const fixtures = require('../common/fixtures'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { subtle } = crypto.webcrypto; + +const keyData = { + 'Ed25519': { + jwsAlg: 'EdDSA', + spki: Buffer.from( + '302a300506032b6570032100a054b618c12b26c8d43595a5c38dd2b0140b944a' + + '151f75003278c2b6c58ec08f', 'hex'), + pkcs8: Buffer.from( + '302e020100300506032b657004220420d53150bdcd17b4d4b21ae756d4965639' + + 'd75b28f56ff9111b1f88326913e445bc', 'hex'), + jwk: { + kty: 'OKP', + crv: 'Ed25519', + x: 'oFS2GMErJsjUNZWlw43SsBQLlEoVH3UAMnjCtsWOwI8', + d: '1TFQvc0XtNSyGudW1JZWOddbKPVv-REbH4gyaRPkRbw' + } + }, + 'Ed448': { + jwsAlg: 'EdDSA', + spki: Buffer.from( + '3043300506032b6571033a0008cc38160c85bca5656ac4924af7ea97a9161b20' + + '2528273dcb84afd2eeb99ac912a401b34ef15ef4d9486406a6eecc31e5909219' + + 'bd54866800', 'hex'), + pkcs8: Buffer.from( + '3047020100300506032b6571043b0439afd05b2fbb153b47c18dfa66baaed0de' + + 'fb4e88c651487cdee0fafc40fa3d048fe1cd145a44143243c0468166b5bc161a' + + '82e3b904f3e2fcaaf9', 'hex'), + jwk: { + kty: 'OKP', + crv: 'Ed448', + x: 'CMw4FgyFvKVlasSSSvfql6kWGyAlKCc9y4Sv0u65mskSpAGzTvFe9NlIZAam7' + + 'swx5ZCSGb1UhmgA', + d: 'r9BbL7sVO0fBjfpmuq7Q3vtOiMZRSHze4Pr8QPo9BI_hzRRaRBQyQ8BGgWa1v' + + 'BYaguO5BPPi_Kr5' + } + }, + 'X25519': { + jwsAlg: 'ECDH-ES', + spki: Buffer.from( + '302a300506032b656e032100f38d9f4e621a44e0428176a4c8a534b34f07f8db' + + '30152f9ca0167aabf598fe65', 'hex'), + pkcs8: Buffer.from( + '302e020100300506032b656e04220420a8327850317b4b03a5a8b4e923413b1d' + + 'a4a642e0d6f7a72cf4d16a549e628a5f', 'hex'), + jwk: { + kty: 'OKP', + crv: 'X25519', + x: '842fTmIaROBCgXakyKU0s08H-NswFS-coBZ6q_WY_mU', + d: 'qDJ4UDF7SwOlqLTpI0E7HaSmQuDW96cs9NFqVJ5iil8' + } + }, + 'X448': { + jwsAlg: 'ECDH-ES', + spki: Buffer.from( + '3042300506032b656f0339001d451c8c0c369a42eadfc2875cd44953caeb46c4' + + '66dc86568280bfdbbb01f4709a1b0b1e0dd66cf7b11c84119ddc98890db72891' + + '29e30da4', 'hex'), + pkcs8: Buffer.from( + '3046020100300506032b656f043a0438fc818f6546a81f963c27765dc1c05bfd' + + 'b169667e5e0cf45318ed1cb93872217ab0d9004e0c7dd0dcb00192f72039cc1a' + + '1dff750ec31c8afb', 'hex'), + jwk: { + kty: 'OKP', + crv: 'X448', + x: 'HUUcjAw2mkLq38KHXNRJU8rrRsRm3IZWgoC_27sB9HCaGwseDdZs97EchBGd3' + + 'JiJDbcokSnjDaQ', + d: '_IGPZUaoH5Y8J3ZdwcBb_bFpZn5eDPRTGO0cuThyIXqw2QBODH3Q3LABkvcgO' + + 'cwaHf91DsMcivs' + } + } +}; + +const testVectors = [ + { + name: 'Ed25519', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'Ed448', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'X25519', + privateUsages: ['deriveKey', 'deriveBits'], + publicUsages: [] + }, + { + name: 'X448', + privateUsages: ['deriveKey', 'deriveBits'], + publicUsages: [] + }, +]; + +async function testImportSpki({ name, publicUsages }, extractable) { + const key = await subtle.importKey( + 'spki', + keyData[name].spki, + { name }, + extractable, + publicUsages); + assert.strictEqual(key.type, 'public'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, publicUsages); + assert.deepStrictEqual(key.algorithm.name, name); + + if (extractable) { + // Test the roundtrip + const spki = await subtle.exportKey('spki', key); + assert.strictEqual( + Buffer.from(spki).toString('hex'), + keyData[name].spki.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('spki', key), { + message: /key is not extractable/ + }); + } + + // Bad usage + await assert.rejects( + subtle.importKey( + 'spki', + keyData[name].spki, + { name }, + extractable, + ['wrapKey']), + { message: /Unsupported key usage/ }); +} + +async function testImportPkcs8({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + + if (extractable) { + // Test the roundtrip + const pkcs8 = await subtle.exportKey('pkcs8', key); + assert.strictEqual( + Buffer.from(pkcs8).toString('hex'), + keyData[name].pkcs8.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } +} + +async function testImportJwk({ name, publicUsages, privateUsages }, extractable) { + + const jwk = keyData[name].jwk; + + const [ + publicKey, + privateKey, + ] = await Promise.all([ + subtle.importKey( + 'jwk', + { + kty: jwk.kty, + crv: jwk.crv, + x: jwk.x, + }, + { name }, + extractable, publicUsages), + subtle.importKey( + 'jwk', + jwk, + { name }, + extractable, + privateUsages), + subtle.importKey( + 'jwk', + { + alg: keyData[name].jwsAlg, + kty: jwk.kty, + crv: jwk.crv, + x: jwk.x, + }, + { name }, + extractable, publicUsages), + subtle.importKey( + 'jwk', + { + ...jwk, + alg: keyData[name].jwsAlg, + }, + { name }, + extractable, + privateUsages), + ]); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(publicKey.extractable, extractable); + assert.strictEqual(privateKey.extractable, extractable); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm.name, name); + + if (extractable) { + // Test the round trip + const [ + pubJwk, + pvtJwk, + ] = await Promise.all([ + subtle.exportKey('jwk', publicKey), + subtle.exportKey('jwk', privateKey), + ]); + + assert.deepStrictEqual(pubJwk.key_ops, publicUsages); + assert.strictEqual(pubJwk.ext, true); + assert.strictEqual(pubJwk.kty, 'OKP'); + assert.strictEqual(pubJwk.x, jwk.x); + assert.strictEqual(pubJwk.crv, jwk.crv); + + assert.deepStrictEqual(pvtJwk.key_ops, privateUsages); + assert.strictEqual(pvtJwk.ext, true); + assert.strictEqual(pvtJwk.kty, 'OKP'); + assert.strictEqual(pvtJwk.x, jwk.x); + assert.strictEqual(pvtJwk.crv, jwk.crv); + assert.strictEqual(pvtJwk.d, jwk.d); + + if (jwk.crv.startsWith('Ed')) { + assert.strictEqual(pubJwk.alg, 'EdDSA'); + assert.strictEqual(pvtJwk.alg, 'EdDSA'); + } else { + assert.strictEqual(pubJwk.alg, undefined); + assert.strictEqual(pvtJwk.alg, undefined); + } + } else { + await assert.rejects( + subtle.exportKey('jwk', publicKey), { + message: /key is not extractable/ + }); + await assert.rejects( + subtle.exportKey('jwk', privateKey), { + message: /key is not extractable/ + }); + } +} + +(async function() { + const tests = []; + testVectors.forEach((vector) => { + [true, false].forEach((extractable) => { + tests.push(testImportSpki(vector, extractable)); + tests.push(testImportPkcs8(vector, extractable)); + tests.push(testImportJwk(vector, extractable)); + }); + }); + + await Promise.all(tests); +})().then(common.mustCall()); + +{ + const rsaPublic = crypto.createPublicKey( + fixtures.readKey('rsa_public_2048.pem')); + const rsaPrivate = crypto.createPrivateKey( + fixtures.readKey('rsa_private_2048.pem')); + + for (const [name, [publicUsage, privateUsage]] of Object.entries({ + 'Ed25519': ['verify', 'sign'], + 'X448': ['deriveBits', 'deriveBits'], + })) { + assert.rejects(subtle.importKey( + 'node.keyObject', + rsaPublic, + { name }, + true, [publicUsage]), { message: /Invalid key type/ }); + assert.rejects(subtle.importKey( + 'node.keyObject', + rsaPrivate, + { name }, + true, [privateUsage]), { message: /Invalid key type/ }); + assert.rejects(subtle.importKey( + 'spki', + rsaPublic.export({ format: 'der', type: 'spki' }), + { name }, + true, [publicUsage]), { message: /Invalid key type/ }); + assert.rejects(subtle.importKey( + 'pkcs8', + rsaPrivate.export({ format: 'der', type: 'pkcs8' }), + { name }, + true, [privateUsage]), { message: /Invalid key type/ }); + } +} From 211d3e057964332cca711b1c996d0c39a2704216 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 13:26:43 +0200 Subject: [PATCH 08/23] add keygen tests --- test/parallel/test-webcrypto-keygen.js | 94 ++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index 502c86cf32abf3..17f6924caec286 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -129,6 +129,40 @@ const vectors = { 'deriveBits', ] }, + 'Ed25519': { + usages: [ + 'sign', + 'verify', + ], + mandatoryUsages: ['sign'] + }, + 'Ed448': { + usages: [ + 'sign', + 'verify', + ], + mandatoryUsages: ['sign'] + }, + 'X25519': { + usages: [ + 'deriveKey', + 'deriveBits', + ], + mandatoryUsages: [ + 'deriveKey', + 'deriveBits', + ] + }, + 'X448': { + usages: [ + 'deriveKey', + 'deriveBits', + ], + mandatoryUsages: [ + 'deriveKey', + 'deriveBits', + ] + }, 'NODE-DSA': { algorithm: { modulusLength: 1024, hash: 'SHA-256' }, usages: [ @@ -665,3 +699,63 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); assert(!isCryptoKey(buffer)); assert(!isCryptoKey(keyObject)); } + +// Test OKP Key Generation +{ + async function test( + name, + privateUsages, + publicUsages = privateUsages) { + + let usages = privateUsages; + if (publicUsages !== privateUsages) + usages = usages.concat(publicUsages); + + const { publicKey, privateKey } = await subtle.generateKey({ + name, + }, true, usages); + + assert(publicKey); + assert(privateKey); + assert(isCryptoKey(publicKey)); + assert(isCryptoKey(privateKey)); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(publicKey.extractable, true); + assert.strictEqual(privateKey.extractable, true); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm.name, name); + } + + const kTests = [ + [ + 'Ed25519', + ['sign'], + ['verify'], + ], + [ + 'Ed448', + ['sign'], + ['verify'], + ], + [ + 'X25519', + ['deriveKey', 'deriveBits'], + [], + ], + [ + 'X448', + ['deriveKey', 'deriveBits'], + [], + ], + ]; + + const tests = kTests.map((args) => test(...args)); + + // Test bad parameters + + Promise.all(tests).then(common.mustCall()); +} From 3a89861c1915e174b3d77b016662e37abb53e801 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 13:31:28 +0200 Subject: [PATCH 09/23] fix test ecdh messages --- test/parallel/test-webcrypto-derivebits-ecdh.js | 2 +- test/parallel/test-webcrypto-derivekey-ecdh.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-webcrypto-derivebits-ecdh.js b/test/parallel/test-webcrypto-derivebits-ecdh.js index 166da81e3e4e6d..de19ac1cd4e415 100644 --- a/test/parallel/test-webcrypto-derivebits-ecdh.js +++ b/test/parallel/test-webcrypto-derivebits-ecdh.js @@ -207,7 +207,7 @@ async function prepareKeys() { name: 'ECDH', public: publicKey }, keys['P-521'].privateKey, null), { - message: /Keys must be ECDH keys/ + message: /Keys must be ECDH, X25519, or X448 keys/ }); } diff --git a/test/parallel/test-webcrypto-derivekey-ecdh.js b/test/parallel/test-webcrypto-derivekey-ecdh.js index 42c8d250f42b06..203a0c5677fbc0 100644 --- a/test/parallel/test-webcrypto-derivekey-ecdh.js +++ b/test/parallel/test-webcrypto-derivekey-ecdh.js @@ -175,7 +175,7 @@ async function prepareKeys() { }, keys['P-521'].privateKey, ...otherArgs), - { message: /Keys must be ECDH keys/ }); + { message: /Keys must be ECDH, X25519, or X448 keys/ }); } { From da87c06025afc8a04fc63cce3d2b2fef6a0d8d9c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 14:10:43 +0200 Subject: [PATCH 10/23] remove proprietary docs, add updated docs --- doc/api/webcrypto.md | 143 +++++++++++++------------------------- tools/doc/type-parser.mjs | 5 +- 2 files changed, 49 insertions(+), 99 deletions(-) diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 9454fead349b69..1696d75143587c 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -69,22 +69,20 @@ async function generateEcKey(namedCurve = 'P-521') { } ``` -#### ED25519/ED448/X25519/X448 key pairs +#### Ed25519/Ed448/X25519/X448 key pairs ```js const { subtle } = require('node:crypto').webcrypto; async function generateEd25519Key() { return subtle.generateKey({ - name: 'NODE-ED25519', - namedCurve: 'NODE-ED25519', + name: 'Ed25519', }, true, ['sign', 'verify']); } async function generateX25519Key() { return subtle.generateKey({ - name: 'ECDH', - namedCurve: 'NODE-X25519', + name: 'X25519', }, true, ['deriveKey']); } ``` @@ -315,7 +313,11 @@ implementation and the APIs supported for each: | `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | | `'RSA-OAEP'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | | `'ECDSA'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | +| `'Ed25519'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | +| `'Ed448'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | | `'ECDH'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | +| `'X25519'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | +| `'X448'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | | `'AES-CTR'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | | `'AES-CBC'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | | `'AES-GCM'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | @@ -329,8 +331,6 @@ implementation and the APIs supported for each: | `'SHA-512'` | | | | | | | | | | | | ✔ | | `'NODE-DSA'`[^1] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | | `'NODE-DH'`[^1] | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | -| `'NODE-ED25519'`[^1] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | -| `'NODE-ED448'`[^1] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | ## Class: `Crypto` @@ -394,7 +394,7 @@ added: v15.0.0 -* Type: {AesKeyGenParams|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams|NodeEdKeyGenParams} +* Type: {AesKeyGenParams|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams} @@ -459,7 +459,11 @@ Valid key usages depend on the key algorithm (identified by | `'AES-GCM'` | ✔ | ✔ | | | | | ✔ | ✔ | | `'AES-KW'` | | | | | | | ✔ | ✔ | | `'ECDH'` | | | | | ✔ | ✔ | | | +| `'X25519'` | | | | | ✔ | ✔ | | | +| `'X448'` | | | | | ✔ | ✔ | | | | `'ECDSA'` | | | ✔ | ✔ | | | | | +| `'Ed25519'` | | | ✔ | ✔ | | | | | +| `'Ed448'` | | | ✔ | ✔ | | | | | | `'HDKF'` | | | | | ✔ | ✔ | | | | `'HMAC'` | | | ✔ | ✔ | | | | | | `'PBKDF2'` | | | | | ✔ | ✔ | | | @@ -469,8 +473,6 @@ Valid key usages depend on the key algorithm (identified by | `'NODE-DSA'`[^1] | | | ✔ | ✔ | | | | | | `'NODE-DH'`[^1] | | | | | ✔ | ✔ | | | | `'NODE-SCRYPT'`[^1] | | | | | ✔ | ✔ | | | -| `'NODE-ED25519'`[^1] | | | ✔ | ✔ | | | | | -| `'NODE-ED448'`[^1] | | | ✔ | ✔ | | | | | ## Class: `CryptoKeyPair` @@ -534,7 +536,7 @@ added: v15.0.0 -* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams} +* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|AlgorithmIdentifier|NodeDhDeriveBitsParams|NodeScryptParams} * `baseKey`: {CryptoKey} * `length`: {number} * Returns: {Promise} containing {ArrayBuffer} @@ -563,7 +565,7 @@ added: v15.0.0 -* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams} +* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|AlgorithmIdentifier|NodeDhDeriveBitsParams|NodeScryptParams} * `baseKey`: {CryptoKey} * `derivedKeyAlgorithm`: {HmacKeyGenParams|AesKeyGenParams} * `extractable`: {boolean} @@ -674,6 +676,8 @@ extension that allows converting a {CryptoKey} into a Node.js {KeyObject}. | `'AES-KW'` | | | ✔ | ✔ | | `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | +| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | +| `'Ed448'` | ✔ | ✔ | ✔ | ✔ | | `'HDKF'` | | | | | | `'HMAC'` | | | ✔ | ✔ | | `'PBKDF2'` | | | | | @@ -683,8 +687,6 @@ extension that allows converting a {CryptoKey} into a Node.js {KeyObject}. | `'NODE-DSA'`[^1] | ✔ | ✔ | | | | `'NODE-DH'`[^1] | ✔ | ✔ | | | | `'NODE-SCRYPT'`[^1] | | | | | -| `'NODE-ED25519'`[^1] | ✔ | ✔ | ✔ | ✔ | -| `'NODE-ED448'`[^1] | ✔ | ✔ | ✔ | ✔ | ### `subtle.generateKey(algorithm, extractable, keyUsages)` @@ -694,7 +696,7 @@ added: v15.0.0 -* `algorithm`: {RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams|NodeEdKeyGenParams} +* `algorithm`: {RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|AlgorithmIdentifier|NodeDsaKeyGenParams|NodeDhKeyGenParams} @@ -713,11 +715,13 @@ include: * `'RSA-PSS'` * `'RSA-OAEP'` * `'ECDSA'` +* `'Ed25519'` +* `'Ed448'` * `'ECDH'` +* `'X25519'` +* `'X448'` * `'NODE-DSA'`[^1] * `'NODE-DH'`[^1] -* `'NODE-ED25519'`[^1] -* `'NODE-ED448'`[^1] The {CryptoKey} (secret key) generating algorithms supported include: @@ -743,7 +747,7 @@ changes: -* `algorithm`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams|Pbkdf2ImportParams|NodeDsaImportParams|NodeDhImportParams|NodeScryptImportParams|NodeEdKeyImportParams} +* `algorithm`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams|Pbkdf2ImportParams|AlgorithmIdentifier|NodeDsaImportParams|NodeDhImportParams|NodeScryptImportParams} @@ -770,7 +774,11 @@ The algorithms currently supported include: | `'AES-GCM'` | | | ✔ | ✔ | | `'AES-KW'` | | | ✔ | ✔ | | `'ECDH'` | ✔ | ✔ | ✔ | ✔ | +| `'X25519'` | ✔ | ✔ | ✔ | ✔ | +| `'X448'` | ✔ | ✔ | ✔ | ✔ | | `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | +| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | +| `'Ed448'` | ✔ | ✔ | ✔ | ✔ | | `'HDKF'` | | | | ✔ | | `'HMAC'` | | | ✔ | ✔ | | `'PBKDF2'` | | | | ✔ | @@ -780,8 +788,6 @@ The algorithms currently supported include: | `'NODE-DSA'`[^1] | ✔ | ✔ | | | | `'NODE-DH'`[^1] | ✔ | ✔ | | | | `'NODE-SCRYPT'`[^1] | | | | ✔ | -| `'NODE-ED25519'`[^1] | ✔ | ✔ | ✔ | ✔ | -| `'NODE-ED448'`[^1] | ✔ | ✔ | ✔ | ✔ | ### `subtle.sign(algorithm, key, data)` @@ -791,7 +797,7 @@ added: v15.0.0 -* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|NodeDsaSignParams} +* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|NodeDsaSignParams} * `key`: {CryptoKey} * `data`: {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} containing {ArrayBuffer} @@ -808,10 +814,10 @@ The algorithms currently supported include: * `'RSASSA-PKCS1-v1_5'` * `'RSA-PSS'` * `'ECDSA'` +* `'Ed25519'` +* `'Ed448'` * `'HMAC'` * `'NODE-DSA'`[^1] -* `'NODE-ED25519'`[^1] -* `'NODE-ED448'`[^1] ### `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` @@ -874,7 +880,7 @@ added: v15.0.0 -* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|NodeDsaSignParams} +* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|NodeDsaSignParams} * `key`: {CryptoKey} * `signature`: {ArrayBuffer|TypedArray|DataView|Buffer} * `data`: {ArrayBuffer|TypedArray|DataView|Buffer} @@ -892,10 +898,10 @@ The algorithms currently supported include: * `'RSASSA-PKCS1-v1_5'` * `'RSA-PSS'` * `'ECDSA'` +* `'Ed25519'` +* `'Ed448'` * `'HMAC'` * `'NODE-DSA'`[^1] -* `'NODE-ED25519'`[^1] -* `'NODE-ED448'`[^1] ### `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` @@ -933,6 +939,20 @@ The algorithm parameter objects define the methods and parameters used by the various {SubtleCrypto} methods. While described here as "classes", they are simple JavaScript dictionary objects. +### Class: `AlgorithmIdentifier` + + + +#### `algorithmIdentifier.name` + + + +* Type: {string} + ### Class: `AesCbcParams` -* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`, - `'NODE-ED25519'`, `'NODE-ED448'`, `'NODE-X25519'`, or `'NODE-X448'`. +* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`. ### Class: `EcKeyImportParams` @@ -1204,8 +1223,7 @@ added: v15.0.0 added: v15.0.0 --> -* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`, - `'NODE-ED25519'`, `'NODE-ED448'`, `'NODE-X25519'`, or `'NODE-X448'`. +* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`. ### Class: `HkdfParams` @@ -1781,71 +1799,6 @@ added: v15.0.0 * Type: {string} Must be `'NODE-DSA'` -### `NODE-ED25519` and `NODE-ED448` Algorithms - - - -#### Class: `NodeEdKeyGenParams` - - - -##### `nodeEdKeyGenParams.name` - - - -* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'` or `'ECDH'`. - -##### `nodeEdKeyGenParams.namedCurve` - - - -* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'`, - `'NODE-X25519'`, or `'NODE-X448'`. - -#### Class: `NodeEdKeyImportParams` - - - -##### `nodeEdKeyImportParams.name` - - - -* Type: {string} Must be one of `'NODE-ED25519'` or `'NODE-ED448'` - if importing an `Ed25519` or `Ed448` key, or `'ECDH'` if importing - an `X25519` or `X448` key. - -##### `nodeEdKeyImportParams.namedCurve` - - - -* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'`, - `'NODE-X25519'`, or `'NODE-X448'`. - -##### `nodeEdKeyImportParams.public` - - - -* Type: {boolean} - -The `public` parameter is used to specify that the `'raw'` format key is to be -interpreted as a public key. **Default:** `false`. - ### `NODE-SCRYPT` Algorithm + > Stability: 1 - Experimental @@ -532,6 +548,10 @@ The algorithms currently supported include: @@ -561,6 +581,10 @@ The algorithms currently supported include: @@ -642,6 +666,10 @@ The algorithms currently supported include: @@ -876,6 +912,10 @@ The unwrapped key algorithms supported include: From 51d68f19efe20f6c15f9e0188bf27d880db11481 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 29 Mar 2022 14:28:33 +0200 Subject: [PATCH 13/23] remove doc trailing whitespace --- doc/api/webcrypto.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index d815dd7903ad42..3fc1be2ad34720 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -4,11 +4,11 @@ changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/42507 - description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'` + description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'` algorithms. - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/42507 - description: Removed proprietary `'NODE-ED25519'` and `'NODE-ED448'` + description: Removed proprietary `'NODE-ED25519'` and `'NODE-ED448'` algorithms. - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/42507 @@ -668,7 +668,7 @@ added: v15.0.0 changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/42507 - description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'` + description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'` algorithms. - version: v15.9.0 pr-url: https://github.com/nodejs/node/pull/37203 @@ -766,7 +766,7 @@ added: v15.0.0 changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/42507 - description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'` + description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'` algorithms. - version: v15.9.0 pr-url: https://github.com/nodejs/node/pull/37203 From 98bc13db8125695d59ae37c189167c89d7453917 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 31 Mar 2022 10:32:59 +0200 Subject: [PATCH 14/23] deal with unsupported Ed448 context --- lib/internal/crypto/cfrg.js | 11 ++++++- lib/internal/crypto/webcrypto.js | 2 +- .../test-webcrypto-sign-verify-eddsa.js | 31 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index d785bfd50185f0..124966fc51f26f 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -325,13 +325,22 @@ async function cfrgImportKey( extractable); } -function eddsaSignVerify(key, data, signature) { +function eddsaSignVerify(key, data, { name, context }, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; if (key.type !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); + if (name === 'Ed448' && context !== undefined) { + context = + getArrayBufferOrView(context, 'algorithm.context'); + if (context.byteLength !== 0) { + throw lazyDOMException( + 'Non zero-length context is not yet supported.', 'NotSupportedError'); + } + } + return jobPromise(new SignJob( kCryptoJobAsync, mode, diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 76ddcfd9bb43c9..3eba3af345aa7e 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -712,7 +712,7 @@ function signVerify(algorithm, key, data, signature) { case 'Ed448': // Fall through return lazyRequire('internal/crypto/cfrg') - .eddsaSignVerify(key, data, signature); + .eddsaSignVerify(key, data, algorithm, signature); case 'HMAC': return lazyRequire('internal/crypto/mac') .hmacSignVerify(key, data, algorithm, signature); diff --git a/test/parallel/test-webcrypto-sign-verify-eddsa.js b/test/parallel/test-webcrypto-sign-verify-eddsa.js index 4d23d11eaa28b1..cec4f57a41a2a4 100644 --- a/test/parallel/test-webcrypto-sign-verify-eddsa.js +++ b/test/parallel/test-webcrypto-sign-verify-eddsa.js @@ -230,3 +230,34 @@ async function testSign({ name, await Promise.all(variations); })().then(common.mustCall()); + +// Ed448 context +{ + const vector = vectors.find(({ name }) => name === 'Ed448'); + Promise.all([ + subtle.importKey( + 'pkcs8', + vector.privateKeyBuffer, + { name: 'Ed448' }, + false, + ['sign']), + subtle.importKey( + 'spki', + vector.publicKeyBuffer, + { name: 'Ed448' }, + false, + ['verify']), + ]).then(async ([privateKey, publicKey]) => { + const sig = await subtle.sign({ name: 'Ed448', context: Buffer.alloc(0) }, privateKey, vector.data); + assert.deepStrictEqual(Buffer.from(sig), vector.signature); + assert.strictEqual( + await subtle.verify({ name: 'Ed448', context: Buffer.alloc(0) }, publicKey, sig, vector.data), true); + + await assert.rejects(subtle.sign({ name: 'Ed448', context: Buffer.alloc(1) }, privateKey, vector.data), { + message: /Non zero-length context is not yet supported/ + }); + await assert.rejects(subtle.verify({ name: 'Ed448', context: Buffer.alloc(1) }, publicKey, sig, vector.data), { + message: /Non zero-length context is not yet supported/ + }); + }).then(common.mustCall()); +} From e69f1116f259d9b556ab4f6716b28376b14500b0 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 31 Mar 2022 12:49:59 +0200 Subject: [PATCH 15/23] add Ed448Params docs --- doc/api/webcrypto.md | 31 +++++++++++++++++++++++++++++-- tools/doc/type-parser.mjs | 1 + 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 3fc1be2ad34720..6551a36705a5b9 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -833,7 +833,7 @@ changes: -* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|NodeDsaSignParams} +* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|Ed448Params|NodeDsaSignParams} * `key`: {CryptoKey} * `data`: {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} containing {ArrayBuffer} @@ -920,7 +920,7 @@ changes: -* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|NodeDsaSignParams} +* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|Ed448Params|NodeDsaSignParams} * `key`: {CryptoKey} * `signature`: {ArrayBuffer|TypedArray|DataView|Buffer} * `data`: {ArrayBuffer|TypedArray|DataView|Buffer} @@ -1265,6 +1265,33 @@ added: v15.0.0 * Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`. +### Class: `Ed448Params` + + + +#### `ed448Params.name` + + + +* Type: {string} Must be `'Ed448'`. + +#### `ed448Params.context` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +The `context` member represents the optional context data to associate with +the message. +The Node.js Web Crypto API implementation only supports zero-length context +which is equivalent to not providing context at all. + ### Class: `HkdfParams` -* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|AlgorithmIdentifier|NodeDhDeriveBitsParams|NodeScryptParams} +* `algorithm`: {AlgorithmIdentifier|EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams} * `baseKey`: {CryptoKey} * `length`: {number} * Returns: {Promise} containing {ArrayBuffer} @@ -589,7 +589,7 @@ changes: -* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|AlgorithmIdentifier|NodeDhDeriveBitsParams|NodeScryptParams} +* `algorithm`: {AlgorithmIdentifier|EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams} * `baseKey`: {CryptoKey} * `derivedKeyAlgorithm`: {HmacKeyGenParams|AesKeyGenParams} * `extractable`: {boolean} @@ -724,7 +724,7 @@ added: v15.0.0 -* `algorithm`: {RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|AlgorithmIdentifier|NodeDsaKeyGenParams|NodeDhKeyGenParams} +* `algorithm`: {AlgorithmIdentifier|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams} @@ -779,7 +779,7 @@ changes: -* `algorithm`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams|Pbkdf2ImportParams|AlgorithmIdentifier|NodeDsaImportParams|NodeDhImportParams|NodeScryptImportParams} +* `algorithm`: {AlgorithmIdentifier|RsaHashedImportParams|EcKeyImportParams|HmacImportParams|NodeDsaImportParams} @@ -833,7 +833,7 @@ changes: -* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|Ed448Params|NodeDsaSignParams} +* `algorithm`: {AlgorithmIdentifier|RsaPssParams|EcdsaParams|Ed448Params} * `key`: {CryptoKey} * `data`: {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} containing {ArrayBuffer} @@ -867,8 +867,8 @@ added: v15.0.0 -* `unwrapAlgo`: {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams|AesKwParams} -* `unwrappedKeyAlgo`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams} +* `unwrapAlgo`: {AlgorithmIdentifier|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `unwrappedKeyAlgo`: {AlgorithmIdentifier|RsaHashedImportParams|EcKeyImportParams|HmacImportParams} @@ -920,7 +920,7 @@ changes: -* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|AlgorithmIdentifier|Ed448Params|NodeDsaSignParams} +* `algorithm`: {AlgorithmIdentifier|RsaPssParams|EcdsaParams|Ed448Params} * `key`: {CryptoKey} * `signature`: {ArrayBuffer|TypedArray|DataView|Buffer} * `data`: {ArrayBuffer|TypedArray|DataView|Buffer} @@ -952,7 +952,7 @@ added: v15.0.0 * `format`: {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. * `key`: {CryptoKey} * `wrappingKey`: {CryptoKey} -* `wrapAlgo`: {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams|AesKwParams} +* `wrapAlgo`: {AlgorithmIdentifier|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} * Returns: {Promise} containing {ArrayBuffer} In cryptography, "wrapping a key" refers to exporting and then encrypting the @@ -1107,21 +1107,6 @@ added: v15.0.0 This values must be one of `32`, `64`, `96`, `104`, `112`, `120`, or `128`. **Default:** `128`. -### Class: `AesImportParams` - - - -#### `aesImportParams.name` - - - -* Type: {string} Must be one of `'AES-CTR'`, `'AES-CBC'`, `'AES-GCM'`, or - `'AES-KW'`. - ### Class: `AesKeyGenParams` - -#### `aesKwParams.name` - - - -* Type: {string} Must be `'AES-KW'`. - ### Class: `EcdhKeyDeriveParams` - -#### `hmacParams.name` - - - -* Type: {string} Must be `'HMAC'`. - -### Class: `Pbkdf2ImportParams` - - - -#### `pbkdf2ImportParams.name` - - - -* Type: {string} Must be `'PBKDF2'` - ### Class: `Pbkdf2Params` - -#### `rsaSignParams.name` - - - -* Type: {string} Must be `'RSASSA-PKCS1-v1_5'` - ## Node.js-specific extensions The Node.js Web Crypto API extends various aspects of the Web Crypto API. @@ -1692,20 +1621,6 @@ added: v15.0.0 The `NODE-DH` algorithm is the common implementation of Diffie-Hellman key agreement. -#### Class: `NodeDhImportParams` - - - -##### `nodeDhImportParams.name` - - - -* Type: {string} Must be `'NODE-DH'`. - #### Class: `NodeDhKeyGenParams` - -##### `nodeDsaSignParams.name` - - - -* Type: {string} Must be `'NODE-DSA'` - ### `NODE-SCRYPT` Algorithm - -##### `nodeScryptImportParams.name` - - - -* Type: {string} Must be `'NODE-SCRYPT'`. - #### Class: `NodeScryptParams` + + * `format`: {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. * `key`: {CryptoKey} * `wrappingKey`: {CryptoKey} * `wrapAlgo`: {AlgorithmIdentifier|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} * Returns: {Promise} containing {ArrayBuffer} + + In cryptography, "wrapping a key" refers to exporting and then encrypting the keying material. The `subtle.wrapKey()` method exports the keying material into the format identified by `format`, then encrypts it using the method and From af94f8d8b37f10339d5494bf1f1170e2663cf1a0 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 1 Apr 2022 10:28:11 +0200 Subject: [PATCH 18/23] fix EcdhKeyDeriveParams params use with X25519 and X448 --- doc/api/webcrypto.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 1eb7c4f35f81fc..336cfaf4d04d91 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -1149,7 +1149,7 @@ added: v15.0.0 added: v15.0.0 --> -* Type: {string} Must be `'ECDH'`. +* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`. #### `ecdhKeyDeriveParams.public` From 2d8e4c53ef086d6ff6bae264bcc863126f8cf586 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 1 Apr 2022 11:50:06 +0200 Subject: [PATCH 19/23] move cfrg algorithms away from node-specific exts --- lib/internal/crypto/util.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 0ceca3ceeeaca6..83926451edd490 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -166,15 +166,15 @@ const kAlgorithms = { 'sha-512': 'SHA-512', 'hkdf': 'HKDF', 'pbkdf2': 'PBKDF2', + 'ed25519': 'Ed25519', + 'ed448': 'Ed448', + 'x25519': 'X25519', + 'x448': 'X448', // Following here are Node.js specific extensions. All // should be prefixed with 'node-' 'node-dsa': 'NODE-DSA', 'node-dh': 'NODE-DH', 'node-scrypt': 'NODE-SCRYPT', - 'ed25519': 'Ed25519', - 'ed448': 'Ed448', - 'x25519': 'X25519', - 'x448': 'X448', }; const kAlgorithmsKeys = ObjectKeys(kAlgorithms); From bf409be05f8622faf171c5532f718a02ccb7f1ff Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 2 Jun 2022 09:14:01 +0200 Subject: [PATCH 20/23] refactor test/fixtures/crypto/eddsa.js --- test/fixtures/crypto/eddsa.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/test/fixtures/crypto/eddsa.js b/test/fixtures/crypto/eddsa.js index 4aeba95dc730c7..8b1a5ce1c5a45d 100644 --- a/test/fixtures/crypto/eddsa.js +++ b/test/fixtures/crypto/eddsa.js @@ -39,16 +39,13 @@ module.exports = function() { const algorithms = ['Ed25519', 'Ed448']; - const vectors = []; - algorithms.forEach((algorithm) => { - vectors.push({ - publicKeyBuffer: spki[algorithm], - privateKeyBuffer: pkcs8[algorithm], - name: algorithm, - data, - signature: signatures[algorithm] - }); - }); + const vectors = algorithms.map((algorithm) => ({ + publicKeyBuffer: spki[algorithm], + privateKeyBuffer: pkcs8[algorithm], + name: algorithm, + data, + signature: signatures[algorithm], + })); return vectors; } From 2a5dc1c10c005320b1a1570437383dbcf97b6207 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 2 Jun 2022 11:21:34 +0200 Subject: [PATCH 21/23] add experimental warning upon use --- doc/api/webcrypto.md | 49 ++++++++++++++++++++----------------- lib/internal/crypto/cfrg.js | 5 ++++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 336cfaf4d04d91..9803ee49393b22 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -87,6 +87,8 @@ async function generateEcKey(namedCurve = 'P-521') { #### Ed25519/Ed448/X25519/X448 key pairs +> Stability: 1 - Experimental + ```js const { subtle } = require('node:crypto').webcrypto; @@ -329,11 +331,11 @@ implementation and the APIs supported for each: | `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | | `'RSA-OAEP'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | | `'ECDSA'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | -| `'Ed25519'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | -| `'Ed448'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | +| `'Ed25519'`[^2] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | +| `'Ed448'`[^2] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | | | `'ECDH'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | -| `'X25519'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | -| `'X448'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | +| `'X25519'`[^2] | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | +| `'X448'`[^2] | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | | | `'AES-CTR'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | | `'AES-CBC'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | | `'AES-GCM'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | | @@ -475,11 +477,11 @@ Valid key usages depend on the key algorithm (identified by | `'AES-GCM'` | ✔ | ✔ | | | | | ✔ | ✔ | | `'AES-KW'` | | | | | | | ✔ | ✔ | | `'ECDH'` | | | | | ✔ | ✔ | | | -| `'X25519'` | | | | | ✔ | ✔ | | | -| `'X448'` | | | | | ✔ | ✔ | | | +| `'X25519'`[^2] | | | | | ✔ | ✔ | | | +| `'X448'`[^2] | | | | | ✔ | ✔ | | | | `'ECDSA'` | | | ✔ | ✔ | | | | | -| `'Ed25519'` | | | ✔ | ✔ | | | | | -| `'Ed448'` | | | ✔ | ✔ | | | | | +| `'Ed25519'`[^2] | | | ✔ | ✔ | | | | | +| `'Ed448'`[^2] | | | ✔ | ✔ | | | | | | `'HDKF'` | | | | | ✔ | ✔ | | | | `'HMAC'` | | | ✔ | ✔ | | | | | | `'PBKDF2'` | | | | | ✔ | ✔ | | | @@ -704,8 +706,8 @@ extension that allows converting a {CryptoKey} into a Node.js {KeyObject}. | `'AES-KW'` | | | ✔ | ✔ | | `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed448'` | ✔ | ✔ | ✔ | ✔ | +| `'Ed25519'`[^2] | ✔ | ✔ | ✔ | ✔ | +| `'Ed448'`[^2] | ✔ | ✔ | ✔ | ✔ | | `'HDKF'` | | | | | | `'HMAC'` | | | ✔ | ✔ | | `'PBKDF2'` | | | | | @@ -743,11 +745,11 @@ include: * `'RSA-PSS'` * `'RSA-OAEP'` * `'ECDSA'` -* `'Ed25519'` -* `'Ed448'` +* `'Ed25519'`[^2] +* `'Ed448'`[^2] * `'ECDH'` -* `'X25519'` -* `'X448'` +* `'X25519'`[^2] +* `'X448'`[^2] * `'NODE-DSA'`[^1] * `'NODE-DH'`[^1] @@ -806,11 +808,11 @@ The algorithms currently supported include: | `'AES-GCM'` | | | ✔ | ✔ | | `'AES-KW'` | | | ✔ | ✔ | | `'ECDH'` | ✔ | ✔ | ✔ | ✔ | -| `'X25519'` | ✔ | ✔ | ✔ | ✔ | -| `'X448'` | ✔ | ✔ | ✔ | ✔ | +| `'X25519'`[^2] | ✔ | ✔ | ✔ | ✔ | +| `'X448'`[^2] | ✔ | ✔ | ✔ | ✔ | | `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed448'` | ✔ | ✔ | ✔ | ✔ | +| `'Ed25519'`[^2] | ✔ | ✔ | ✔ | ✔ | +| `'Ed448'`[^2] | ✔ | ✔ | ✔ | ✔ | | `'HDKF'` | | | | ✔ | | `'HMAC'` | | | ✔ | ✔ | | `'PBKDF2'` | | | | ✔ | @@ -850,8 +852,8 @@ The algorithms currently supported include: * `'RSASSA-PKCS1-v1_5'` * `'RSA-PSS'` * `'ECDSA'` -* `'Ed25519'` -* `'Ed448'` +* `'Ed25519'`[^2] +* `'Ed448'`[^2] * `'HMAC'` * `'NODE-DSA'`[^1] @@ -938,8 +940,8 @@ The algorithms currently supported include: * `'RSASSA-PKCS1-v1_5'` * `'RSA-PSS'` * `'ECDSA'` -* `'Ed25519'` -* `'Ed448'` +* `'Ed25519'`[^2] +* `'Ed448'`[^2] * `'HMAC'` * `'NODE-DSA'`[^1] @@ -1837,9 +1839,12 @@ added: v15.0.0 * Type: {string|ArrayBuffer|Buffer|TypedArray|DataView} [^1]: Non-standard Node.js-specific extension +[^2]: An experimental implementation of +[Secure Curves in the Web Cryptography API][] as of 05 May 2022 [JSON Web Key]: https://tools.ietf.org/html/rfc7517 [Key usages]: #cryptokeyusages [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt +[Secure Curves in the Web Cryptography API]: https://wicg.github.io/webcrypto-secure-curves/ [Web Crypto API]: https://www.w3.org/TR/WebCryptoAPI/ diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 124966fc51f26f..eada39c526c79a 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -35,6 +35,7 @@ const { } = require('internal/crypto/util'); const { + emitExperimentalWarning, lazyDOMException, } = require('internal/util'); @@ -121,6 +122,7 @@ function createCFRGRawKey(name, keyData, isPublic) { async function cfrgGenerateKey(algorithm, extractable, keyUsages) { const { name } = algorithm; + emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); const usageSet = new SafeSet(keyUsages); switch (name) { @@ -205,6 +207,7 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) { } function cfrgExportKey(key, format) { + emitExperimentalWarning(`The ${key.algorithm.name} Web Crypto API algorithm`); return jobPromise(new ECKeyExportJob( kCryptoJobAsync, format, @@ -219,6 +222,7 @@ async function cfrgImportKey( keyUsages) { const { name } = algorithm; + emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); let keyObject; const usagesSet = new SafeSet(keyUsages); switch (format) { @@ -326,6 +330,7 @@ async function cfrgImportKey( } function eddsaSignVerify(key, data, { name, context }, signature) { + emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; From ad465b240f7c274d5efbdfd0067995704c086a3e Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 2 Jun 2022 11:38:29 +0200 Subject: [PATCH 22/23] run make format-md --- doc/api/webcrypto.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 9803ee49393b22..8973ce9d820545 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -1839,8 +1839,9 @@ added: v15.0.0 * Type: {string|ArrayBuffer|Buffer|TypedArray|DataView} [^1]: Non-standard Node.js-specific extension + [^2]: An experimental implementation of -[Secure Curves in the Web Cryptography API][] as of 05 May 2022 + [Secure Curves in the Web Cryptography API][] as of 05 May 2022 [JSON Web Key]: https://tools.ietf.org/html/rfc7517 [Key usages]: #cryptokeyusages From 3eb7ce7f3561aac07cf90221e90bbc08f3505ad7 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 2 Jun 2022 18:42:19 +0200 Subject: [PATCH 23/23] resolve nit Co-authored-by: Antoine du Hamel --- lib/internal/crypto/cfrg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index eada39c526c79a..041f2d824a79bf 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -21,7 +21,7 @@ const { const { codes: { ERR_INVALID_ARG_TYPE, - } + }, } = require('internal/errors'); const {