mirror of
https://github.com/nodejs/node.git
synced 2025-05-01 08:42:45 +00:00

md and mgf1_md are internal variable names and should not appear in JS error messages. Also include the invalid digest name in the error message. PR-URL: https://github.com/nodejs/node/pull/44307 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
1820 lines
53 KiB
JavaScript
1820 lines
53 KiB
JavaScript
'use strict';
|
|
|
|
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 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.
|
|
{
|
|
assert.throws(() => generateKeyPairSync('ec', {
|
|
namedCurve: 'P-256',
|
|
paramEncoding: 'otherEncoding',
|
|
publicKeyEncoding: {
|
|
type: 'spki',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
cipher: 'aes-128-cbc',
|
|
passphrase: 'top secret'
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
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);
|
|
}));
|
|
}
|
|
|
|
{
|
|
// Test invalid key types.
|
|
for (const type of [undefined, null, 0]) {
|
|
assert.throws(() => generateKeyPairSync(type, {}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message: 'The "type" argument must be of type string.' +
|
|
common.invalidArgTypeHelper(type)
|
|
});
|
|
}
|
|
|
|
assert.throws(() => generateKeyPairSync('rsa2', {}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The argument 'type' must be a supported key type. Received 'rsa2'"
|
|
});
|
|
}
|
|
|
|
{
|
|
// Test keygen without options object.
|
|
assert.throws(() => generateKeyPair('rsa', common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message: 'The "options" argument must be of type object. ' +
|
|
'Received undefined'
|
|
});
|
|
|
|
// Even if no options are required, it should be impossible to pass anything
|
|
// but an object (or undefined).
|
|
assert.throws(() => generateKeyPair('ed448', 0, common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message: 'The "options" argument must be of type object. ' +
|
|
'Received type number (0)'
|
|
});
|
|
}
|
|
|
|
{
|
|
// 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]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: enc,
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.publicKeyEncoding' is invalid. " +
|
|
`Received ${inspect(enc)}`
|
|
});
|
|
}
|
|
|
|
// Missing publicKeyEncoding.type.
|
|
for (const type of [undefined, null, 0, true, {}]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type,
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.publicKeyEncoding.type' is invalid. " +
|
|
`Received ${inspect(type)}`
|
|
});
|
|
}
|
|
|
|
// Missing / invalid publicKeyEncoding.format.
|
|
for (const format of [undefined, null, 0, false, 'a', {}]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.publicKeyEncoding.format' is invalid. " +
|
|
`Received ${inspect(format)}`
|
|
});
|
|
}
|
|
|
|
// Invalid privateKeyEncoding.
|
|
for (const enc of [0, 'a', true]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: enc
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.privateKeyEncoding' is invalid. " +
|
|
`Received ${inspect(enc)}`
|
|
});
|
|
}
|
|
|
|
// Missing / invalid privateKeyEncoding.type.
|
|
for (const type of [undefined, null, 0, true, {}]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type,
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.privateKeyEncoding.type' is invalid. " +
|
|
`Received ${inspect(type)}`
|
|
});
|
|
}
|
|
|
|
// Missing / invalid privateKeyEncoding.format.
|
|
for (const format of [undefined, null, 0, false, 'a', {}]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.privateKeyEncoding.format' is invalid. " +
|
|
`Received ${inspect(format)}`
|
|
});
|
|
}
|
|
|
|
// Cipher of invalid type.
|
|
for (const cipher of [0, true, {}]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem',
|
|
cipher
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.privateKeyEncoding.cipher' is invalid. " +
|
|
`Received ${inspect(cipher)}`
|
|
});
|
|
}
|
|
|
|
// Invalid cipher.
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
cipher: 'foo',
|
|
passphrase: 'secret'
|
|
}
|
|
}), {
|
|
name: 'Error',
|
|
code: 'ERR_CRYPTO_UNKNOWN_CIPHER',
|
|
message: 'Unknown cipher'
|
|
});
|
|
|
|
// Cipher, but no valid passphrase.
|
|
for (const passphrase of [undefined, null, 5, false, true]) {
|
|
assert.throws(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
cipher: 'aes-128-cbc',
|
|
passphrase
|
|
}
|
|
}), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.privateKeyEncoding.passphrase' " +
|
|
`is invalid. Received ${inspect(passphrase)}`
|
|
});
|
|
}
|
|
|
|
// Test invalid callbacks.
|
|
for (const cb of [undefined, null, 0, {}]) {
|
|
assert.throws(() => generateKeyPair('rsa', {
|
|
modulusLength: 512,
|
|
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
|
|
}, cb), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test RSA parameters.
|
|
{
|
|
// Test invalid modulus lengths. (non-number)
|
|
for (const modulusLength of [undefined, null, 'a', true, {}, []]) {
|
|
assert.throws(() => generateKeyPair('rsa', {
|
|
modulusLength
|
|
}, common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message:
|
|
'The "options.modulusLength" property must be of type number.' +
|
|
common.invalidArgTypeHelper(modulusLength)
|
|
});
|
|
}
|
|
|
|
// Test invalid modulus lengths. (non-integer)
|
|
for (const modulusLength of [512.1, 1.3, 1.1, 5000.9, 100.5]) {
|
|
assert.throws(() => generateKeyPair('rsa', {
|
|
modulusLength
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message:
|
|
'The value of "options.modulusLength" is out of range. ' +
|
|
'It must be an integer. ' +
|
|
`Received ${inspect(modulusLength)}`
|
|
});
|
|
}
|
|
|
|
// Test invalid modulus lengths. (out of range)
|
|
for (const modulusLength of [-1, -9, 4294967297]) {
|
|
assert.throws(() => generateKeyPair('rsa', {
|
|
modulusLength
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
});
|
|
}
|
|
|
|
// Test invalid exponents. (non-number)
|
|
for (const publicExponent of ['a', true, {}, []]) {
|
|
assert.throws(() => generateKeyPair('rsa', {
|
|
modulusLength: 4096,
|
|
publicExponent
|
|
}, common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message:
|
|
'The "options.publicExponent" property must be of type number.' +
|
|
common.invalidArgTypeHelper(publicExponent)
|
|
});
|
|
}
|
|
|
|
// Test invalid exponents. (non-integer)
|
|
for (const publicExponent of [3.5, 1.1, 50.5, 510.5]) {
|
|
assert.throws(() => generateKeyPair('rsa', {
|
|
modulusLength: 4096,
|
|
publicExponent
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message:
|
|
'The value of "options.publicExponent" is out of range. ' +
|
|
'It must be an integer. ' +
|
|
`Received ${inspect(publicExponent)}`
|
|
});
|
|
}
|
|
|
|
// Test invalid exponents. (out of range)
|
|
for (const publicExponent of [-5, -3, 4294967297]) {
|
|
assert.throws(() => generateKeyPair('rsa', {
|
|
modulusLength: 4096,
|
|
publicExponent
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test DSA parameters.
|
|
{
|
|
// Test invalid modulus lengths. (non-number)
|
|
for (const modulusLength of [undefined, null, 'a', true, {}, []]) {
|
|
assert.throws(() => generateKeyPair('dsa', {
|
|
modulusLength
|
|
}, common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message:
|
|
'The "options.modulusLength" property must be of type number.' +
|
|
common.invalidArgTypeHelper(modulusLength)
|
|
});
|
|
}
|
|
|
|
// Test invalid modulus lengths. (non-integer)
|
|
for (const modulusLength of [512.1, 1.3, 1.1, 5000.9, 100.5]) {
|
|
assert.throws(() => generateKeyPair('dsa', {
|
|
modulusLength
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
});
|
|
}
|
|
|
|
// Test invalid modulus lengths. (out of range)
|
|
for (const modulusLength of [-1, -9, 4294967297]) {
|
|
assert.throws(() => generateKeyPair('dsa', {
|
|
modulusLength
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
});
|
|
}
|
|
|
|
// Test invalid divisor lengths. (non-number)
|
|
for (const divisorLength of ['a', true, {}, []]) {
|
|
assert.throws(() => generateKeyPair('dsa', {
|
|
modulusLength: 2048,
|
|
divisorLength
|
|
}, common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message:
|
|
'The "options.divisorLength" property must be of type number.' +
|
|
common.invalidArgTypeHelper(divisorLength)
|
|
});
|
|
}
|
|
|
|
// Test invalid divisor lengths. (non-integer)
|
|
for (const divisorLength of [4096.1, 5.1, 6.9, 9.5]) {
|
|
assert.throws(() => generateKeyPair('dsa', {
|
|
modulusLength: 2048,
|
|
divisorLength
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message:
|
|
'The value of "options.divisorLength" is out of range. ' +
|
|
'It must be an integer. ' +
|
|
`Received ${inspect(divisorLength)}`
|
|
});
|
|
}
|
|
|
|
// Test invalid divisor lengths. (out of range)
|
|
for (const divisorLength of [-6, -9, 2147483648]) {
|
|
assert.throws(() => generateKeyPair('dsa', {
|
|
modulusLength: 2048,
|
|
divisorLength
|
|
}, common.mustNotCall()), {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message:
|
|
'The value of "options.divisorLength" is out of range. ' +
|
|
'It must be >= 0 && <= 2147483647. ' +
|
|
`Received ${inspect(divisorLength)}`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test EC parameters.
|
|
{
|
|
// Test invalid curves.
|
|
assert.throws(() => {
|
|
generateKeyPairSync('ec', {
|
|
namedCurve: 'abcdef',
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
|
});
|
|
}, {
|
|
name: 'TypeError',
|
|
message: 'Invalid EC curve name'
|
|
});
|
|
|
|
// Test error type when curve is not a string
|
|
for (const namedCurve of [true, {}, [], 123]) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync('ec', {
|
|
namedCurve,
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
|
});
|
|
}, {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message:
|
|
'The "options.namedCurve" property must be of type string.' +
|
|
common.invalidArgTypeHelper(namedCurve)
|
|
});
|
|
}
|
|
|
|
// It should recognize both NIST and standard curve names.
|
|
generateKeyPair('ec', {
|
|
namedCurve: 'P-256',
|
|
}, common.mustSucceed((publicKey, privateKey) => {
|
|
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
|
|
namedCurve: 'prime256v1'
|
|
});
|
|
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
|
|
namedCurve: 'prime256v1'
|
|
});
|
|
}));
|
|
|
|
generateKeyPair('ec', {
|
|
namedCurve: 'secp256k1',
|
|
}, common.mustSucceed((publicKey, privateKey) => {
|
|
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
|
|
namedCurve: 'secp256k1'
|
|
});
|
|
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
|
|
namedCurve: 'secp256k1'
|
|
});
|
|
}));
|
|
}
|
|
|
|
// 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());
|
|
}, {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message: 'The "options" argument must be of type object. Received undefined'
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPair('dh', {}, common.mustNotCall());
|
|
}, {
|
|
name: 'TypeError',
|
|
code: 'ERR_MISSING_OPTION',
|
|
message: 'At least one of the group, prime, or primeLength options is ' +
|
|
'required'
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPair('dh', {
|
|
group: 'modp0'
|
|
}, common.mustNotCall());
|
|
}, {
|
|
name: 'Error',
|
|
code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP',
|
|
message: 'Unknown DH group'
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPair('dh', {
|
|
primeLength: 2147483648
|
|
}, common.mustNotCall());
|
|
}, {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message: 'The value of "options.primeLength" is out of range. ' +
|
|
'It must be >= 0 && <= 2147483647. ' +
|
|
'Received 2147483648',
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPair('dh', {
|
|
primeLength: -1
|
|
}, common.mustNotCall());
|
|
}, {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message: 'The value of "options.primeLength" is out of range. ' +
|
|
'It must be >= 0 && <= 2147483647. ' +
|
|
'Received -1',
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPair('dh', {
|
|
primeLength: 2,
|
|
generator: 2147483648,
|
|
}, common.mustNotCall());
|
|
}, {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message: 'The value of "options.generator" is out of range. ' +
|
|
'It must be >= 0 && <= 2147483647. ' +
|
|
'Received 2147483648',
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPair('dh', {
|
|
primeLength: 2,
|
|
generator: -1,
|
|
}, common.mustNotCall());
|
|
}, {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message: 'The value of "options.generator" is out of range. ' +
|
|
'It must be >= 0 && <= 2147483647. ' +
|
|
'Received -1',
|
|
});
|
|
|
|
// Test incompatible options.
|
|
const allOpts = {
|
|
group: 'modp5',
|
|
prime: Buffer.alloc(0),
|
|
primeLength: 1024,
|
|
generator: 2
|
|
};
|
|
const incompatible = [
|
|
['group', 'prime'],
|
|
['group', 'primeLength'],
|
|
['group', 'generator'],
|
|
['prime', 'primeLength'],
|
|
];
|
|
for (const [opt1, opt2] of incompatible) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync('dh', {
|
|
[opt1]: allOpts[opt1],
|
|
[opt2]: allOpts[opt2]
|
|
});
|
|
}, {
|
|
name: 'TypeError',
|
|
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
|
|
message: `Option "${opt1}" cannot be used in combination with option ` +
|
|
`"${opt2}"`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test invalid key encoding types.
|
|
{
|
|
// Invalid public key type.
|
|
for (const type of ['foo', 'pkcs8', 'sec1']) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: { type, format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
});
|
|
}, {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.publicKeyEncoding.type' is invalid. " +
|
|
`Received ${inspect(type)}`
|
|
});
|
|
}
|
|
|
|
// Invalid hash value.
|
|
for (const hashValue of [123, true, {}, []]) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync('rsa-pss', {
|
|
modulusLength: 4096,
|
|
hashAlgorithm: hashValue
|
|
});
|
|
}, {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message:
|
|
'The "options.hashAlgorithm" property must be of type string.' +
|
|
common.invalidArgTypeHelper(hashValue)
|
|
});
|
|
}
|
|
|
|
// too long salt length
|
|
assert.throws(() => {
|
|
generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: 2147483648,
|
|
hashAlgorithm: 'sha256',
|
|
mgf1HashAlgorithm: 'sha256'
|
|
}, common.mustNotCall());
|
|
}, {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message: 'The value of "options.saltLength" is out of range. ' +
|
|
'It must be >= 0 && <= 2147483647. ' +
|
|
'Received 2147483648'
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: -1,
|
|
hashAlgorithm: 'sha256',
|
|
mgf1HashAlgorithm: 'sha256'
|
|
}, common.mustNotCall());
|
|
}, {
|
|
name: 'RangeError',
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
message: 'The value of "options.saltLength" is out of range. ' +
|
|
'It must be >= 0 && <= 2147483647. ' +
|
|
'Received -1'
|
|
});
|
|
|
|
// Invalid private key type.
|
|
for (const type of ['foo', 'spki']) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type, format: 'pem' }
|
|
});
|
|
}, {
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The property 'options.privateKeyEncoding.type' is invalid. " +
|
|
`Received ${inspect(type)}`
|
|
});
|
|
}
|
|
|
|
// Key encoding doesn't match key type.
|
|
for (const type of ['dsa', 'ec']) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync(type, {
|
|
modulusLength: 4096,
|
|
namedCurve: 'P-256',
|
|
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
});
|
|
}, {
|
|
name: 'Error',
|
|
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
|
message: 'The selected key encoding pkcs1 can only be used for RSA keys.'
|
|
});
|
|
|
|
assert.throws(() => {
|
|
generateKeyPairSync(type, {
|
|
modulusLength: 4096,
|
|
namedCurve: 'P-256',
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
|
|
});
|
|
}, {
|
|
name: 'Error',
|
|
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
|
message: 'The selected key encoding pkcs1 can only be used for RSA keys.'
|
|
});
|
|
}
|
|
|
|
for (const type of ['rsa', 'dsa']) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync(type, {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
|
});
|
|
}, {
|
|
name: 'Error',
|
|
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
|
message: 'The selected key encoding sec1 can only be used for EC keys.'
|
|
});
|
|
}
|
|
|
|
// Attempting to encrypt a DER-encoded, non-PKCS#8 key.
|
|
for (const type of ['pkcs1', 'sec1']) {
|
|
assert.throws(() => {
|
|
generateKeyPairSync(type === 'pkcs1' ? 'rsa' : 'ec', {
|
|
modulusLength: 4096,
|
|
namedCurve: 'P-256',
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: {
|
|
type,
|
|
format: 'der',
|
|
cipher: 'aes-128-cbc',
|
|
passphrase: 'hello'
|
|
}
|
|
});
|
|
}, {
|
|
name: 'Error',
|
|
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
|
message: `The selected key encoding ${type} does not support encryption.`
|
|
});
|
|
}
|
|
}
|
|
|
|
{
|
|
// Test RSA-PSS.
|
|
assert.throws(
|
|
() => {
|
|
generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: 16,
|
|
hashAlgorithm: 'sha256',
|
|
mgf1HashAlgorithm: undefined
|
|
});
|
|
},
|
|
{
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
}
|
|
);
|
|
|
|
for (const mgf1HashAlgorithm of [null, 0, false, {}, []]) {
|
|
assert.throws(
|
|
() => {
|
|
generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: 16,
|
|
hashAlgorithm: 'sha256',
|
|
mgf1HashAlgorithm
|
|
}, common.mustNotCall());
|
|
},
|
|
{
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message:
|
|
'The "options.mgf1HashAlgorithm" property must be of type string.' +
|
|
common.invalidArgTypeHelper(mgf1HashAlgorithm)
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
assert.throws(() => generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
hashAlgorithm: 'sha2',
|
|
}, common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_CRYPTO_INVALID_DIGEST',
|
|
message: 'Invalid digest: sha2'
|
|
});
|
|
|
|
assert.throws(() => generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
mgf1HashAlgorithm: 'sha2',
|
|
}, common.mustNotCall()), {
|
|
name: 'TypeError',
|
|
code: 'ERR_CRYPTO_INVALID_DIGEST',
|
|
message: 'Invalid MGF1 digest: sha2'
|
|
});
|
|
}
|
|
|
|
// 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.
|
|
|
|
assert.throws(() => generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: 16,
|
|
mgf1Hash: 'sha256',
|
|
mgf1HashAlgorithm: 'sha1'
|
|
}, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' });
|
|
|
|
assert.throws(() => generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: 16,
|
|
hash: 'sha256',
|
|
hashAlgorithm: 'sha1'
|
|
}, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' });
|
|
}
|