mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
ERR_INVALID_ARG_TYPE is the most common error used throughout the code base. This improves the error message by providing more details to the user and by indicating more precisely which values are allowed ones and which ones are not. It adds the actual input to the error message in case it's a primitive. If it's a class instance, it'll print the class name instead of "object" and "falsy" or similar entries are not named "type" anymore. PR-URL: https://github.com/nodejs/node/pull/29675 Reviewed-By: Rich Trott <rtrott@gmail.com>
1132 lines
31 KiB
JavaScript
1132 lines
31 KiB
JavaScript
'use strict';
|
|
|
|
const common = require('../common');
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
|
|
const assert = require('assert');
|
|
const {
|
|
constants,
|
|
createSign,
|
|
createVerify,
|
|
generateKeyPair,
|
|
generateKeyPairSync,
|
|
publicEncrypt,
|
|
privateDecrypt,
|
|
sign,
|
|
verify
|
|
} = require('crypto');
|
|
const { 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(pkcs1PubExp.test(publicKey));
|
|
assertApproximateSize(publicKey, 162);
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(pkcs8Exp.test(privateKey));
|
|
assertApproximateSize(privateKey, 512);
|
|
|
|
testEncryptDecrypt(publicKey, privateKey);
|
|
testSignVerify(publicKey, privateKey);
|
|
}
|
|
|
|
{
|
|
// 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.strictEqual(typeof privateKey, 'object');
|
|
assert.strictEqual(privateKey.type, 'private');
|
|
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
|
|
}
|
|
|
|
{
|
|
const publicKeyEncoding = {
|
|
type: 'pkcs1',
|
|
format: 'der'
|
|
};
|
|
|
|
// Test async RSA key generation.
|
|
generateKeyPair('rsa', {
|
|
publicExponent: 0x10001,
|
|
modulusLength: 512,
|
|
publicKeyEncoding,
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
}
|
|
}, common.mustCall((err, publicKeyDER, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert(Buffer.isBuffer(publicKeyDER));
|
|
assertApproximateSize(publicKeyDER, 74);
|
|
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(pkcs1PrivExp.test(privateKey));
|
|
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: 0x1001,
|
|
modulusLength: 512,
|
|
publicKeyEncoding,
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem',
|
|
cipher: 'aes-256-cbc',
|
|
passphrase: 'secret'
|
|
}
|
|
}, common.mustCall((err, publicKeyDER, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert(Buffer.isBuffer(publicKeyDER));
|
|
assertApproximateSize(publicKeyDER, 74);
|
|
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(pkcs1EncExp('AES-256-CBC').test(privateKey));
|
|
|
|
// Since the private key is encrypted, signing shouldn't work anymore.
|
|
const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
|
|
common.expectsError(() => testSignVerify(publicKey, privateKey), {
|
|
type: TypeError,
|
|
code: 'ERR_MISSING_PASSPHRASE',
|
|
message: 'Passphrase required for encrypted key'
|
|
});
|
|
|
|
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.mustCall((err, publicKeyDER, privateKeyDER) => {
|
|
assert.ifError(err);
|
|
|
|
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 };
|
|
common.expectsError(() => {
|
|
testSignVerify(publicKey, {
|
|
key: privateKeyDER,
|
|
format: 'der',
|
|
type: 'pkcs8'
|
|
});
|
|
}, {
|
|
type: 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.mustCall((err, publicKeyDER, privateKeyDER) => {
|
|
assert.ifError(err);
|
|
|
|
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,
|
|
hash: 'sha256',
|
|
mgf1Hash: 'sha256'
|
|
}, common.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(publicKey.type, 'public');
|
|
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
|
|
|
|
assert.strictEqual(privateKey.type, 'private');
|
|
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
|
|
|
|
// 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);
|
|
}));
|
|
}
|
|
|
|
{
|
|
const privateKeyEncoding = {
|
|
type: 'pkcs8',
|
|
format: 'der'
|
|
};
|
|
|
|
// Test async DSA key generation.
|
|
generateKeyPair('dsa', {
|
|
modulusLength: 512,
|
|
divisorLength: 256,
|
|
publicKeyEncoding: {
|
|
type: 'spki',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
cipher: 'aes-128-cbc',
|
|
passphrase: 'secret',
|
|
...privateKeyEncoding
|
|
}
|
|
}, common.mustCall((err, publicKey, privateKeyDER) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(typeof publicKey, 'string');
|
|
assert(spkiExp.test(publicKey));
|
|
// The private key is DER-encoded.
|
|
assert(Buffer.isBuffer(privateKeyDER));
|
|
|
|
assertApproximateSize(publicKey, 440);
|
|
assertApproximateSize(privateKeyDER, 336);
|
|
|
|
// Since the private key is encrypted, signing shouldn't work anymore.
|
|
common.expectsError(() => {
|
|
return testSignVerify(publicKey, {
|
|
key: privateKeyDER,
|
|
...privateKeyEncoding
|
|
});
|
|
}, {
|
|
type: 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 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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(typeof publicKey, 'string');
|
|
assert(spkiExp.test(publicKey));
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(sec1Exp.test(privateKey));
|
|
|
|
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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(typeof publicKey, 'string');
|
|
assert(spkiExp.test(publicKey));
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(sec1Exp.test(privateKey));
|
|
|
|
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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(typeof publicKey, 'string');
|
|
assert(spkiExp.test(publicKey));
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(sec1EncExp('AES-128-CBC').test(privateKey));
|
|
|
|
// Since the private key is encrypted, signing shouldn't work anymore.
|
|
common.expectsError(() => testSignVerify(publicKey, privateKey), {
|
|
type: 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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(typeof publicKey, 'string');
|
|
assert(spkiExp.test(publicKey));
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(sec1EncExp('AES-128-CBC').test(privateKey));
|
|
|
|
// Since the private key is encrypted, signing shouldn't work anymore.
|
|
common.expectsError(() => testSignVerify(publicKey, privateKey), {
|
|
type: 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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(typeof publicKey, 'string');
|
|
assert(spkiExp.test(publicKey));
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(pkcs8EncExp.test(privateKey));
|
|
|
|
// Since the private key is encrypted, signing shouldn't work anymore.
|
|
common.expectsError(() => testSignVerify(publicKey, privateKey), {
|
|
type: 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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(typeof publicKey, 'string');
|
|
assert(spkiExp.test(publicKey));
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(pkcs8EncExp.test(privateKey));
|
|
|
|
// Since the private key is encrypted, signing shouldn't work anymore.
|
|
common.expectsError(() => testSignVerify(publicKey, privateKey), {
|
|
type: TypeError,
|
|
code: 'ERR_MISSING_PASSPHRASE',
|
|
message: 'Passphrase required for encrypted key'
|
|
});
|
|
|
|
testSignVerify(publicKey, {
|
|
key: privateKey,
|
|
passphrase: 'top secret'
|
|
});
|
|
}));
|
|
}
|
|
|
|
// Test invalid parameter encoding.
|
|
{
|
|
common.expectsError(() => generateKeyPairSync('ec', {
|
|
namedCurve: 'P-256',
|
|
paramEncoding: 'otherEncoding',
|
|
publicKeyEncoding: {
|
|
type: 'spki',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
cipher: 'aes-128-cbc',
|
|
passphrase: 'top secret'
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: 'The value "otherEncoding" is invalid for ' +
|
|
'option "paramEncoding"'
|
|
});
|
|
}
|
|
|
|
{
|
|
// 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(pkcs1PubExp.test(publicKey));
|
|
assertApproximateSize(publicKey, 180);
|
|
|
|
assert.strictEqual(typeof privateKey, 'string');
|
|
assert(pkcs1PrivExp.test(privateKey));
|
|
assertApproximateSize(privateKey, 512);
|
|
|
|
testEncryptDecrypt(publicKey, privateKey);
|
|
testSignVerify(publicKey, privateKey);
|
|
}));
|
|
}
|
|
|
|
{
|
|
// Test invalid key types.
|
|
for (const type of [undefined, null, 0]) {
|
|
common.expectsError(() => generateKeyPairSync(type, {}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message: 'The "type" argument must be of type string.' +
|
|
common.invalidArgTypeHelper(type)
|
|
});
|
|
}
|
|
|
|
common.expectsError(() => generateKeyPairSync('rsa2', {}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
message: "The argument 'type' must be a supported key type. Received 'rsa2'"
|
|
});
|
|
}
|
|
|
|
{
|
|
// Test keygen without options object.
|
|
common.expectsError(() => generateKeyPair('rsa', common.mustNotCall()), {
|
|
type: 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).
|
|
common.expectsError(() => generateKeyPair('ed448', 0, common.mustNotCall()), {
|
|
type: 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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
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.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
// 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]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: enc,
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${enc}" is invalid for option "publicKeyEncoding"`
|
|
});
|
|
}
|
|
|
|
// Missing publicKeyEncoding.type.
|
|
for (const type of [undefined, null, 0, true, {}]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type,
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${type}" is invalid for option ` +
|
|
'"publicKeyEncoding.type"'
|
|
});
|
|
}
|
|
|
|
// Missing / invalid publicKeyEncoding.format.
|
|
for (const format of [undefined, null, 0, false, 'a', {}]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${format}" is invalid for option ` +
|
|
'"publicKeyEncoding.format"'
|
|
});
|
|
}
|
|
|
|
// Invalid privateKeyEncoding.
|
|
for (const enc of [0, 'a', true]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: enc
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${enc}" is invalid for option "privateKeyEncoding"`
|
|
});
|
|
}
|
|
|
|
// Missing / invalid privateKeyEncoding.type.
|
|
for (const type of [undefined, null, 0, true, {}]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type,
|
|
format: 'pem'
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${type}" is invalid for option ` +
|
|
'"privateKeyEncoding.type"'
|
|
});
|
|
}
|
|
|
|
// Missing / invalid privateKeyEncoding.format.
|
|
for (const format of [undefined, null, 0, false, 'a', {}]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${format}" is invalid for option ` +
|
|
'"privateKeyEncoding.format"'
|
|
});
|
|
}
|
|
|
|
// Cipher of invalid type.
|
|
for (const cipher of [0, true, {}]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem',
|
|
cipher
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${cipher}" is invalid for option ` +
|
|
'"privateKeyEncoding.cipher"'
|
|
});
|
|
}
|
|
|
|
// Invalid cipher.
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
cipher: 'foo',
|
|
passphrase: 'secret'
|
|
}
|
|
}), {
|
|
type: Error,
|
|
message: 'Unknown cipher'
|
|
});
|
|
|
|
// Cipher, but no valid passphrase.
|
|
for (const passphrase of [undefined, null, 5, false, true]) {
|
|
common.expectsError(() => generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'pkcs1',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
cipher: 'aes-128-cbc',
|
|
passphrase
|
|
}
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${passphrase}" is invalid for option ` +
|
|
'"privateKeyEncoding.passphrase"'
|
|
});
|
|
}
|
|
|
|
// Test invalid callbacks.
|
|
for (const cb of [undefined, null, 0, {}]) {
|
|
common.expectsError(() => generateKeyPair('rsa', {
|
|
modulusLength: 512,
|
|
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
|
|
}, cb), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_CALLBACK'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test RSA parameters.
|
|
{
|
|
// Test invalid modulus lengths.
|
|
for (const modulusLength of [undefined, null, 'a', true, {}, [], 512.1, -1]) {
|
|
common.expectsError(() => generateKeyPair('rsa', {
|
|
modulusLength
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${modulusLength}" is invalid for option ` +
|
|
'"modulusLength"'
|
|
});
|
|
}
|
|
|
|
// Test invalid exponents.
|
|
for (const publicExponent of ['a', true, {}, [], 3.5, -1]) {
|
|
common.expectsError(() => generateKeyPair('rsa', {
|
|
modulusLength: 4096,
|
|
publicExponent
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${publicExponent}" is invalid for option ` +
|
|
'"publicExponent"'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test DSA parameters.
|
|
{
|
|
// Test invalid modulus lengths.
|
|
for (const modulusLength of [undefined, null, 'a', true, {}, [], 4096.1]) {
|
|
common.expectsError(() => generateKeyPair('dsa', {
|
|
modulusLength
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${modulusLength}" is invalid for option ` +
|
|
'"modulusLength"'
|
|
});
|
|
}
|
|
|
|
// Test invalid divisor lengths.
|
|
for (const divisorLength of ['a', true, {}, [], 4096.1]) {
|
|
common.expectsError(() => generateKeyPair('dsa', {
|
|
modulusLength: 2048,
|
|
divisorLength
|
|
}), {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${divisorLength}" is invalid for option ` +
|
|
'"divisorLength"'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test EC parameters.
|
|
{
|
|
// Test invalid curves.
|
|
common.expectsError(() => {
|
|
generateKeyPairSync('ec', {
|
|
namedCurve: 'abcdef',
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
|
});
|
|
}, {
|
|
type: TypeError,
|
|
message: 'Invalid ECDH curve name'
|
|
});
|
|
|
|
// Test error type when curve is not a string
|
|
for (const namedCurve of [true, {}, [], 123]) {
|
|
common.expectsError(() => {
|
|
generateKeyPairSync('ec', {
|
|
namedCurve,
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
|
});
|
|
}, {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${namedCurve}" is invalid for option ` +
|
|
'"namedCurve"'
|
|
});
|
|
}
|
|
|
|
// It should recognize both NIST and standard curve names.
|
|
generateKeyPair('ec', {
|
|
namedCurve: 'P-256',
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
}, common.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
}));
|
|
|
|
generateKeyPair('ec', {
|
|
namedCurve: 'secp256k1',
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
}, common.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
}));
|
|
}
|
|
|
|
// Test EdDSA key generation.
|
|
{
|
|
if (!/^1\.1\.0/.test(process.versions.openssl)) {
|
|
['ed25519', 'ed448', 'x25519', 'x448'].forEach((keyType) => {
|
|
generateKeyPair(keyType, common.mustCall((err, publicKey, privateKey) => {
|
|
assert.ifError(err);
|
|
|
|
assert.strictEqual(publicKey.type, 'public');
|
|
assert.strictEqual(publicKey.asymmetricKeyType, keyType);
|
|
|
|
assert.strictEqual(privateKey.type, 'private');
|
|
assert.strictEqual(privateKey.asymmetricKeyType, keyType);
|
|
}));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test invalid key encoding types.
|
|
{
|
|
// Invalid public key type.
|
|
for (const type of ['foo', 'pkcs8', 'sec1']) {
|
|
common.expectsError(() => {
|
|
generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: { type, format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
});
|
|
}, {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${type}" is invalid for option ` +
|
|
'"publicKeyEncoding.type"'
|
|
});
|
|
}
|
|
|
|
// Invalid hash value.
|
|
for (const hashValue of [123, true, {}, []]) {
|
|
common.expectsError(() => {
|
|
generateKeyPairSync('rsa-pss', {
|
|
modulusLength: 4096,
|
|
hash: hashValue
|
|
});
|
|
}, {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${hashValue}" is invalid for option "hash"`
|
|
});
|
|
}
|
|
|
|
// Invalid private key type.
|
|
for (const type of ['foo', 'spki']) {
|
|
common.expectsError(() => {
|
|
generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type, format: 'pem' }
|
|
});
|
|
}, {
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${type}" is invalid for option ` +
|
|
'"privateKeyEncoding.type"'
|
|
});
|
|
}
|
|
|
|
// Key encoding doesn't match key type.
|
|
for (const type of ['dsa', 'ec']) {
|
|
common.expectsError(() => {
|
|
generateKeyPairSync(type, {
|
|
modulusLength: 4096,
|
|
namedCurve: 'P-256',
|
|
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
});
|
|
}, {
|
|
type: Error,
|
|
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
|
message: 'The selected key encoding pkcs1 can only be used for RSA keys.'
|
|
});
|
|
|
|
common.expectsError(() => {
|
|
generateKeyPairSync(type, {
|
|
modulusLength: 4096,
|
|
namedCurve: 'P-256',
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
|
|
});
|
|
}, {
|
|
type: 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']) {
|
|
common.expectsError(() => {
|
|
generateKeyPairSync(type, {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
|
});
|
|
}, {
|
|
type: 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']) {
|
|
common.expectsError(() => {
|
|
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'
|
|
}
|
|
});
|
|
}, {
|
|
type: Error,
|
|
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
|
message: `The selected key encoding ${type} does not support encryption.`
|
|
});
|
|
}
|
|
}
|
|
{
|
|
// Test RSA-PSS.
|
|
common.expectsError(
|
|
() => {
|
|
generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: 16,
|
|
hash: 'sha256',
|
|
mgf1Hash: undefined
|
|
});
|
|
},
|
|
{
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_CALLBACK',
|
|
message: 'Callback must be a function. Received undefined'
|
|
}
|
|
);
|
|
|
|
for (const mgf1Hash of [null, 0, false, {}, []]) {
|
|
common.expectsError(
|
|
() => {
|
|
generateKeyPair('rsa-pss', {
|
|
modulusLength: 512,
|
|
saltLength: 16,
|
|
hash: 'sha256',
|
|
mgf1Hash
|
|
});
|
|
},
|
|
{
|
|
type: TypeError,
|
|
code: 'ERR_INVALID_OPT_VALUE',
|
|
message: `The value "${mgf1Hash}" is invalid for option "mgf1Hash"`
|
|
}
|
|
);
|
|
}
|
|
}
|