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

Adds [JWK](https://tools.ietf.org/html/rfc7517) keyObject.export format option. Supported key types: `ec`, `rsa`, `ed25519`, `ed448`, `x25519`, `x448`, and symmetric keys, resulting in JWK `kty` (Key Type) values `EC`, `RSA`, `OKP`, and `oct`. `rsa-pss` is not supported since the JWK format does not support PSS Parameters. `EC` JWK curves supported are `P-256`, `secp256k1`, `P-384`, and `P-521` PR-URL: https://github.com/nodejs/node/pull/37081 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
620 lines
18 KiB
JavaScript
620 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayFrom,
|
|
ObjectDefineProperty,
|
|
ObjectSetPrototypeOf,
|
|
Symbol,
|
|
Uint8Array,
|
|
} = primordials;
|
|
|
|
const {
|
|
KeyObjectHandle,
|
|
createNativeKeyObjectClass,
|
|
kKeyTypeSecret,
|
|
kKeyTypePublic,
|
|
kKeyTypePrivate,
|
|
kKeyFormatPEM,
|
|
kKeyFormatDER,
|
|
kKeyEncodingPKCS1,
|
|
kKeyEncodingPKCS8,
|
|
kKeyEncodingSPKI,
|
|
kKeyEncodingSEC1,
|
|
} = internalBinding('crypto');
|
|
|
|
const {
|
|
validateObject,
|
|
validateOneOf,
|
|
} = require('internal/validators');
|
|
|
|
const {
|
|
codes: {
|
|
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
|
|
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_OUT_OF_RANGE,
|
|
ERR_OPERATION_FAILED,
|
|
ERR_CRYPTO_JWK_UNSUPPORTED_CURVE,
|
|
ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE,
|
|
}
|
|
} = require('internal/errors');
|
|
|
|
const {
|
|
kHandle,
|
|
kKeyObject,
|
|
getArrayBufferOrView,
|
|
bigIntArrayToUnsignedBigInt,
|
|
} = require('internal/crypto/util');
|
|
|
|
const {
|
|
isAnyArrayBuffer,
|
|
isArrayBufferView,
|
|
} = require('internal/util/types');
|
|
|
|
const {
|
|
JSTransferable,
|
|
kClone,
|
|
kDeserialize,
|
|
} = require('internal/worker/js_transferable');
|
|
|
|
const {
|
|
customInspectSymbol: kInspect,
|
|
} = require('internal/util');
|
|
|
|
const { inspect } = require('internal/util/inspect');
|
|
|
|
const kAlgorithm = Symbol('kAlgorithm');
|
|
const kExtractable = Symbol('kExtractable');
|
|
const kKeyType = Symbol('kKeyType');
|
|
const kKeyUsages = Symbol('kKeyUsages');
|
|
|
|
// 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];
|
|
}
|
|
|
|
static from(key) {
|
|
if (!isCryptoKey(key))
|
|
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
|
|
return key[kKeyObject];
|
|
}
|
|
}
|
|
|
|
class SecretKeyObject extends KeyObject {
|
|
constructor(handle) {
|
|
super('secret', handle);
|
|
}
|
|
|
|
get symmetricKeySize() {
|
|
return this[kHandle].getSymmetricKeySize();
|
|
}
|
|
|
|
export(options) {
|
|
if (options !== undefined) {
|
|
validateObject(options, 'options');
|
|
validateOneOf(
|
|
options.format, 'options.format', [undefined, 'buffer', 'jwk']);
|
|
if (options.format === 'jwk') {
|
|
return this[kHandle].exportJwk({});
|
|
}
|
|
}
|
|
return this[kHandle].export();
|
|
}
|
|
}
|
|
|
|
const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
|
|
const kAsymmetricKeyDetails = Symbol('kAsymmetricKeyDetails');
|
|
const kAsymmetricKeyJWKProperties = Symbol('kAsymmetricKeyJWKProperties');
|
|
|
|
function normalizeKeyDetails(details = {}) {
|
|
if (details.publicExponent !== undefined) {
|
|
return {
|
|
...details,
|
|
publicExponent:
|
|
bigIntArrayToUnsignedBigInt(new Uint8Array(details.publicExponent))
|
|
};
|
|
}
|
|
return details;
|
|
}
|
|
|
|
class AsymmetricKeyObject extends KeyObject {
|
|
get asymmetricKeyType() {
|
|
return this[kAsymmetricKeyType] ||
|
|
(this[kAsymmetricKeyType] = this[kHandle].getAsymmetricKeyType());
|
|
}
|
|
|
|
get asymmetricKeyDetails() {
|
|
switch (this.asymmetricKeyType) {
|
|
case 'rsa':
|
|
case 'rsa-pss':
|
|
case 'dsa':
|
|
case 'ec':
|
|
return this[kAsymmetricKeyDetails] ||
|
|
(this[kAsymmetricKeyDetails] = normalizeKeyDetails(
|
|
this[kHandle].keyDetail({})
|
|
));
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
[kAsymmetricKeyJWKProperties]() {
|
|
switch (this.asymmetricKeyType) {
|
|
case 'rsa': return {};
|
|
case 'ec':
|
|
switch (this.asymmetricKeyDetails.namedCurve) {
|
|
case 'prime256v1': return { crv: 'P-256' };
|
|
case 'secp256k1': return { crv: 'secp256k1' };
|
|
case 'secp384r1': return { crv: 'P-384' };
|
|
case 'secp521r1': return { crv: 'P-521' };
|
|
default:
|
|
throw new ERR_CRYPTO_JWK_UNSUPPORTED_CURVE(
|
|
this.asymmetricKeyDetails.namedCurve);
|
|
}
|
|
case 'ed25519': return { crv: 'Ed25519' };
|
|
case 'ed448': return { crv: 'Ed448' };
|
|
case 'x25519': return { crv: 'X25519' };
|
|
case 'x448': return { crv: 'X448' };
|
|
default:
|
|
throw new ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE();
|
|
}
|
|
}
|
|
}
|
|
|
|
class PublicKeyObject extends AsymmetricKeyObject {
|
|
constructor(handle) {
|
|
super('public', handle);
|
|
}
|
|
|
|
export(options) {
|
|
if (options && options.format === 'jwk') {
|
|
const properties = this[kAsymmetricKeyJWKProperties]();
|
|
return this[kHandle].exportJwk(properties);
|
|
}
|
|
const {
|
|
format,
|
|
type
|
|
} = parsePublicKeyEncoding(options, this.asymmetricKeyType);
|
|
return this[kHandle].export(format, type);
|
|
}
|
|
}
|
|
|
|
class PrivateKeyObject extends AsymmetricKeyObject {
|
|
constructor(handle) {
|
|
super('private', handle);
|
|
}
|
|
|
|
export(options) {
|
|
if (options && options.format === 'jwk') {
|
|
if (options.passphrase !== undefined) {
|
|
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
|
'jwk', 'does not support encryption');
|
|
}
|
|
const properties = this[kAsymmetricKeyJWKProperties]();
|
|
return this[kHandle].exportJwk(properties);
|
|
}
|
|
const {
|
|
format,
|
|
type,
|
|
cipher,
|
|
passphrase
|
|
} = parsePrivateKeyEncoding(options, 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) ||
|
|
isAnyArrayBuffer(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, encoding;
|
|
if (isPublic !== true) {
|
|
({ cipher, passphrase, encoding } = 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);
|
|
}
|
|
}
|
|
|
|
if (passphrase !== undefined)
|
|
passphrase = getArrayBufferOrView(passphrase, 'key.passphrase', encoding);
|
|
|
|
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', 'ArrayBuffer', '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 getKeyTypes(allowKeyObject, bufferOnly = false) {
|
|
return [
|
|
'ArrayBuffer',
|
|
'Buffer',
|
|
'TypedArray',
|
|
'DataView',
|
|
...(!bufferOnly ? ['string'] : []),
|
|
...(!bufferOnly && allowKeyObject ? ['KeyObject', 'CryptoKey'] : [])];
|
|
}
|
|
|
|
function prepareAsymmetricKey(key, ctx) {
|
|
if (isKeyObject(key)) {
|
|
// Best case: A key object, as simple as that.
|
|
return { data: getKeyObjectHandle(key, ctx) };
|
|
} else if (isCryptoKey(key)) {
|
|
return { data: getKeyObjectHandle(key[kKeyObject], ctx) };
|
|
} else if (isStringOrBuffer(key)) {
|
|
// Expect PEM by default, mostly for backward compatibility.
|
|
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') };
|
|
} else if (typeof key === 'object') {
|
|
const { key: data, encoding } = 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) };
|
|
else if (isCryptoKey(data))
|
|
return { data: getKeyObjectHandle(data[kKeyObject], ctx) };
|
|
// Either PEM or DER using PKCS#1 or SPKI.
|
|
if (!isStringOrBuffer(data)) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'key.key',
|
|
getKeyTypes(ctx !== kCreatePrivate),
|
|
data);
|
|
}
|
|
|
|
const isPublic =
|
|
(ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined;
|
|
return {
|
|
data: getArrayBufferOrView(data, 'key', encoding),
|
|
...parseKeyEncoding(key, undefined, isPublic)
|
|
};
|
|
}
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'key',
|
|
getKeyTypes(ctx !== kCreatePrivate),
|
|
key);
|
|
}
|
|
|
|
function preparePrivateKey(key) {
|
|
return prepareAsymmetricKey(key, kConsumePrivate);
|
|
}
|
|
|
|
function preparePublicOrPrivateKey(key) {
|
|
return prepareAsymmetricKey(key, kConsumePublic);
|
|
}
|
|
|
|
function prepareSecretKey(key, encoding, bufferOnly = false) {
|
|
if (!bufferOnly) {
|
|
if (isKeyObject(key)) {
|
|
if (key.type !== 'secret')
|
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
|
|
return key[kHandle];
|
|
} else if (isCryptoKey(key)) {
|
|
if (key.type !== 'secret')
|
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
|
|
return key[kKeyObject][kHandle];
|
|
}
|
|
}
|
|
if (typeof key !== 'string' &&
|
|
!isArrayBufferView(key) &&
|
|
!isAnyArrayBuffer(key)) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'key',
|
|
getKeyTypes(!bufferOnly, bufferOnly),
|
|
key);
|
|
}
|
|
return getArrayBufferOrView(key, 'key', encoding);
|
|
}
|
|
|
|
function createSecretKey(key, encoding) {
|
|
key = prepareSecretKey(key, encoding, 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, passphrase } =
|
|
prepareAsymmetricKey(key, kCreatePublic);
|
|
const handle = new KeyObjectHandle();
|
|
handle.init(kKeyTypePublic, data, format, type, passphrase);
|
|
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;
|
|
}
|
|
|
|
// Our implementation of CryptoKey is a simple wrapper around a KeyObject
|
|
// that adapts it to the standard interface. This implementation also
|
|
// extends the JSTransferable class, allowing the CryptoKey to be cloned
|
|
// to Workers.
|
|
// TODO(@jasnell): Embedder environments like electron may have issues
|
|
// here similar to other things like URL. A chromium provided CryptoKey
|
|
// will not be recognized as a Node.js CryptoKey, and vice versa. It
|
|
// would be fantastic if we could find a way of making those interop.
|
|
class CryptoKey extends JSTransferable {
|
|
constructor() {
|
|
throw new ERR_OPERATION_FAILED('Illegal constructor');
|
|
}
|
|
|
|
[kInspect](depth, options) {
|
|
if (depth < 0)
|
|
return this;
|
|
|
|
const opts = {
|
|
...options,
|
|
depth: options.depth == null ? null : options.depth - 1
|
|
};
|
|
|
|
return `CryptoKey ${inspect({
|
|
type: this.type,
|
|
extractable: this.extractable,
|
|
algorithm: this.algorithm,
|
|
usages: this.usages
|
|
}, opts)}`;
|
|
}
|
|
|
|
get type() {
|
|
return this[kKeyObject].type;
|
|
}
|
|
|
|
get extractable() {
|
|
return this[kExtractable];
|
|
}
|
|
|
|
get algorithm() {
|
|
return this[kAlgorithm];
|
|
}
|
|
|
|
get usages() {
|
|
return ArrayFrom(this[kKeyUsages]);
|
|
}
|
|
|
|
[kClone]() {
|
|
const keyObject = this[kKeyObject];
|
|
const algorithm = this.algorithm;
|
|
const extractable = this.extractable;
|
|
const usages = this.usages;
|
|
|
|
return {
|
|
data: {
|
|
keyObject,
|
|
algorithm,
|
|
usages,
|
|
extractable,
|
|
},
|
|
deserializeInfo: 'internal/crypto/keys:InternalCryptoKey'
|
|
};
|
|
}
|
|
|
|
[kDeserialize]({ keyObject, algorithm, usages, extractable }) {
|
|
this[kKeyObject] = keyObject;
|
|
this[kAlgorithm] = algorithm;
|
|
this[kKeyUsages] = usages;
|
|
this[kExtractable] = extractable;
|
|
}
|
|
}
|
|
|
|
// All internal code must use new InternalCryptoKey to create
|
|
// CryptoKey instances. The CryptoKey class is exposed to end
|
|
// user code but is not permitted to be constructed directly.
|
|
class InternalCryptoKey extends JSTransferable {
|
|
constructor(
|
|
keyObject,
|
|
algorithm,
|
|
keyUsages,
|
|
extractable) {
|
|
super();
|
|
// Using symbol properties here currently instead of private
|
|
// properties because (for now) the performance penalty of
|
|
// private fields is still too high.
|
|
this[kKeyObject] = keyObject;
|
|
this[kAlgorithm] = algorithm;
|
|
this[kExtractable] = extractable;
|
|
this[kKeyUsages] = keyUsages;
|
|
}
|
|
}
|
|
|
|
InternalCryptoKey.prototype.constructor = CryptoKey;
|
|
ObjectSetPrototypeOf(InternalCryptoKey.prototype, CryptoKey.prototype);
|
|
|
|
function isCryptoKey(obj) {
|
|
return obj != null && obj[kKeyObject] !== undefined;
|
|
}
|
|
|
|
module.exports = {
|
|
// Public API.
|
|
createSecretKey,
|
|
createPublicKey,
|
|
createPrivateKey,
|
|
KeyObject,
|
|
CryptoKey,
|
|
InternalCryptoKey,
|
|
|
|
// These are designed for internal use only and should not be exposed.
|
|
parsePublicKeyEncoding,
|
|
parsePrivateKeyEncoding,
|
|
parseKeyEncoding,
|
|
preparePrivateKey,
|
|
preparePublicOrPrivateKey,
|
|
prepareSecretKey,
|
|
SecretKeyObject,
|
|
PublicKeyObject,
|
|
PrivateKeyObject,
|
|
isKeyObject,
|
|
isCryptoKey,
|
|
};
|