| 'use strict'; |
| |
| const common = require('../common'); |
| if (!common.hasCrypto) |
| common.skip('missing crypto'); |
| |
| const { hasOpenSSL } = require('../common/crypto'); |
| |
| const assert = require('assert'); |
| const { |
| createSecretKey, |
| KeyObject, |
| randomBytes, |
| generateKeyPairSync, |
| } = require('crypto'); |
| |
| function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { |
| assert.strictEqual(cryptoKey instanceof CryptoKey, true); |
| assert.strictEqual(cryptoKey.type, keyObject.type); |
| assert.strictEqual(cryptoKey.algorithm.name, algorithm); |
| assert.strictEqual(cryptoKey.extractable, extractable); |
| assert.deepStrictEqual(cryptoKey.usages, usages); |
| assert.strictEqual(keyObject.equals(KeyObject.from(cryptoKey)), true); |
| } |
| |
| { |
| for (const length of [128, 192, 256]) { |
| const key = createSecretKey(randomBytes(length >> 3)); |
| const algorithms = ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']; |
| if (length === 256) |
| algorithms.push('ChaCha20-Poly1305'); |
| for (const algorithm of algorithms) { |
| const usages = algorithm === 'AES-KW' ? ['wrapKey', 'unwrapKey'] : ['encrypt', 'decrypt']; |
| for (const extractable of [true, false]) { |
| const cryptoKey = key.toCryptoKey(algorithm, extractable, usages); |
| assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); |
| assert.strictEqual(cryptoKey.algorithm.length, algorithm !== 'ChaCha20-Poly1305' ? length : undefined); |
| } |
| } |
| } |
| } |
| |
| { |
| const pbkdf2 = createSecretKey(randomBytes(16)); |
| const algorithm = 'PBKDF2'; |
| const usages = ['deriveBits']; |
| assert.throws(() => pbkdf2.toCryptoKey(algorithm, true, usages), { |
| name: 'SyntaxError', |
| message: 'PBKDF2 keys are not extractable' |
| }); |
| assert.throws(() => pbkdf2.toCryptoKey(algorithm, false, ['wrapKey']), { |
| name: 'SyntaxError', |
| message: 'Unsupported key usage for a PBKDF2 key' |
| }); |
| const cryptoKey = pbkdf2.toCryptoKey(algorithm, false, usages); |
| assertCryptoKey(cryptoKey, pbkdf2, algorithm, false, usages); |
| assert.strictEqual(cryptoKey.algorithm.length, undefined); |
| } |
| |
| { |
| for (const length of [128, 192, 256]) { |
| const hmac = createSecretKey(randomBytes(length >> 3)); |
| const algorithm = 'HMAC'; |
| const usages = ['sign', 'verify']; |
| |
| assert.throws(() => { |
| createSecretKey(Buffer.alloc(0)).toCryptoKey({ name: algorithm, hash: 'SHA-256' }, true, usages); |
| }, { |
| name: 'DataError', |
| message: 'Zero-length key is not supported', |
| }); |
| |
| assert.throws(() => { |
| hmac.toCryptoKey({ |
| name: algorithm, |
| hash: 'SHA-256', |
| }, true, []); |
| }, { |
| name: 'SyntaxError', |
| message: 'Usages cannot be empty when importing a secret key.' |
| }); |
| |
| for (const hash of ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']) { |
| for (const extractable of [true, false]) { |
| assert.throws(() => { |
| hmac.toCryptoKey({ name: algorithm, hash: 'SHA-256', length: 0 }, true, usages); |
| }, { |
| name: 'DataError', |
| message: 'HmacImportParams.length cannot be 0', |
| }); |
| const cryptoKey = hmac.toCryptoKey({ name: algorithm, hash }, extractable, usages); |
| assertCryptoKey(cryptoKey, hmac, algorithm, extractable, usages); |
| assert.strictEqual(cryptoKey.algorithm.length, length); |
| } |
| } |
| } |
| } |
| |
| { |
| for (const algorithm of ['Ed25519', 'Ed448', 'X25519', 'X448']) { |
| const { publicKey, privateKey } = generateKeyPairSync(algorithm.toLowerCase()); |
| assert.throws(() => { |
| publicKey.toCryptoKey(algorithm === 'Ed25519' ? 'X25519' : 'Ed25519', true, []); |
| }, { |
| name: 'DataError', |
| message: 'Invalid key type' |
| }); |
| for (const key of [publicKey, privateKey]) { |
| let usages; |
| if (algorithm.startsWith('E')) { |
| usages = key.type === 'public' ? ['verify'] : ['sign']; |
| } else { |
| usages = key.type === 'public' ? [] : ['deriveBits']; |
| } |
| for (const extractable of [true, false]) { |
| const cryptoKey = key.toCryptoKey(algorithm, extractable, usages); |
| assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); |
| } |
| } |
| } |
| } |
| |
| { |
| const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); |
| for (const key of [publicKey, privateKey]) { |
| for (const algorithm of ['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP']) { |
| let usages; |
| if (algorithm === 'RSA-OAEP') { |
| usages = key.type === 'public' ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey']; |
| } else { |
| usages = key.type === 'public' ? ['verify'] : ['sign']; |
| } |
| for (const extractable of [true, false]) { |
| for (const hash of ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']) { |
| const cryptoKey = key.toCryptoKey({ |
| name: algorithm, |
| hash |
| }, extractable, usages); |
| assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); |
| assert.strictEqual(cryptoKey.algorithm.hash.name, hash); |
| } |
| } |
| } |
| } |
| } |
| |
| { |
| for (const namedCurve of ['P-256', 'P-384', 'P-521']) { |
| const { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve }); |
| assert.throws(() => { |
| privateKey.toCryptoKey({ |
| name: 'ECDH', |
| namedCurve, |
| }, true, []); |
| }, { |
| name: 'SyntaxError', |
| message: 'Usages cannot be empty when importing a private key.' |
| }); |
| assert.throws(() => { |
| publicKey.toCryptoKey({ |
| name: 'ECDH', |
| namedCurve: namedCurve === 'P-256' ? 'P-384' : 'P-256' |
| }, true, []); |
| }, { |
| name: 'DataError', |
| message: 'Named curve mismatch' |
| }); |
| for (const key of [publicKey, privateKey]) { |
| for (const algorithm of ['ECDH', 'ECDSA']) { |
| let usages; |
| if (algorithm === 'ECDH') { |
| usages = key.type === 'public' ? [] : ['deriveBits']; |
| } else { |
| usages = key.type === 'public' ? ['verify'] : ['sign']; |
| } |
| for (const extractable of [true, false]) { |
| const cryptoKey = key.toCryptoKey({ |
| name: algorithm, |
| namedCurve |
| }, extractable, usages); |
| assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); |
| assert.strictEqual(cryptoKey.algorithm.namedCurve, namedCurve); |
| } |
| } |
| } |
| } |
| } |
| |
| if (hasOpenSSL(3, 5)) { |
| for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { |
| const { publicKey, privateKey } = generateKeyPairSync(name.toLowerCase()); |
| assert.throws(() => { |
| privateKey.toCryptoKey(name, true, []); |
| }, { |
| name: 'SyntaxError', |
| message: 'Usages cannot be empty when importing a private key.' |
| }); |
| for (const key of [publicKey, privateKey]) { |
| const usages = key.type === 'public' ? ['verify'] : ['sign']; |
| for (const extractable of [true, false]) { |
| const cryptoKey = key.toCryptoKey({ name }, extractable, usages); |
| assertCryptoKey(cryptoKey, key, name, extractable, usages); |
| assert.strictEqual(cryptoKey.algorithm.name, name); |
| } |
| } |
| } |
| } |
| |
| if (hasOpenSSL(3)) { |
| for (const algorithm of ['KMAC128', 'KMAC256']) { |
| const hmac = createSecretKey(randomBytes(32)); |
| const usages = ['sign', 'verify']; |
| |
| assert.throws(() => { |
| createSecretKey(Buffer.alloc(0)).toCryptoKey({ name: algorithm }, true, usages); |
| }, { |
| name: 'DataError', |
| message: 'Zero-length key is not supported', |
| }); |
| |
| assert.throws(() => { |
| hmac.toCryptoKey({ |
| name: algorithm, |
| }, true, []); |
| }, { |
| name: 'SyntaxError', |
| message: 'Usages cannot be empty when importing a secret key.' |
| }); |
| |
| for (const extractable of [true, false]) { |
| assert.throws(() => { |
| hmac.toCryptoKey({ name: algorithm, length: 0 }, true, usages); |
| }, { |
| name: 'DataError', |
| message: 'KmacImportParams.length cannot be 0', |
| }); |
| const cryptoKey = hmac.toCryptoKey({ name: algorithm }, extractable, usages); |
| assertCryptoKey(cryptoKey, hmac, algorithm, extractable, usages); |
| assert.strictEqual(cryptoKey.algorithm.length, 256); |
| } |
| } |
| } |