diff --git a/test/common/crypto.js b/test/common/crypto.js index 8919b54d1fc632..ba47285df49a43 100644 --- a/test/common/crypto.js +++ b/test/common/crypto.js @@ -6,6 +6,14 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { + createSign, + createVerify, + publicEncrypt, + privateDecrypt, + sign, + verify, +} = crypto; // The values below (modp2/modp2buf) are for a 1024 bits long prime from // RFC 2412 E.2, see https://tools.ietf.org/html/rfc2412. */ @@ -42,7 +50,83 @@ function testDH({ publicKey: alicePublicKey, privateKey: alicePrivateKey }, assert.deepStrictEqual(buf1, expectedValue); } +// Asserts that the size of the given key (in chars or bytes) is within 10% of +// the expected size. +function assertApproximateSize(key, expectedSize) { + const u = typeof key === 'string' ? 'chars' : 'bytes'; + const min = Math.floor(0.9 * expectedSize); + const max = Math.ceil(1.1 * expectedSize); + assert(key.length >= min, + `Key (${key.length} ${u}) is shorter than expected (${min} ${u})`); + assert(key.length <= max, + `Key (${key.length} ${u}) is longer than expected (${max} ${u})`); +} + +// Tests that a key pair can be used for encryption / decryption. +function testEncryptDecrypt(publicKey, privateKey) { + const message = 'Hello Node.js world!'; + const plaintext = Buffer.from(message, 'utf8'); + for (const key of [publicKey, privateKey]) { + const ciphertext = publicEncrypt(key, plaintext); + const received = privateDecrypt(privateKey, ciphertext); + assert.strictEqual(received.toString('utf8'), message); + } +} + +// Tests that a key pair can be used for signing / verification. +function testSignVerify(publicKey, privateKey) { + const message = Buffer.from('Hello Node.js world!'); + + function oldSign(algo, data, key) { + return createSign(algo).update(data).sign(key); + } + + function oldVerify(algo, data, key, signature) { + return createVerify(algo).update(data).verify(key, signature); + } + + for (const signFn of [sign, oldSign]) { + const signature = signFn('SHA256', message, privateKey); + for (const verifyFn of [verify, oldVerify]) { + for (const key of [publicKey, privateKey]) { + const okay = verifyFn('SHA256', message, key, signature); + assert(okay); + } + } + } +} + +// Constructs a regular expression for a PEM-encoded key with the given label. +function getRegExpForPEM(label, cipher) { + const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`; + const rfc1421Header = cipher == null ? '' : + `\nProc-Type: 4,ENCRYPTED\nDEK-Info: ${cipher},[^\n]+\n`; + const body = '([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}'; + const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`; + return new RegExp(`^${head}${rfc1421Header}\n${body}\n${end}\n$`); +} + +const pkcs1PubExp = getRegExpForPEM('RSA PUBLIC KEY'); +const pkcs1PrivExp = getRegExpForPEM('RSA PRIVATE KEY'); +const pkcs1EncExp = (cipher) => getRegExpForPEM('RSA PRIVATE KEY', cipher); +const spkiExp = getRegExpForPEM('PUBLIC KEY'); +const pkcs8Exp = getRegExpForPEM('PRIVATE KEY'); +const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY'); +const sec1Exp = getRegExpForPEM('EC PRIVATE KEY'); +const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); + module.exports = { modp2buf, testDH, + assertApproximateSize, + testEncryptDecrypt, + testSignVerify, + pkcs1PubExp, + pkcs1PrivExp, + pkcs1EncExp, // used once + spkiExp, + pkcs8Exp, // used once + pkcs8EncExp, // used once + sec1Exp, + sec1EncExp, }; diff --git a/test/parallel/test-crypto-keygen-async-dsa-key-object.js b/test/parallel/test-crypto-keygen-async-dsa-key-object.js new file mode 100644 index 00000000000000..c15807295541e2 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-dsa-key-object.js @@ -0,0 +1,32 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Test async DSA key object generation. +{ + generateKeyPair('dsa', { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'dsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'dsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-dsa.js b/test/parallel/test-crypto-keygen-async-dsa.js new file mode 100644 index 00000000000000..048c0ce6fb92ef --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-dsa.js @@ -0,0 +1,64 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + assertApproximateSize, + testSignVerify, + spkiExp, +} = require('../common/crypto'); + +// Test async DSA key generation. +{ + const privateKeyEncoding = { + type: 'pkcs8', + format: 'der' + }; + + generateKeyPair('dsa', { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + cipher: 'aes-128-cbc', + passphrase: 'secret', + ...privateKeyEncoding + } + }, common.mustSucceed((publicKey, privateKeyDER) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + // The private key is DER-encoded. + assert(Buffer.isBuffer(privateKeyDER)); + + assertApproximateSize(publicKey, common.hasOpenSSL3 ? 1194 : 440); + assertApproximateSize(privateKeyDER, common.hasOpenSSL3 ? 721 : 336); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => { + return testSignVerify(publicKey, { + key: privateKeyDER, + ...privateKeyEncoding + }); + }, { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + // Signing should work with the correct password. + testSignVerify(publicKey, { + key: privateKeyDER, + ...privateKeyEncoding, + passphrase: 'secret' + }); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js new file mode 100644 index 00000000000000..a716f26dfe0275 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js @@ -0,0 +1,100 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Test async elliptic curve key generation with 'jwk' encoding +{ + [ + ['ec', ['P-384', 'P-256', 'P-521', 'secp256k1']], + ['rsa'], + ['ed25519'], + ['ed448'], + ['x25519'], + ['x448'], + ].forEach((types) => { + const [type, options] = types; + switch (type) { + case 'ec': { + return options.forEach((curve) => { + generateKeyPair(type, { + namedCurve: curve, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.x, privateKey.x); + assert.strictEqual(publicKey.y, privateKey.y); + assert(!publicKey.d); + assert(privateKey.d); + assert.strictEqual(publicKey.kty, 'EC'); + assert.strictEqual(publicKey.kty, privateKey.kty); + assert.strictEqual(publicKey.crv, curve); + assert.strictEqual(publicKey.crv, privateKey.crv); + })); + }); + } + case 'rsa': { + return generateKeyPair(type, { + modulusLength: 4096, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.kty, 'RSA'); + assert.strictEqual(publicKey.kty, privateKey.kty); + assert.strictEqual(typeof publicKey.n, 'string'); + assert.strictEqual(publicKey.n, privateKey.n); + assert.strictEqual(typeof publicKey.e, 'string'); + assert.strictEqual(publicKey.e, privateKey.e); + assert.strictEqual(typeof privateKey.d, 'string'); + assert.strictEqual(typeof privateKey.p, 'string'); + assert.strictEqual(typeof privateKey.q, 'string'); + assert.strictEqual(typeof privateKey.dp, 'string'); + assert.strictEqual(typeof privateKey.dq, 'string'); + assert.strictEqual(typeof privateKey.qi, 'string'); + })); + } + case 'ed25519': + case 'ed448': + case 'x25519': + case 'x448': { + generateKeyPair(type, { + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.x, privateKey.x); + assert(!publicKey.d); + assert(privateKey.d); + assert.strictEqual(publicKey.kty, 'OKP'); + assert.strictEqual(publicKey.kty, privateKey.kty); + const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`; + assert.strictEqual(publicKey.crv, expectedCrv); + assert.strictEqual(publicKey.crv, privateKey.crv); + })); + } + } + }); +} diff --git a/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js b/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js new file mode 100644 index 00000000000000..3203dfe16eb690 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js @@ -0,0 +1,50 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + assertApproximateSize, + testEncryptDecrypt, + testSignVerify, +} = require('../common/crypto'); + +// Test async RSA key generation with an encrypted private key, but encoded as DER. +{ + generateKeyPair('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding: { + type: 'pkcs1', + format: 'der' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'der' + } + }, common.mustSucceed((publicKeyDER, privateKeyDER) => { + assert(Buffer.isBuffer(publicKeyDER)); + assertApproximateSize(publicKeyDER, 74); + + assert(Buffer.isBuffer(privateKeyDER)); + + const publicKey = { + key: publicKeyDER, + type: 'pkcs1', + format: 'der', + }; + const privateKey = { + key: privateKeyDER, + format: 'der', + type: 'pkcs8', + passphrase: 'secret' + }; + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-encrypted-private-key.js b/test/parallel/test-crypto-keygen-async-encrypted-private-key.js new file mode 100644 index 00000000000000..727cccc6f3eff5 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-encrypted-private-key.js @@ -0,0 +1,67 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + assertApproximateSize, + testEncryptDecrypt, + testSignVerify, +} = require('../common/crypto'); + +// Test async RSA key generation with an encrypted private key, but encoded as DER. +{ + generateKeyPair('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding: { + type: 'pkcs1', + format: 'der' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'der', + cipher: 'aes-256-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKeyDER, privateKeyDER) => { + assert(Buffer.isBuffer(publicKeyDER)); + assertApproximateSize(publicKeyDER, 74); + + assert(Buffer.isBuffer(privateKeyDER)); + + // Since the private key is encrypted, signing shouldn't work anymore. + const publicKey = { + key: publicKeyDER, + type: 'pkcs1', + format: 'der', + }; + assert.throws(() => { + testSignVerify(publicKey, { + key: privateKeyDER, + format: 'der', + type: 'pkcs8' + }); + }, { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + // Signing should work with the correct password. + + const privateKey = { + key: privateKeyDER, + format: 'der', + type: 'pkcs8', + passphrase: 'secret' + }; + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js new file mode 100644 index 00000000000000..553674774571d3 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js @@ -0,0 +1,55 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + testSignVerify, + spkiExp, + pkcs8EncExp, +} = require('../common/crypto'); + +// Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted +// private key with paramEncoding explicit. +{ + generateKeyPair('ec', { + namedCurve: 'P-256', + paramEncoding: 'explicit', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'top secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs8EncExp); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { + key: privateKey, + passphrase: 'top secret' + }); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js new file mode 100644 index 00000000000000..79a132eed0b854 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js @@ -0,0 +1,52 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + testSignVerify, + spkiExp, + sec1EncExp, +} = require('../common/crypto'); + +{ + // Test async explicit elliptic curve key generation with an encrypted + // private key. + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'explicit', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1EncExp('AES-128-CBC')); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' }); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js new file mode 100644 index 00000000000000..46223f08d7445a --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js @@ -0,0 +1,39 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + testSignVerify, + spkiExp, + sec1Exp, +} = require('../common/crypto'); + +// Test async explicit elliptic curve key generation, e.g. for ECDSA, +// with a SEC1 private key with paramEncoding explicit. +{ + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'explicit', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1Exp); + + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted-p256.js b/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted-p256.js new file mode 100644 index 00000000000000..5e7d1a6c9b6611 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted-p256.js @@ -0,0 +1,55 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + testSignVerify, + spkiExp, + pkcs8EncExp, +} = require('../common/crypto'); + +// Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted +// private key. +{ + generateKeyPair('ec', { + namedCurve: 'P-256', + paramEncoding: 'named', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'top secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs8EncExp); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { + key: privateKey, + passphrase: 'top secret' + }); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js b/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js new file mode 100644 index 00000000000000..1cc93d0a794931 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js @@ -0,0 +1,52 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + testSignVerify, + spkiExp, + sec1EncExp, +} = require('../common/crypto'); + +{ + // Test async named elliptic curve key generation with an encrypted + // private key. + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'named', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1EncExp('AES-128-CBC')); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' }); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-named-elliptic-curve.js b/test/parallel/test-crypto-keygen-async-named-elliptic-curve.js new file mode 100644 index 00000000000000..a1dfdbce1f2360 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-named-elliptic-curve.js @@ -0,0 +1,39 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + testSignVerify, + spkiExp, + sec1Exp, +} = require('../common/crypto'); + +// Test async named elliptic curve key generation, e.g. for ECDSA, +// with a SEC1 private key. +{ + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'named', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1Exp); + + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/parallel/test-crypto-keygen-async-rsa.js b/test/parallel/test-crypto-keygen-async-rsa.js new file mode 100644 index 00000000000000..f4a83809dc73c7 --- /dev/null +++ b/test/parallel/test-crypto-keygen-async-rsa.js @@ -0,0 +1,61 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + assertApproximateSize, + testEncryptDecrypt, + testSignVerify, + pkcs1EncExp, +} = require('../common/crypto'); + +// Test async RSA key generation with an encrypted private key. +{ + generateKeyPair('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding: { + type: 'pkcs1', + format: 'der' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKeyDER, privateKey) => { + assert(Buffer.isBuffer(publicKeyDER)); + assertApproximateSize(publicKeyDER, 74); + + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs1EncExp('AES-256-CBC')); + + // Since the private key is encrypted, signing shouldn't work anymore. + const publicKey = { + key: publicKeyDER, + type: 'pkcs1', + format: 'der', + }; + const expectedError = common.hasOpenSSL3 ? { + name: 'Error', + message: 'error:07880109:common libcrypto routines::interrupted or ' + + 'cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }; + assert.throws(() => testSignVerify(publicKey, privateKey), expectedError); + + const key = { key: privateKey, passphrase: 'secret' }; + testEncryptDecrypt(publicKey, key); + testSignVerify(publicKey, key); + })); +} diff --git a/test/parallel/test-crypto-keygen-bit-length.js b/test/parallel/test-crypto-keygen-bit-length.js new file mode 100644 index 00000000000000..08772ba2e496b8 --- /dev/null +++ b/test/parallel/test-crypto-keygen-bit-length.js @@ -0,0 +1,39 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// This tests check that generateKeyPair returns correct bit length in +// KeyObject's asymmetricKeyDetails. +// https://github.com/nodejs/node/issues/46102#issuecomment-1372153541 +{ + generateKeyPair('rsa', { + modulusLength: 513, + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513); + assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513); + })); + + generateKeyPair('rsa-pss', { + modulusLength: 513, + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513); + assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513); + })); + + if (common.hasOpenSSL3) { + generateKeyPair('dsa', { + modulusLength: 2049, + divisorLength: 256, + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 2049); + assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2049); + })); + } +} diff --git a/test/parallel/test-crypto-keygen-dh-classic.js b/test/parallel/test-crypto-keygen-dh-classic.js new file mode 100644 index 00000000000000..bed748ca6dd4fa --- /dev/null +++ b/test/parallel/test-crypto-keygen-dh-classic.js @@ -0,0 +1,23 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Test classic Diffie-Hellman key generation. +{ + generateKeyPair('dh', { + primeLength: 1024 + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'dh'); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'dh'); + })); +} diff --git a/test/parallel/test-crypto-keygen-duplicate-deprecated-option.js b/test/parallel/test-crypto-keygen-duplicate-deprecated-option.js new file mode 100644 index 00000000000000..854ad6e35efcd3 --- /dev/null +++ b/test/parallel/test-crypto-keygen-duplicate-deprecated-option.js @@ -0,0 +1,43 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// This test makes sure deprecated and new options may be used +// simultaneously so long as they're identical values. +{ + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + hashAlgorithm: 'sha256', + mgf1Hash: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + })); +} diff --git a/test/parallel/test-crypto-keygen-eddsa.js b/test/parallel/test-crypto-keygen-eddsa.js new file mode 100644 index 00000000000000..5a097c2524f3ea --- /dev/null +++ b/test/parallel/test-crypto-keygen-eddsa.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Test EdDSA key generation. +{ + if (!/^1\.1\.0/.test(process.versions.openssl)) { + ['ed25519', 'ed448', 'x25519', 'x448'].forEach((keyType) => { + generateKeyPair(keyType, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, keyType); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {}); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, keyType); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {}); + })); + }); + } +} diff --git a/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js b/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js new file mode 100644 index 00000000000000..94cfda91bd32b9 --- /dev/null +++ b/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js @@ -0,0 +1,29 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Passing an empty passphrase string should not throw ERR_OSSL_CRYPTO_MALLOC_FAILURE even on OpenSSL 3. +// Regression test for https://github.com/nodejs/node/issues/41428. +generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: '' + } +}, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.strictEqual(typeof privateKey, 'string'); +})); diff --git a/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js b/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js new file mode 100644 index 00000000000000..7679a492c3194c --- /dev/null +++ b/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js @@ -0,0 +1,53 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + createPrivateKey, + generateKeyPair, +} = require('crypto'); +const { + testSignVerify, +} = require('../common/crypto'); + +// Passing an empty passphrase string should not cause OpenSSL's default +// passphrase prompt in the terminal. +// See https://github.com/nodejs/node/issues/35898. +for (const type of ['pkcs1', 'pkcs8']) { + generateKeyPair('rsa', { + modulusLength: 1024, + privateKeyEncoding: { + type, + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: '' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + + for (const passphrase of ['', Buffer.alloc(0)]) { + const privateKeyObject = createPrivateKey({ + passphrase, + key: privateKey + }); + assert.strictEqual(privateKeyObject.asymmetricKeyType, 'rsa'); + } + + // Encrypting with an empty passphrase is not the same as not encrypting + // the key, and not specifying a passphrase should fail when decoding it. + assert.throws(() => { + return testSignVerify(publicKey, privateKey); + }, common.hasOpenSSL3 ? { + name: 'Error', + code: 'ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED', + message: 'error:07880109:common libcrypto routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + })); +} diff --git a/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js b/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js new file mode 100644 index 00000000000000..fc70981ecb150b --- /dev/null +++ b/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js @@ -0,0 +1,28 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generateKeyPairSync, +} = require('crypto'); + +// Test invalid parameter encoding. +{ + assert.throws(() => generateKeyPairSync('dsa', { + modulusLength: 4096, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }), { + name: 'Error', + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', + message: 'Unsupported JWK Key Type.' + }); +} diff --git a/test/parallel/test-crypto-keygen-invalid-parameter-encoding-ec.js b/test/parallel/test-crypto-keygen-invalid-parameter-encoding-ec.js new file mode 100644 index 00000000000000..b4adb58d0fab5a --- /dev/null +++ b/test/parallel/test-crypto-keygen-invalid-parameter-encoding-ec.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generateKeyPairSync, +} = require('crypto'); + +{ + assert.throws(() => generateKeyPairSync('ec', { + namedCurve: 'secp224r1', + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }), { + name: 'Error', + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', + message: 'Unsupported JWK EC curve: secp224r1.' + }); +} diff --git a/test/parallel/test-crypto-keygen-key-object-without-encoding.js b/test/parallel/test-crypto-keygen-key-object-without-encoding.js new file mode 100644 index 00000000000000..abcd282871b638 --- /dev/null +++ b/test/parallel/test-crypto-keygen-key-object-without-encoding.js @@ -0,0 +1,55 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + testEncryptDecrypt, + testSignVerify, +} = require('../common/crypto'); + +// Tests key objects are returned when key encodings are not specified. +{ + // If no publicKeyEncoding is specified, a key object should be returned. + generateKeyPair('rsa', { + modulusLength: 1024, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + + // The private key should still be a string. + assert.strictEqual(typeof privateKey, 'string'); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); + + // If no privateKeyEncoding is specified, a key object should be returned. + generateKeyPair('rsa', { + modulusLength: 1024, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + // The public key should still be a string. + assert.strictEqual(typeof publicKey, 'string'); + + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/parallel/test-crypto-keygen-key-objects.js b/test/parallel/test-crypto-keygen-key-objects.js new file mode 100644 index 00000000000000..a0f1bdf2bcb5ed --- /dev/null +++ b/test/parallel/test-crypto-keygen-key-objects.js @@ -0,0 +1,33 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPairSync, +} = require('crypto'); + +// Test sync key generation with key objects. +{ + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + modulusLength: 512 + }); + + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n + }); + + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n + }); +} diff --git a/test/parallel/test-crypto-keygen-missing-oid.js b/test/parallel/test-crypto-keygen-missing-oid.js new file mode 100644 index 00000000000000..f7fefe13848d4b --- /dev/null +++ b/test/parallel/test-crypto-keygen-missing-oid.js @@ -0,0 +1,43 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, + generateKeyPairSync, + getCurves, +} = require('crypto'); + +// This test creates EC key pairs on curves without associated OIDs. +// Specifying a key encoding should not crash. +{ + if (process.versions.openssl >= '1.1.1i') { + for (const namedCurve of ['Oakley-EC2N-3', 'Oakley-EC2N-4']) { + if (!getCurves().includes(namedCurve)) + continue; + + const expectedErrorCode = + common.hasOpenSSL3 ? 'ERR_OSSL_MISSING_OID' : 'ERR_OSSL_EC_MISSING_OID'; + const params = { + namedCurve, + publicKeyEncoding: { + format: 'der', + type: 'spki' + } + }; + + assert.throws(() => { + generateKeyPairSync('ec', params); + }, { + code: expectedErrorCode + }); + + generateKeyPair('ec', params, common.mustCall((err) => { + assert.strictEqual(err.code, expectedErrorCode); + })); + } + } +} diff --git a/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js b/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js new file mode 100644 index 00000000000000..97dafe1be3cbd0 --- /dev/null +++ b/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js @@ -0,0 +1,32 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// 'rsa-pss' should not add a RSASSA-PSS-params sequence by default. +// Regression test for: https://github.com/nodejs/node/issues/39936 +{ + generateKeyPair('rsa-pss', { + modulusLength: 512 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + + // To allow backporting the fix to versions that do not support + // asymmetricKeyDetails for RSA-PSS params, also verify that the exported + // AlgorithmIdentifier member of the SubjectPublicKeyInfo has the expected + // length of 11 bytes (as opposed to > 11 bytes if node added params). + const spki = publicKey.export({ format: 'der', type: 'spki' }); + assert.strictEqual(spki[3], 11, spki.toString('hex')); + })); +} diff --git a/test/parallel/test-crypto-keygen-non-standard-public-exponent.js b/test/parallel/test-crypto-keygen-non-standard-public-exponent.js new file mode 100644 index 00000000000000..f54a9e8a6d9b5c --- /dev/null +++ b/test/parallel/test-crypto-keygen-non-standard-public-exponent.js @@ -0,0 +1,35 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPairSync, +} = require('crypto'); + +// Test sync key generation with key objects with a non-standard +// publicExponent +{ + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + publicExponent: 3, + modulusLength: 512 + }); + + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 3n + }); + + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 3n + }); +} diff --git a/test/parallel/test-crypto-keygen-promisify.js b/test/parallel/test-crypto-keygen-promisify.js new file mode 100644 index 00000000000000..cd6ca7d6e3e65c --- /dev/null +++ b/test/parallel/test-crypto-keygen-promisify.js @@ -0,0 +1,46 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + assertApproximateSize, + testEncryptDecrypt, + testSignVerify, + pkcs1PubExp, + pkcs1PrivExp, +} = require('../common/crypto'); +const { promisify } = require('util'); + +// Test the util.promisified API with async RSA key generation. +{ + promisify(generateKeyPair)('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }).then(common.mustCall((keys) => { + const { publicKey, privateKey } = keys; + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, pkcs1PubExp); + assertApproximateSize(publicKey, 180); + + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs1PrivExp); + assertApproximateSize(privateKey, 512); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/parallel/test-crypto-keygen-rfc8017-9-1.js b/test/parallel/test-crypto-keygen-rfc8017-9-1.js new file mode 100644 index 00000000000000..7198be1c41343b --- /dev/null +++ b/test/parallel/test-crypto-keygen-rfc8017-9-1.js @@ -0,0 +1,32 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// RFC 8017, 9.1.: "Assuming that the mask generation function is based on a +// hash function, it is RECOMMENDED that the hash function be the same as the +// one that is applied to the message." +{ + + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha256', + saltLength: 16 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); +} diff --git a/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js b/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js new file mode 100644 index 00000000000000..f87dcf749bf6d0 --- /dev/null +++ b/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js @@ -0,0 +1,46 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// RFC 8017, A.2.3.: "For a given hashAlgorithm, the default value of +// saltLength is the octet length of the hash value." +{ + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha512' + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha512', + mgf1HashAlgorithm: 'sha512', + saltLength: 64 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); + + // It is still possible to explicitly set saltLength to 0. + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha512', + saltLength: 0 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha512', + mgf1HashAlgorithm: 'sha512', + saltLength: 0 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); +} diff --git a/test/parallel/test-crypto-keygen-rsa-pss.js b/test/parallel/test-crypto-keygen-rsa-pss.js new file mode 100644 index 00000000000000..41ebec97a5d2dd --- /dev/null +++ b/test/parallel/test-crypto-keygen-rsa-pss.js @@ -0,0 +1,64 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + constants, + generateKeyPair, +} = require('crypto'); +const { + testEncryptDecrypt, + testSignVerify, +} = require('../common/crypto'); + +// Test RSA-PSS. +{ + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + // Unlike RSA, RSA-PSS does not allow encryption. + assert.throws(() => { + testEncryptDecrypt(publicKey, privateKey); + }, /operation not supported for this keytype/); + + // RSA-PSS also does not permit signing with PKCS1 padding. + assert.throws(() => { + testSignVerify({ + key: publicKey, + padding: constants.RSA_PKCS1_PADDING + }, { + key: privateKey, + padding: constants.RSA_PKCS1_PADDING + }); + }, /illegal or unsupported padding mode/); + + // The padding should correctly default to RSA_PKCS1_PSS_PADDING now. + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/parallel/test-crypto-keygen-sync.js b/test/parallel/test-crypto-keygen-sync.js new file mode 100644 index 00000000000000..a100379e21f1b7 --- /dev/null +++ b/test/parallel/test-crypto-keygen-sync.js @@ -0,0 +1,47 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPairSync, +} = require('crypto'); +const { + assertApproximateSize, + testEncryptDecrypt, + testSignVerify, + pkcs1PubExp, + pkcs8Exp, +} = require('../common/crypto'); + +// To make the test faster, we will only test sync key generation once and +// with a relatively small key. +{ + const ret = generateKeyPairSync('rsa', { + publicExponent: 3, + modulusLength: 512, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }); + + assert.strictEqual(Object.keys(ret).length, 2); + const { publicKey, privateKey } = ret; + + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, pkcs1PubExp); + assertApproximateSize(publicKey, 162); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs8Exp); + assertApproximateSize(privateKey, 512); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); +} diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js index df8c5d93a90342..b09ca9e7c531ea 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -1,782 +1,19 @@ 'use strict'; +// This tests early errors for invalid encodings. + const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); + const { - constants, - createPrivateKey, - createSign, - createVerify, generateKeyPair, generateKeyPairSync, - getCurves, - publicEncrypt, - privateDecrypt, - sign, - verify } = require('crypto'); -const { inspect, promisify } = require('util'); - -// Asserts that the size of the given key (in chars or bytes) is within 10% of -// the expected size. -function assertApproximateSize(key, expectedSize) { - const u = typeof key === 'string' ? 'chars' : 'bytes'; - const min = Math.floor(0.9 * expectedSize); - const max = Math.ceil(1.1 * expectedSize); - assert(key.length >= min, - `Key (${key.length} ${u}) is shorter than expected (${min} ${u})`); - assert(key.length <= max, - `Key (${key.length} ${u}) is longer than expected (${max} ${u})`); -} - -// Tests that a key pair can be used for encryption / decryption. -function testEncryptDecrypt(publicKey, privateKey) { - const message = 'Hello Node.js world!'; - const plaintext = Buffer.from(message, 'utf8'); - for (const key of [publicKey, privateKey]) { - const ciphertext = publicEncrypt(key, plaintext); - const received = privateDecrypt(privateKey, ciphertext); - assert.strictEqual(received.toString('utf8'), message); - } -} - -// Tests that a key pair can be used for signing / verification. -function testSignVerify(publicKey, privateKey) { - const message = Buffer.from('Hello Node.js world!'); - - function oldSign(algo, data, key) { - return createSign(algo).update(data).sign(key); - } - - function oldVerify(algo, data, key, signature) { - return createVerify(algo).update(data).verify(key, signature); - } - - for (const signFn of [sign, oldSign]) { - const signature = signFn('SHA256', message, privateKey); - for (const verifyFn of [verify, oldVerify]) { - for (const key of [publicKey, privateKey]) { - const okay = verifyFn('SHA256', message, key, signature); - assert(okay); - } - } - } -} - -// Constructs a regular expression for a PEM-encoded key with the given label. -function getRegExpForPEM(label, cipher) { - const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`; - const rfc1421Header = cipher == null ? '' : - `\nProc-Type: 4,ENCRYPTED\nDEK-Info: ${cipher},[^\n]+\n`; - const body = '([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}'; - const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`; - return new RegExp(`^${head}${rfc1421Header}\n${body}\n${end}\n$`); -} - -const pkcs1PubExp = getRegExpForPEM('RSA PUBLIC KEY'); -const pkcs1PrivExp = getRegExpForPEM('RSA PRIVATE KEY'); -const pkcs1EncExp = (cipher) => getRegExpForPEM('RSA PRIVATE KEY', cipher); -const spkiExp = getRegExpForPEM('PUBLIC KEY'); -const pkcs8Exp = getRegExpForPEM('PRIVATE KEY'); -const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY'); -const sec1Exp = getRegExpForPEM('EC PRIVATE KEY'); -const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); - -{ - // To make the test faster, we will only test sync key generation once and - // with a relatively small key. - const ret = generateKeyPairSync('rsa', { - publicExponent: 3, - modulusLength: 512, - publicKeyEncoding: { - type: 'pkcs1', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem' - } - }); - - assert.strictEqual(Object.keys(ret).length, 2); - const { publicKey, privateKey } = ret; - - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, pkcs1PubExp); - assertApproximateSize(publicKey, 162); - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, pkcs8Exp); - assertApproximateSize(privateKey, 512); - - testEncryptDecrypt(publicKey, privateKey); - testSignVerify(publicKey, privateKey); -} - -{ - // Test sync key generation with key objects with a non-standard - // publicExponent - const { publicKey, privateKey } = generateKeyPairSync('rsa', { - publicExponent: 3, - modulusLength: 512 - }); - - assert.strictEqual(typeof publicKey, 'object'); - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 3n - }); - - assert.strictEqual(typeof privateKey, 'object'); - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 3n - }); -} - -{ - // Test sync key generation with key objects. - const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: 512 - }); - - assert.strictEqual(typeof publicKey, 'object'); - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 65537n - }); - - assert.strictEqual(typeof privateKey, 'object'); - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 65537n - }); -} - -{ - const publicKeyEncoding = { - type: 'pkcs1', - format: 'der' - }; - - // Test async RSA key generation. - generateKeyPair('rsa', { - publicExponent: 0x10001, - modulusLength: 512, - publicKeyEncoding, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem' - } - }, common.mustSucceed((publicKeyDER, privateKey) => { - assert(Buffer.isBuffer(publicKeyDER)); - assertApproximateSize(publicKeyDER, 74); - - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, pkcs1PrivExp); - assertApproximateSize(privateKey, 512); - - const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; - testEncryptDecrypt(publicKey, privateKey); - testSignVerify(publicKey, privateKey); - })); - - // Now do the same with an encrypted private key. - generateKeyPair('rsa', { - publicExponent: 0x10001, - modulusLength: 512, - publicKeyEncoding, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem', - cipher: 'aes-256-cbc', - passphrase: 'secret' - } - }, common.mustSucceed((publicKeyDER, privateKey) => { - assert(Buffer.isBuffer(publicKeyDER)); - assertApproximateSize(publicKeyDER, 74); - - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, pkcs1EncExp('AES-256-CBC')); - - // Since the private key is encrypted, signing shouldn't work anymore. - const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; - const expectedError = common.hasOpenSSL3 ? { - name: 'Error', - message: 'error:07880109:common libcrypto routines::interrupted or ' + - 'cancelled' - } : { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }; - assert.throws(() => testSignVerify(publicKey, privateKey), expectedError); - - const key = { key: privateKey, passphrase: 'secret' }; - testEncryptDecrypt(publicKey, key); - testSignVerify(publicKey, key); - })); - - // Now do the same with an encrypted private key, but encoded as DER. - generateKeyPair('rsa', { - publicExponent: 0x10001, - modulusLength: 512, - publicKeyEncoding, - privateKeyEncoding: { - type: 'pkcs8', - format: 'der', - cipher: 'aes-256-cbc', - passphrase: 'secret' - } - }, common.mustSucceed((publicKeyDER, privateKeyDER) => { - assert(Buffer.isBuffer(publicKeyDER)); - assertApproximateSize(publicKeyDER, 74); - - assert(Buffer.isBuffer(privateKeyDER)); - - // Since the private key is encrypted, signing shouldn't work anymore. - const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; - assert.throws(() => { - testSignVerify(publicKey, { - key: privateKeyDER, - format: 'der', - type: 'pkcs8' - }); - }, { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }); - - // Signing should work with the correct password. - - const privateKey = { - key: privateKeyDER, - format: 'der', - type: 'pkcs8', - passphrase: 'secret' - }; - testEncryptDecrypt(publicKey, privateKey); - testSignVerify(publicKey, privateKey); - })); - - // Now do the same with an encrypted private key, but encoded as DER. - generateKeyPair('rsa', { - publicExponent: 0x10001, - modulusLength: 512, - publicKeyEncoding, - privateKeyEncoding: { - type: 'pkcs8', - format: 'der' - } - }, common.mustSucceed((publicKeyDER, privateKeyDER) => { - assert(Buffer.isBuffer(publicKeyDER)); - assertApproximateSize(publicKeyDER, 74); - - assert(Buffer.isBuffer(privateKeyDER)); +const { inspect } = require('util'); - const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; - const privateKey = { - key: privateKeyDER, - format: 'der', - type: 'pkcs8', - passphrase: 'secret' - }; - testEncryptDecrypt(publicKey, privateKey); - testSignVerify(publicKey, privateKey); - })); -} - -{ - // Test RSA-PSS. - generateKeyPair('rsa-pss', { - modulusLength: 512, - saltLength: 16, - hashAlgorithm: 'sha256', - mgf1HashAlgorithm: 'sha256' - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 65537n, - hashAlgorithm: 'sha256', - mgf1HashAlgorithm: 'sha256', - saltLength: 16 - }); - - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 65537n, - hashAlgorithm: 'sha256', - mgf1HashAlgorithm: 'sha256', - saltLength: 16 - }); - - // Unlike RSA, RSA-PSS does not allow encryption. - assert.throws(() => { - testEncryptDecrypt(publicKey, privateKey); - }, /operation not supported for this keytype/); - - // RSA-PSS also does not permit signing with PKCS1 padding. - assert.throws(() => { - testSignVerify({ - key: publicKey, - padding: constants.RSA_PKCS1_PADDING - }, { - key: privateKey, - padding: constants.RSA_PKCS1_PADDING - }); - }, /illegal or unsupported padding mode/); - - // The padding should correctly default to RSA_PKCS1_PSS_PADDING now. - testSignVerify(publicKey, privateKey); - })); -} - -{ - // 'rsa-pss' should not add a RSASSA-PSS-params sequence by default. - // Regression test for: https://github.com/nodejs/node/issues/39936 - - generateKeyPair('rsa-pss', { - modulusLength: 512 - }, common.mustSucceed((publicKey, privateKey) => { - const expectedKeyDetails = { - modulusLength: 512, - publicExponent: 65537n - }; - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); - - // To allow backporting the fix to versions that do not support - // asymmetricKeyDetails for RSA-PSS params, also verify that the exported - // AlgorithmIdentifier member of the SubjectPublicKeyInfo has the expected - // length of 11 bytes (as opposed to > 11 bytes if node added params). - const spki = publicKey.export({ format: 'der', type: 'spki' }); - assert.strictEqual(spki[3], 11, spki.toString('hex')); - })); -} - -{ - // RFC 8017, 9.1.: "Assuming that the mask generation function is based on a - // hash function, it is RECOMMENDED that the hash function be the same as the - // one that is applied to the message." - - generateKeyPair('rsa-pss', { - modulusLength: 512, - hashAlgorithm: 'sha256', - saltLength: 16 - }, common.mustSucceed((publicKey, privateKey) => { - const expectedKeyDetails = { - modulusLength: 512, - publicExponent: 65537n, - hashAlgorithm: 'sha256', - mgf1HashAlgorithm: 'sha256', - saltLength: 16 - }; - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); - })); -} - -{ - // RFC 8017, A.2.3.: "For a given hashAlgorithm, the default value of - // saltLength is the octet length of the hash value." - - generateKeyPair('rsa-pss', { - modulusLength: 512, - hashAlgorithm: 'sha512' - }, common.mustSucceed((publicKey, privateKey) => { - const expectedKeyDetails = { - modulusLength: 512, - publicExponent: 65537n, - hashAlgorithm: 'sha512', - mgf1HashAlgorithm: 'sha512', - saltLength: 64 - }; - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); - })); - - // It is still possible to explicitly set saltLength to 0. - generateKeyPair('rsa-pss', { - modulusLength: 512, - hashAlgorithm: 'sha512', - saltLength: 0 - }, common.mustSucceed((publicKey, privateKey) => { - const expectedKeyDetails = { - modulusLength: 512, - publicExponent: 65537n, - hashAlgorithm: 'sha512', - mgf1HashAlgorithm: 'sha512', - saltLength: 0 - }; - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); - })); -} - -{ - const privateKeyEncoding = { - type: 'pkcs8', - format: 'der' - }; - - // Test async DSA key generation. - generateKeyPair('dsa', { - modulusLength: common.hasOpenSSL3 ? 2048 : 512, - divisorLength: 256, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - cipher: 'aes-128-cbc', - passphrase: 'secret', - ...privateKeyEncoding - } - }, common.mustSucceed((publicKey, privateKeyDER) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, spkiExp); - // The private key is DER-encoded. - assert(Buffer.isBuffer(privateKeyDER)); - - assertApproximateSize(publicKey, common.hasOpenSSL3 ? 1194 : 440); - assertApproximateSize(privateKeyDER, common.hasOpenSSL3 ? 721 : 336); - - // Since the private key is encrypted, signing shouldn't work anymore. - assert.throws(() => { - return testSignVerify(publicKey, { - key: privateKeyDER, - ...privateKeyEncoding - }); - }, { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }); - - // Signing should work with the correct password. - testSignVerify(publicKey, { - key: privateKeyDER, - ...privateKeyEncoding, - passphrase: 'secret' - }); - })); -} -{ - // Test async DSA key object generation. - generateKeyPair('dsa', { - modulusLength: common.hasOpenSSL3 ? 2048 : 512, - divisorLength: 256 - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'dsa'); - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: common.hasOpenSSL3 ? 2048 : 512, - divisorLength: 256 - }); - - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'dsa'); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: common.hasOpenSSL3 ? 2048 : 512, - divisorLength: 256 - }); - })); -} - -{ - // Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1 - // private key. - generateKeyPair('ec', { - namedCurve: 'prime256v1', - paramEncoding: 'named', - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'sec1', - format: 'pem' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, spkiExp); - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, sec1Exp); - - testSignVerify(publicKey, privateKey); - })); - - // Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1 - // private key with paramEncoding explicit. - generateKeyPair('ec', { - namedCurve: 'prime256v1', - paramEncoding: 'explicit', - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'sec1', - format: 'pem' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, spkiExp); - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, sec1Exp); - - testSignVerify(publicKey, privateKey); - })); - - // Do the same with an encrypted private key. - generateKeyPair('ec', { - namedCurve: 'prime256v1', - paramEncoding: 'named', - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'sec1', - format: 'pem', - cipher: 'aes-128-cbc', - passphrase: 'secret' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, spkiExp); - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, sec1EncExp('AES-128-CBC')); - - // Since the private key is encrypted, signing shouldn't work anymore. - assert.throws(() => testSignVerify(publicKey, privateKey), - common.hasOpenSSL3 ? { - message: 'error:07880109:common libcrypto ' + - 'routines::interrupted or cancelled' - } : { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }); - - testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' }); - })); - - // Do the same with an encrypted private key with paramEncoding explicit. - generateKeyPair('ec', { - namedCurve: 'prime256v1', - paramEncoding: 'explicit', - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'sec1', - format: 'pem', - cipher: 'aes-128-cbc', - passphrase: 'secret' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, spkiExp); - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, sec1EncExp('AES-128-CBC')); - - // Since the private key is encrypted, signing shouldn't work anymore. - assert.throws(() => testSignVerify(publicKey, privateKey), - common.hasOpenSSL3 ? { - message: 'error:07880109:common libcrypto ' + - 'routines::interrupted or cancelled' - } : { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }); - - testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' }); - })); -} - -{ - // Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted - // private key. - generateKeyPair('ec', { - namedCurve: 'P-256', - paramEncoding: 'named', - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - cipher: 'aes-128-cbc', - passphrase: 'top secret' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, spkiExp); - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, pkcs8EncExp); - - // Since the private key is encrypted, signing shouldn't work anymore. - assert.throws(() => testSignVerify(publicKey, privateKey), - common.hasOpenSSL3 ? { - message: 'error:07880109:common libcrypto ' + - 'routines::interrupted or cancelled' - } : { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }); - - testSignVerify(publicKey, { - key: privateKey, - passphrase: 'top secret' - }); - })); - - // Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted - // private key with paramEncoding explicit. - generateKeyPair('ec', { - namedCurve: 'P-256', - paramEncoding: 'explicit', - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - cipher: 'aes-128-cbc', - passphrase: 'top secret' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, spkiExp); - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, pkcs8EncExp); - - // Since the private key is encrypted, signing shouldn't work anymore. - assert.throws(() => testSignVerify(publicKey, privateKey), - common.hasOpenSSL3 ? { - message: 'error:07880109:common libcrypto ' + - 'routines::interrupted or cancelled' - } : { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }); - - testSignVerify(publicKey, { - key: privateKey, - passphrase: 'top secret' - }); - })); - - // Test async elliptic curve key generation with 'jwk' encoding - [ - ['ec', ['P-384', 'P-256', 'P-521', 'secp256k1']], - ['rsa'], - ['ed25519'], - ['ed448'], - ['x25519'], - ['x448'], - ].forEach((types) => { - const [type, options] = types; - switch (type) { - case 'ec': { - return options.forEach((curve) => { - generateKeyPair(type, { - namedCurve: curve, - publicKeyEncoding: { - format: 'jwk' - }, - privateKeyEncoding: { - format: 'jwk' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'object'); - assert.strictEqual(typeof privateKey, 'object'); - assert.strictEqual(publicKey.x, privateKey.x); - assert.strictEqual(publicKey.y, privateKey.y); - assert(!publicKey.d); - assert(privateKey.d); - assert.strictEqual(publicKey.kty, 'EC'); - assert.strictEqual(publicKey.kty, privateKey.kty); - assert.strictEqual(publicKey.crv, curve); - assert.strictEqual(publicKey.crv, privateKey.crv); - })); - }); - } - case 'rsa': { - return generateKeyPair(type, { - modulusLength: 4096, - publicKeyEncoding: { - format: 'jwk' - }, - privateKeyEncoding: { - format: 'jwk' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'object'); - assert.strictEqual(typeof privateKey, 'object'); - assert.strictEqual(publicKey.kty, 'RSA'); - assert.strictEqual(publicKey.kty, privateKey.kty); - assert.strictEqual(typeof publicKey.n, 'string'); - assert.strictEqual(publicKey.n, privateKey.n); - assert.strictEqual(typeof publicKey.e, 'string'); - assert.strictEqual(publicKey.e, privateKey.e); - assert.strictEqual(typeof privateKey.d, 'string'); - assert.strictEqual(typeof privateKey.p, 'string'); - assert.strictEqual(typeof privateKey.q, 'string'); - assert.strictEqual(typeof privateKey.dp, 'string'); - assert.strictEqual(typeof privateKey.dq, 'string'); - assert.strictEqual(typeof privateKey.qi, 'string'); - })); - } - case 'ed25519': - case 'ed448': - case 'x25519': - case 'x448': { - generateKeyPair(type, { - publicKeyEncoding: { - format: 'jwk' - }, - privateKeyEncoding: { - format: 'jwk' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'object'); - assert.strictEqual(typeof privateKey, 'object'); - assert.strictEqual(publicKey.x, privateKey.x); - assert(!publicKey.d); - assert(privateKey.d); - assert.strictEqual(publicKey.kty, 'OKP'); - assert.strictEqual(publicKey.kty, privateKey.kty); - const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`; - assert.strictEqual(publicKey.crv, expectedCrv); - assert.strictEqual(publicKey.crv, privateKey.crv); - })); - } - } - }); -} // Test invalid parameter encoding. { @@ -799,60 +36,6 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); message: "The property 'options.paramEncoding' is invalid. " + "Received 'otherEncoding'" }); - assert.throws(() => generateKeyPairSync('dsa', { - modulusLength: 4096, - publicKeyEncoding: { - format: 'jwk' - }, - privateKeyEncoding: { - format: 'jwk' - } - }), { - name: 'Error', - code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', - message: 'Unsupported JWK Key Type.' - }); - assert.throws(() => generateKeyPairSync('ec', { - namedCurve: 'secp224r1', - publicKeyEncoding: { - format: 'jwk' - }, - privateKeyEncoding: { - format: 'jwk' - } - }), { - name: 'Error', - code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', - message: 'Unsupported JWK EC curve: secp224r1.' - }); -} - -{ - // Test the util.promisified API with async RSA key generation. - promisify(generateKeyPair)('rsa', { - publicExponent: 0x10001, - modulusLength: 512, - publicKeyEncoding: { - type: 'pkcs1', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem' - } - }).then(common.mustCall((keys) => { - const { publicKey, privateKey } = keys; - assert.strictEqual(typeof publicKey, 'string'); - assert.match(publicKey, pkcs1PubExp); - assertApproximateSize(publicKey, 180); - - assert.strictEqual(typeof privateKey, 'string'); - assert.match(privateKey, pkcs1PrivExp); - assertApproximateSize(privateKey, 512); - - testEncryptDecrypt(publicKey, privateKey); - testSignVerify(publicKey, privateKey); - })); } { @@ -892,46 +75,6 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); }); } -{ - // If no publicKeyEncoding is specified, a key object should be returned. - generateKeyPair('rsa', { - modulusLength: 1024, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'object'); - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); - - // The private key should still be a string. - assert.strictEqual(typeof privateKey, 'string'); - - testEncryptDecrypt(publicKey, privateKey); - testSignVerify(publicKey, privateKey); - })); - - // If no privateKeyEncoding is specified, a key object should be returned. - generateKeyPair('rsa', { - modulusLength: 1024, - publicKeyEncoding: { - type: 'pkcs1', - format: 'pem' - } - }, common.mustSucceed((publicKey, privateKey) => { - // The public key should still be a string. - assert.strictEqual(typeof publicKey, 'string'); - - assert.strictEqual(typeof privateKey, 'object'); - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); - - testEncryptDecrypt(publicKey, privateKey); - testSignVerify(publicKey, privateKey); - })); -} - { // Invalid publicKeyEncoding. for (const enc of [0, 'a', true]) { @@ -1348,35 +491,7 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); })); } -// Test EdDSA key generation. { - if (!/^1\.1\.0/.test(process.versions.openssl)) { - ['ed25519', 'ed448', 'x25519', 'x448'].forEach((keyType) => { - generateKeyPair(keyType, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, keyType); - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {}); - - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, keyType); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {}); - })); - }); - } -} - -// Test classic Diffie-Hellman key generation. -{ - generateKeyPair('dh', { - primeLength: 1024 - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'dh'); - - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'dh'); - })); - assert.throws(() => { generateKeyPair('dh', common.mustNotCall()); }, { @@ -1685,131 +800,6 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); }); } -// Passing an empty passphrase string should not cause OpenSSL's default -// passphrase prompt in the terminal. -// See https://github.com/nodejs/node/issues/35898. - -for (const type of ['pkcs1', 'pkcs8']) { - generateKeyPair('rsa', { - modulusLength: 1024, - privateKeyEncoding: { - type, - format: 'pem', - cipher: 'aes-256-cbc', - passphrase: '' - } - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(publicKey.type, 'public'); - - for (const passphrase of ['', Buffer.alloc(0)]) { - const privateKeyObject = createPrivateKey({ - passphrase, - key: privateKey - }); - assert.strictEqual(privateKeyObject.asymmetricKeyType, 'rsa'); - } - - // Encrypting with an empty passphrase is not the same as not encrypting - // the key, and not specifying a passphrase should fail when decoding it. - assert.throws(() => { - return testSignVerify(publicKey, privateKey); - }, common.hasOpenSSL3 ? { - name: 'Error', - code: 'ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED', - message: 'error:07880109:common libcrypto routines::interrupted or cancelled' - } : { - name: 'TypeError', - code: 'ERR_MISSING_PASSPHRASE', - message: 'Passphrase required for encrypted key' - }); - })); -} - -// Passing an empty passphrase string should not throw ERR_OSSL_CRYPTO_MALLOC_FAILURE even on OpenSSL 3. -// Regression test for https://github.com/nodejs/node/issues/41428. -generateKeyPair('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - cipher: 'aes-256-cbc', - passphrase: '' - } -}, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(typeof publicKey, 'string'); - assert.strictEqual(typeof privateKey, 'string'); -})); - -{ - // This test creates EC key pairs on curves without associated OIDs. - // Specifying a key encoding should not crash. - - if (process.versions.openssl >= '1.1.1i') { - for (const namedCurve of ['Oakley-EC2N-3', 'Oakley-EC2N-4']) { - if (!getCurves().includes(namedCurve)) - continue; - - const expectedErrorCode = - common.hasOpenSSL3 ? 'ERR_OSSL_MISSING_OID' : 'ERR_OSSL_EC_MISSING_OID'; - const params = { - namedCurve, - publicKeyEncoding: { - format: 'der', - type: 'spki' - } - }; - - assert.throws(() => { - generateKeyPairSync('ec', params); - }, { - code: expectedErrorCode - }); - - generateKeyPair('ec', params, common.mustCall((err) => { - assert.strictEqual(err.code, expectedErrorCode); - })); - } - } -} - -{ - // This test makes sure deprecated and new options may be used - // simultaneously so long as they're identical values. - - generateKeyPair('rsa-pss', { - modulusLength: 512, - saltLength: 16, - hash: 'sha256', - hashAlgorithm: 'sha256', - mgf1Hash: 'sha256', - mgf1HashAlgorithm: 'sha256' - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 65537n, - hashAlgorithm: 'sha256', - mgf1HashAlgorithm: 'sha256', - saltLength: 16 - }); - - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 65537n, - hashAlgorithm: 'sha256', - mgf1HashAlgorithm: 'sha256', - saltLength: 16 - }); - })); -} - { // This test makes sure deprecated and new options must // be the same value. @@ -1828,31 +818,3 @@ generateKeyPair('rsa', { hashAlgorithm: 'sha1' }, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' }); } - -{ - // https://github.com/nodejs/node/issues/46102#issuecomment-1372153541 - - generateKeyPair('rsa', { - modulusLength: 513, - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513); - assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513); - })); - - generateKeyPair('rsa-pss', { - modulusLength: 513, - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513); - assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513); - })); - - if (common.hasOpenSSL3) { - generateKeyPair('dsa', { - modulusLength: 2049, - divisorLength: 256, - }, common.mustSucceed((publicKey, privateKey) => { - assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 2049); - assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2049); - })); - } -}