mirror of
https://github.com/nodejs/node.git
synced 2025-04-30 23:56:58 +00:00

This will be a start to generalize all argument validation errors. As currently we throw ARG/OPT, OUT_OF_RANGE, and other more specific errors. The OPT errors didn't bring much to the errors as it's just another variant of ARG error which is sometimes more confusing (some of our code used OPT errors to denote just argument validation errors presumably because of similarity of OPT to 'option' and not 'options-object') and they don't specify the name of the options object where the invalid value is located. Much better approach would be to just specify path to the invalid value in the name of the value as it is done in this PR (i.e. 'options.format', 'options.publicKey.type' etc) Also since this decreases a variety of errors we have it'd be easier to reuse validation code across the codebase. Refs: https://github.com/nodejs/node/pull/31251 Refs: https://github.com/nodejs/node/pull/34070#discussion_r467251009 Signed-off-by: Denys Otrishko <shishugi@gmail.com> PR-URL: https://github.com/nodejs/node/pull/34682 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
377 lines
11 KiB
JavaScript
377 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ObjectDefineProperty,
|
|
Symbol,
|
|
} = primordials;
|
|
|
|
const {
|
|
KeyObjectHandle,
|
|
createNativeKeyObjectClass,
|
|
kKeyTypeSecret,
|
|
kKeyTypePublic,
|
|
kKeyTypePrivate,
|
|
kKeyFormatPEM,
|
|
kKeyFormatDER,
|
|
kKeyEncodingPKCS1,
|
|
kKeyEncodingPKCS8,
|
|
kKeyEncodingSPKI,
|
|
kKeyEncodingSEC1
|
|
} = internalBinding('crypto');
|
|
const {
|
|
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
|
|
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_OUT_OF_RANGE
|
|
} = require('internal/errors').codes;
|
|
const { kHandle } = require('internal/crypto/util');
|
|
|
|
const { isArrayBufferView } = require('internal/util/types');
|
|
|
|
const kKeyType = Symbol('kKeyType');
|
|
|
|
// Key input contexts.
|
|
const kConsumePublic = 0;
|
|
const kConsumePrivate = 1;
|
|
const kCreatePublic = 2;
|
|
const kCreatePrivate = 3;
|
|
|
|
const encodingNames = [];
|
|
for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
|
|
[kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
|
|
encodingNames[m[0]] = m[1];
|
|
|
|
// Creating the KeyObject class is a little complicated due to inheritance
|
|
// and that fact that KeyObjects should be transferrable between threads,
|
|
// which requires the KeyObject base class to be implemented in C++.
|
|
// The creation requires a callback to make sure that the NativeKeyObject
|
|
// base class cannot exist without the other KeyObject implementations.
|
|
const [
|
|
KeyObject,
|
|
SecretKeyObject,
|
|
PublicKeyObject,
|
|
PrivateKeyObject
|
|
] = createNativeKeyObjectClass((NativeKeyObject) => {
|
|
// Publicly visible KeyObject class.
|
|
class KeyObject extends NativeKeyObject {
|
|
constructor(type, handle) {
|
|
if (type !== 'secret' && type !== 'public' && type !== 'private')
|
|
throw new ERR_INVALID_ARG_VALUE('type', type);
|
|
if (typeof handle !== 'object' || !(handle instanceof KeyObjectHandle))
|
|
throw new ERR_INVALID_ARG_TYPE('handle', 'object', handle);
|
|
|
|
super(handle);
|
|
|
|
this[kKeyType] = type;
|
|
|
|
ObjectDefineProperty(this, kHandle, {
|
|
value: handle,
|
|
enumerable: false,
|
|
configurable: false,
|
|
writable: false
|
|
});
|
|
}
|
|
|
|
get type() {
|
|
return this[kKeyType];
|
|
}
|
|
}
|
|
|
|
class SecretKeyObject extends KeyObject {
|
|
constructor(handle) {
|
|
super('secret', handle);
|
|
}
|
|
|
|
get symmetricKeySize() {
|
|
return this[kHandle].getSymmetricKeySize();
|
|
}
|
|
|
|
export() {
|
|
return this[kHandle].export();
|
|
}
|
|
}
|
|
|
|
const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
|
|
|
|
class AsymmetricKeyObject extends KeyObject {
|
|
get asymmetricKeyType() {
|
|
return this[kAsymmetricKeyType] ||
|
|
(this[kAsymmetricKeyType] = this[kHandle].getAsymmetricKeyType());
|
|
}
|
|
}
|
|
|
|
class PublicKeyObject extends AsymmetricKeyObject {
|
|
constructor(handle) {
|
|
super('public', handle);
|
|
}
|
|
|
|
export(encoding) {
|
|
const {
|
|
format,
|
|
type
|
|
} = parsePublicKeyEncoding(encoding, this.asymmetricKeyType);
|
|
return this[kHandle].export(format, type);
|
|
}
|
|
}
|
|
|
|
class PrivateKeyObject extends AsymmetricKeyObject {
|
|
constructor(handle) {
|
|
super('private', handle);
|
|
}
|
|
|
|
export(encoding) {
|
|
const {
|
|
format,
|
|
type,
|
|
cipher,
|
|
passphrase
|
|
} = parsePrivateKeyEncoding(encoding, this.asymmetricKeyType);
|
|
return this[kHandle].export(format, type, cipher, passphrase);
|
|
}
|
|
}
|
|
|
|
return [KeyObject, SecretKeyObject, PublicKeyObject, PrivateKeyObject];
|
|
});
|
|
|
|
function parseKeyFormat(formatStr, defaultFormat, optionName) {
|
|
if (formatStr === undefined && defaultFormat !== undefined)
|
|
return defaultFormat;
|
|
else if (formatStr === 'pem')
|
|
return kKeyFormatPEM;
|
|
else if (formatStr === 'der')
|
|
return kKeyFormatDER;
|
|
throw new ERR_INVALID_ARG_VALUE(optionName, formatStr);
|
|
}
|
|
|
|
function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
|
|
if (typeStr === undefined && !required) {
|
|
return undefined;
|
|
} else if (typeStr === 'pkcs1') {
|
|
if (keyType !== undefined && keyType !== 'rsa') {
|
|
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
|
typeStr, 'can only be used for RSA keys');
|
|
}
|
|
return kKeyEncodingPKCS1;
|
|
} else if (typeStr === 'spki' && isPublic !== false) {
|
|
return kKeyEncodingSPKI;
|
|
} else if (typeStr === 'pkcs8' && isPublic !== true) {
|
|
return kKeyEncodingPKCS8;
|
|
} else if (typeStr === 'sec1' && isPublic !== true) {
|
|
if (keyType !== undefined && keyType !== 'ec') {
|
|
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
|
typeStr, 'can only be used for EC keys');
|
|
}
|
|
return kKeyEncodingSEC1;
|
|
}
|
|
|
|
throw new ERR_INVALID_ARG_VALUE(optionName, typeStr);
|
|
}
|
|
|
|
function option(name, objName) {
|
|
return objName === undefined ?
|
|
`options.${name}` : `options.${objName}.${name}`;
|
|
}
|
|
|
|
function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
|
|
const { format: formatStr, type: typeStr } = enc;
|
|
|
|
const isInput = keyType === undefined;
|
|
const format = parseKeyFormat(formatStr,
|
|
isInput ? kKeyFormatPEM : undefined,
|
|
option('format', objName));
|
|
|
|
const type = parseKeyType(typeStr,
|
|
!isInput || format === kKeyFormatDER,
|
|
keyType,
|
|
isPublic,
|
|
option('type', objName));
|
|
|
|
return { format, type };
|
|
}
|
|
|
|
function isStringOrBuffer(val) {
|
|
return typeof val === 'string' || isArrayBufferView(val);
|
|
}
|
|
|
|
function parseKeyEncoding(enc, keyType, isPublic, objName) {
|
|
if (enc === null || typeof enc !== 'object')
|
|
throw new ERR_INVALID_ARG_TYPE('options', 'object', enc);
|
|
|
|
const isInput = keyType === undefined;
|
|
|
|
const {
|
|
format,
|
|
type
|
|
} = parseKeyFormatAndType(enc, keyType, isPublic, objName);
|
|
|
|
let cipher, passphrase;
|
|
if (isPublic !== true) {
|
|
({ cipher, passphrase } = enc);
|
|
|
|
if (!isInput) {
|
|
if (cipher != null) {
|
|
if (typeof cipher !== 'string')
|
|
throw new ERR_INVALID_ARG_VALUE(option('cipher', objName), cipher);
|
|
if (format === kKeyFormatDER &&
|
|
(type === kKeyEncodingPKCS1 ||
|
|
type === kKeyEncodingSEC1)) {
|
|
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
|
encodingNames[type], 'does not support encryption');
|
|
}
|
|
} else if (passphrase !== undefined) {
|
|
throw new ERR_INVALID_ARG_VALUE(option('cipher', objName), cipher);
|
|
}
|
|
}
|
|
|
|
if ((isInput && passphrase !== undefined &&
|
|
!isStringOrBuffer(passphrase)) ||
|
|
(!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
|
|
throw new ERR_INVALID_ARG_VALUE(option('passphrase', objName),
|
|
passphrase);
|
|
}
|
|
}
|
|
|
|
return { format, type, cipher, passphrase };
|
|
}
|
|
|
|
// Parses the public key encoding based on an object. keyType must be undefined
|
|
// when this is used to parse an input encoding and must be a valid key type if
|
|
// used to parse an output encoding.
|
|
function parsePublicKeyEncoding(enc, keyType, objName) {
|
|
return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName);
|
|
}
|
|
|
|
// Parses the private key encoding based on an object. keyType must be undefined
|
|
// when this is used to parse an input encoding and must be a valid key type if
|
|
// used to parse an output encoding.
|
|
function parsePrivateKeyEncoding(enc, keyType, objName) {
|
|
return parseKeyEncoding(enc, keyType, false, objName);
|
|
}
|
|
|
|
function getKeyObjectHandle(key, ctx) {
|
|
if (ctx === kCreatePrivate) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'key',
|
|
['string', 'Buffer', 'TypedArray', 'DataView'],
|
|
key
|
|
);
|
|
}
|
|
|
|
if (key.type !== 'private') {
|
|
if (ctx === kConsumePrivate || ctx === kCreatePublic)
|
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'private');
|
|
if (key.type !== 'public') {
|
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type,
|
|
'private or public');
|
|
}
|
|
}
|
|
|
|
return key[kHandle];
|
|
}
|
|
|
|
function prepareAsymmetricKey(key, ctx) {
|
|
if (isKeyObject(key)) {
|
|
// Best case: A key object, as simple as that.
|
|
return { data: getKeyObjectHandle(key, ctx) };
|
|
} else if (typeof key === 'string' || isArrayBufferView(key)) {
|
|
// Expect PEM by default, mostly for backward compatibility.
|
|
return { format: kKeyFormatPEM, data: key };
|
|
} else if (typeof key === 'object') {
|
|
const data = key.key;
|
|
// The 'key' property can be a KeyObject as well to allow specifying
|
|
// additional options such as padding along with the key.
|
|
if (isKeyObject(data))
|
|
return { data: getKeyObjectHandle(data, ctx) };
|
|
// Either PEM or DER using PKCS#1 or SPKI.
|
|
if (!isStringOrBuffer(data)) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'key.key',
|
|
['string', 'Buffer', 'TypedArray', 'DataView',
|
|
...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
|
|
data);
|
|
}
|
|
|
|
const isPublic =
|
|
(ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined;
|
|
return { data, ...parseKeyEncoding(key, undefined, isPublic) };
|
|
}
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'key',
|
|
['string', 'Buffer', 'TypedArray', 'DataView',
|
|
...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
|
|
key
|
|
);
|
|
}
|
|
|
|
function preparePrivateKey(key) {
|
|
return prepareAsymmetricKey(key, kConsumePrivate);
|
|
}
|
|
|
|
function preparePublicOrPrivateKey(key) {
|
|
return prepareAsymmetricKey(key, kConsumePublic);
|
|
}
|
|
|
|
function prepareSecretKey(key, bufferOnly = false) {
|
|
if (!isArrayBufferView(key) && (bufferOnly || typeof key !== 'string')) {
|
|
if (isKeyObject(key) && !bufferOnly) {
|
|
if (key.type !== 'secret')
|
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
|
|
return key[kHandle];
|
|
}
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'key',
|
|
['Buffer', 'TypedArray', 'DataView',
|
|
...(bufferOnly ? [] : ['string', 'KeyObject'])],
|
|
key);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
function createSecretKey(key) {
|
|
key = prepareSecretKey(key, true);
|
|
if (key.byteLength === 0)
|
|
throw new ERR_OUT_OF_RANGE('key.byteLength', '> 0', key.byteLength);
|
|
const handle = new KeyObjectHandle();
|
|
handle.init(kKeyTypeSecret, key);
|
|
return new SecretKeyObject(handle);
|
|
}
|
|
|
|
function createPublicKey(key) {
|
|
const { format, type, data } = prepareAsymmetricKey(key, kCreatePublic);
|
|
const handle = new KeyObjectHandle();
|
|
handle.init(kKeyTypePublic, data, format, type);
|
|
return new PublicKeyObject(handle);
|
|
}
|
|
|
|
function createPrivateKey(key) {
|
|
const { format, type, data, passphrase } =
|
|
prepareAsymmetricKey(key, kCreatePrivate);
|
|
const handle = new KeyObjectHandle();
|
|
handle.init(kKeyTypePrivate, data, format, type, passphrase);
|
|
return new PrivateKeyObject(handle);
|
|
}
|
|
|
|
function isKeyObject(key) {
|
|
return key instanceof KeyObject;
|
|
}
|
|
|
|
module.exports = {
|
|
// Public API.
|
|
createSecretKey,
|
|
createPublicKey,
|
|
createPrivateKey,
|
|
KeyObject,
|
|
|
|
// These are designed for internal use only and should not be exposed.
|
|
parsePublicKeyEncoding,
|
|
parsePrivateKeyEncoding,
|
|
preparePrivateKey,
|
|
preparePublicOrPrivateKey,
|
|
prepareSecretKey,
|
|
SecretKeyObject,
|
|
PublicKeyObject,
|
|
PrivateKeyObject,
|
|
isKeyObject
|
|
};
|