mirror of
https://github.com/nodejs/node.git
synced 2024-11-30 15:30:56 +01:00
f692299764
Fix KeyObject handle type error message. Add test to cover KeyObject ERR_INVALID_ARG_TYPE exception. PR-URL: https://github.com/nodejs/node/pull/27904 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
355 lines
10 KiB
JavaScript
355 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const { Object } = primordials;
|
|
|
|
const {
|
|
KeyObject: KeyObjectHandle,
|
|
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_INVALID_OPT_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];
|
|
|
|
class KeyObject {
|
|
constructor(type, handle) {
|
|
if (type !== 'secret' && type !== 'public' && type !== 'private')
|
|
throw new ERR_INVALID_ARG_VALUE('type', type);
|
|
if (typeof handle !== 'object')
|
|
throw new ERR_INVALID_ARG_TYPE('handle', 'object', handle);
|
|
|
|
this[kKeyType] = type;
|
|
|
|
Object.defineProperty(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);
|
|
}
|
|
}
|
|
|
|
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_OPT_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_OPT_VALUE(optionName, typeStr);
|
|
}
|
|
|
|
function option(name, objName) {
|
|
return objName === undefined ? name : `${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_OPT_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_OPT_VALUE(option('cipher', objName), cipher);
|
|
}
|
|
}
|
|
|
|
if ((isInput && passphrase !== undefined &&
|
|
!isStringOrBuffer(passphrase)) ||
|
|
(!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
|
|
throw new ERR_INVALID_OPT_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',
|
|
['string', 'Buffer', 'TypedArray', 'DataView',
|
|
...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
|
|
key);
|
|
}
|
|
return { data, ...parseKeyEncoding(key, undefined) };
|
|
} else {
|
|
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];
|
|
} else {
|
|
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(kKeyTypeSecret);
|
|
handle.init(key);
|
|
return new SecretKeyObject(handle);
|
|
}
|
|
|
|
function createPublicKey(key) {
|
|
const { format, type, data } = prepareAsymmetricKey(key, kCreatePublic);
|
|
const handle = new KeyObjectHandle(kKeyTypePublic);
|
|
handle.init(data, format, type);
|
|
return new PublicKeyObject(handle);
|
|
}
|
|
|
|
function createPrivateKey(key) {
|
|
const { format, type, data, passphrase } =
|
|
prepareAsymmetricKey(key, kCreatePrivate);
|
|
const handle = new KeyObjectHandle(kKeyTypePrivate);
|
|
handle.init(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
|
|
};
|