'use strict'; const common = require('../common'); const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); const { subtle } = globalThis.crypto; const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto'); { async function test() { const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32)); await Promise.all([1, null, undefined, {}, []].map((format) => assert.rejects( subtle.importKey(format, keyData, {}, false, ['wrapKey']), { code: 'ERR_INVALID_ARG_VALUE' }) )); await assert.rejects( subtle.importKey('not valid', keyData, {}, false, ['wrapKey']), { code: 'ERR_INVALID_ARG_VALUE' }); await assert.rejects( subtle.importKey('KeyObject', keyData, {}, false, ['wrapKey']), { message: /'KeyObject' is not a valid enum value of type KeyFormat/, code: 'ERR_INVALID_ARG_VALUE' }); await assert.rejects( subtle.importKey('raw', 1, {}, false, ['deriveBits']), { code: 'ERR_INVALID_ARG_TYPE' }); await assert.rejects( subtle.importKey('raw', keyData, { name: 'HMAC' }, false, ['sign', 'verify']), { code: 'ERR_MISSING_OPTION' }); await assert.rejects( subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256', length: 384, }, false, ['sign', 'verify']), { name: 'DataError', message: 'Invalid key length' }); await assert.rejects( subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['deriveBits']), { name: 'SyntaxError', message: 'Unsupported key usage for HMAC key' }); await assert.rejects( subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256', length: 0 }, false, ['sign', 'verify']), { name: 'DataError', message: 'HmacImportParams.length cannot be 0' }); await assert.rejects( subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256', length: 1 }, false, ['sign', 'verify']), { name: 'NotSupportedError', message: 'Unsupported HmacImportParams.length' }); await assert.rejects( subtle.importKey('jwk', null, { name: 'HMAC', hash: 'SHA-256', }, false, ['sign', 'verify']), { name: 'DataError', message: 'Invalid keyData' }); } test().then(common.mustCall()); } // Import/Export HMAC Secret Key { async function test() { const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32)); const key = await subtle.importKey( 'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign', 'verify']); assert.strictEqual(key.algorithm, key.algorithm); assert.strictEqual(key.usages, key.usages); const raw = await subtle.exportKey('raw', key); assert.deepStrictEqual( Buffer.from(keyData).toString('hex'), Buffer.from(raw).toString('hex')); const jwk = await subtle.exportKey('jwk', key); assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']); assert(jwk.ext); assert.strictEqual(jwk.kty, 'oct'); assert.strictEqual(jwk.alg, 'HS256'); assert.deepStrictEqual( Buffer.from(jwk.k, 'base64').toString('hex'), Buffer.from(raw).toString('hex')); await subtle.importKey( 'jwk', jwk, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign', 'verify']); await subtle.importKey( 'jwk', { ...jwk, alg: undefined }, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign', 'verify']); await assert.rejects( subtle.importKey( 'jwk', { ...jwk, alg: 'HS384' }, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign', 'verify']), { name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' }); await assert.rejects( subtle.importKey( 'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, true, [/* empty usages */]), { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' }); } test().then(common.mustCall()); } // Import/Export KMAC Secret Key if (hasOpenSSL(3)) { async function test(name) { const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32)); const key = await subtle.importKey( 'raw-secret', keyData, name, true, ['sign', 'verify']); assert.strictEqual(key.algorithm, key.algorithm); assert.strictEqual(key.usages, key.usages); const raw = await subtle.exportKey('raw-secret', key); assert.deepStrictEqual( Buffer.from(keyData).toString('hex'), Buffer.from(raw).toString('hex')); const jwk = await subtle.exportKey('jwk', key); assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']); assert(jwk.ext); assert.strictEqual(jwk.kty, 'oct'); assert.strictEqual(jwk.alg, `K${name.substring(4)}`); assert.deepStrictEqual( Buffer.from(jwk.k, 'base64').toString('hex'), Buffer.from(raw).toString('hex')); await subtle.importKey( 'jwk', jwk, name, true, ['sign', 'verify']); await subtle.importKey( 'jwk', { ...jwk, alg: undefined }, name, true, ['sign', 'verify']); await assert.rejects( subtle.importKey( 'jwk', { ...jwk, alg: name === 'KMAC128' ? 'K256' : 'K128' }, name, true, ['sign', 'verify']), { name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' }); await assert.rejects( subtle.importKey( 'raw', keyData, name, true, ['sign', 'verify']), { name: 'NotSupportedError', message: `Unable to import ${name} using raw format` }); await assert.rejects( subtle.importKey( 'raw-secret', keyData, name, true, [/* empty usages */]), { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' }); } test('KMAC128').then(common.mustCall()); test('KMAC256').then(common.mustCall()); } // Import/Export AES Secret Key { async function test() { const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32)); const key = await subtle.importKey( 'raw', keyData, { name: 'AES-CTR', length: 256, }, true, ['encrypt', 'decrypt']); assert.strictEqual(key.algorithm, key.algorithm); assert.strictEqual(key.usages, key.usages); const raw = await subtle.exportKey('raw', key); assert.deepStrictEqual( Buffer.from(keyData).toString('hex'), Buffer.from(raw).toString('hex')); const jwk = await subtle.exportKey('jwk', key); assert.deepStrictEqual(jwk.key_ops, ['encrypt', 'decrypt']); assert(jwk.ext); assert.strictEqual(jwk.kty, 'oct'); assert.deepStrictEqual( Buffer.from(jwk.k, 'base64').toString('hex'), Buffer.from(raw).toString('hex')); await assert.rejects( subtle.importKey( 'raw', keyData, { name: 'AES-CTR', length: 256, }, true, [/* empty usages */]), { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' }); } test().then(common.mustCall()); } // Import/Export RSA Key Pairs { async function test() { const { publicKey, privateKey } = await subtle.generateKey({ name: 'RSA-PSS', modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-384' }, true, ['sign', 'verify']); const [ spki, pkcs8, publicJwk, privateJwk, ] = await Promise.all([ subtle.exportKey('spki', publicKey), subtle.exportKey('pkcs8', privateKey), subtle.exportKey('jwk', publicKey), subtle.exportKey('jwk', privateKey), ]); assert(spki); assert(pkcs8); assert(publicJwk); assert(privateJwk); const [ importedSpkiPublicKey, importedPkcs8PrivateKey, importedJwkPublicKey, importedJwkPrivateKey, ] = await Promise.all([ subtle.importKey('spki', spki, { name: 'RSA-PSS', hash: 'SHA-384', }, true, ['verify']), subtle.importKey('pkcs8', pkcs8, { name: 'RSA-PSS', hash: 'SHA-384', }, true, ['sign']), subtle.importKey('jwk', publicJwk, { name: 'RSA-PSS', hash: 'SHA-384', }, true, ['verify']), subtle.importKey('jwk', privateJwk, { name: 'RSA-PSS', hash: 'SHA-384', }, true, ['sign']), ]); assert(importedSpkiPublicKey); assert(importedPkcs8PrivateKey); assert(importedJwkPublicKey); assert(importedJwkPrivateKey); } test().then(common.mustCall()); } // Import/Export EC Key Pairs { async function test() { const { publicKey, privateKey } = await subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-384' }, true, ['sign', 'verify']); const [ spki, pkcs8, publicJwk, privateJwk, ] = await Promise.all([ subtle.exportKey('spki', publicKey), subtle.exportKey('pkcs8', privateKey), subtle.exportKey('jwk', publicKey), subtle.exportKey('jwk', privateKey), ]); assert(spki); assert(pkcs8); assert(publicJwk); assert(privateJwk); const [ importedSpkiPublicKey, importedPkcs8PrivateKey, importedJwkPublicKey, importedJwkPrivateKey, ] = await Promise.all([ subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-384' }, true, ['verify']), subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-384' }, true, ['sign']), subtle.importKey('jwk', publicJwk, { name: 'ECDSA', namedCurve: 'P-384' }, true, ['verify']), subtle.importKey('jwk', privateJwk, { name: 'ECDSA', namedCurve: 'P-384' }, true, ['sign']), ]); assert(importedSpkiPublicKey); assert(importedPkcs8PrivateKey); assert(importedJwkPublicKey); assert(importedJwkPrivateKey); } test().then(common.mustCall()); } // SHA-3 hashes and JWK "alg" if (!process.features.openssl_is_boringssl) { const rsa = fixtures.readKey('rsa_private_2048.pem'); const privateKey = createPrivateKey(rsa); const publicKey = createPublicKey(privateKey); async function test(keyObject, algorithm, usages) { const key = keyObject.toCryptoKey(algorithm, true, usages); const jwk = await subtle.exportKey('jwk', key); assert.strictEqual(jwk.alg, undefined); } for (const hash of ['SHA3-256', 'SHA3-384', 'SHA3-512']) { for (const name of ['RSA-OAEP', 'RSA-PSS', 'RSASSA-PKCS1-v1_5']) { test(publicKey, { name, hash }, []).then(common.mustCall()); test(privateKey, { name, hash }, [name === 'RSA-OAEP' ? 'unwrapKey' : 'sign']).then(common.mustCall()); } test(createSecretKey(Buffer.alloc(32)), { name: 'HMAC', hash }, ['sign']); } { const jwk = createSecretKey(Buffer.alloc(16)).export({ format: 'jwk' }); // This is rejected for SHA-2 but ignored for SHA-3 // Otherwise, if the name attribute of hash is defined in another applicable specification: // Perform any key import steps defined by other applicable specifications, passing format, // jwk and hash and obtaining hash. jwk.alg = 'HS3-256'; assert.rejects(subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']), { name: 'DataError', message: 'JWK "alg" does not match the requested algorithm', }).then(common.mustCall()); subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA3-256' }, false, ['sign', 'verify']).then(common.mustCall()); } }