current commit
260925.31 · crypto: use the public CryptoKey prototype
'use strict';

const {
  ArrayPrototypeMap,
  ObjectDefineProperties,
  ObjectPrototypeHasOwnProperty,
  ObjectSetPrototypeOf,
  StringPrototypeIncludes,
  StringPrototypeStartsWith,
  SymbolToStringTag,
  TypedArrayPrototypeIncludes,
  Uint8Array,
} = primordials;

const {
  KeyObjectHandle,
  createNativeKeyObjectClass,
  // eslint-disable-next-line no-restricted-syntax -- intended here
  getKeyObjectSlots: nativeGetKeyObjectSlots,
  isKeyObject: isNativeKeyObject,
  createCryptoKeyClass,
  // eslint-disable-next-line no-restricted-syntax -- intended here
  getCryptoKeySlots: nativeGetCryptoKeySlots,
  isCryptoKey: isNativeCryptoKey,
  kKeyTypeSecret,
  kKeyTypePublic,
  kKeyTypePrivate,
  kKeyFormatPEM,
  kKeyFormatDER,
  kKeyFormatJWK,
  kKeyFormatRawPublic,
  kKeyFormatRawPrivate,
  kKeyFormatRawSeed,
  kKeyFormatStore,
  kKeyEncodingPKCS1,
  kKeyEncodingPKCS8,
  kKeyEncodingSPKI,
  kKeyEncodingSEC1,
  parsePKCS12: _parsePKCS12,
} = internalBinding('crypto');

const {
  crypto: {
    POINT_CONVERSION_COMPRESSED,
    POINT_CONVERSION_UNCOMPRESSED,
  },
} = internalBinding('constants');

const {
  validateObject,
  validateOneOf,
  validateString,
} = require('internal/validators');

const {
  codes: {
    ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
    ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
    ERR_ILLEGAL_CONSTRUCTOR,
    ERR_INVALID_ARG_TYPE,
    ERR_INVALID_ARG_VALUE,
    ERR_INVALID_THIS,
  },
} = require('internal/errors');

const {
  getArrayBufferOrView,
  getBufferSourceBytes,
  bigIntArrayToUnsignedBigInt,
  normalizeAlgorithm,
  hasAnyNotIn,
  getUsagesMask,
  getUsagesFromMask,
  hasUsage,
  toUsagesSet,
} = require('internal/crypto/util');

const {
  isAnyArrayBuffer,
  isArrayBufferView,
} = require('internal/util/types');

const {
  fileURLToPath,
  getURLHref,
  isURLInstance,
} = require('internal/url');

const {
  customInspectSymbol: kInspect,
  kEnumerableProperty,
  kEmptyObject,
  lazyDOMException,
} = require('internal/util');

const { inspect } = require('internal/util/inspect');
const { Buffer } = require('buffer');

// 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];

// KeyObject state lives on the native NativeKeyObject base class. JS caches
// the constructor-known type enum in a private field, then replaces it with
// the native type and KeyObjectHandle tuple when the handle is first needed.
// No forgeable own Symbols are exposed on public KeyObject instances.
let getKeyObjectSlots; // Populated by the createNativeKeyObjectClass callback.
let isKeyObject;
let getKeyObjectTypeValue;

const kKeyObjectSlotType = 0;
const kKeyObjectSlotHandle = 1;
// The native slot tuple stops at kKeyObjectSlotHandle. The remaining entries
// are JS-side lazy cache slots derived from the KeyObjectHandle on first use.
const kKeyObjectSlotSymmetricKeySize = 2;
const kKeyObjectSlotAsymmetricKeyType = 3;
const kKeyObjectSlotAsymmetricKeyDetails = 4;

function normalizeKeyDetails(details = kEmptyObject) {
  if (details.publicExponent !== undefined) {
    return {
      __proto__: null,
      ...details,
      publicExponent:
        bigIntArrayToUnsignedBigInt(new Uint8Array(details.publicExponent)),
    };
  }
  return details;
}

// Creating the KeyObject class is a little complicated due to inheritance
// and the fact that KeyObjects should be transferable 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 {
  0: KeyObject,
  1: SecretKeyObject,
  2: PublicKeyObject,
  3: PrivateKeyObject,
} = createNativeKeyObjectClass((NativeKeyObject) => {
  let webidl;

  // Publicly visible KeyObject class.
  class KeyObject extends NativeKeyObject {
    #slots;

    constructor(type, handle) {
      let keyType;
      switch (type) {
        case 'secret': keyType = kKeyTypeSecret; break;
        case 'public': keyType = kKeyTypePublic; break;
        case 'private': keyType = kKeyTypePrivate; break;
        default: 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.#slots = keyType;
    }

    get type() {
      return getKeyObjectType(this);
    }

    toCryptoKey(algorithm, extractable, keyUsages) {
      webidl ??= require('internal/crypto/webidl');
      algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey');
      extractable = webidl.converters.boolean(extractable);
      keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages);

      const type = getKeyObjectType(this);
      const handle = getKeyObjectHandle(this);
      switch (type) {
        case 'secret':
          return toCryptoKeySecret(
            handle,
            algorithm,
            extractable,
            keyUsages);
        case 'public':
          // Fall through
        case 'private':
          return toCryptoKey(
            handle,
            algorithm,
            extractable,
            keyUsages);
      }
    }

    static from(key) {
      if (!isCryptoKey(key))
        throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
      if (!getCryptoKeyExtractable(key)) {
        throw new ERR_INVALID_ARG_VALUE(
          'key',
          key,
          'must be an extractable CryptoKey');
      }
      if (getCryptoKeySecondaryHandle(key) !== undefined) {
        throw new ERR_INVALID_ARG_VALUE(
          'key',
          key,
          'cannot be represented by a single KeyObject');
      }
      const handle = getCryptoKeyHandle(key);
      switch (getCryptoKeyType(key)) {
        /* eslint-disable no-use-before-define */
        case 'secret': return new SecretKeyObject(handle);
        case 'public': return new PublicKeyObject(handle);
        case 'private': return new PrivateKeyObject(handle);
        /* eslint-enable no-use-before-define */
      }
    }

    equals(otherKeyObject) {
      if (!isKeyObject(otherKeyObject)) {
        throw new ERR_INVALID_ARG_TYPE(
          'otherKeyObject', 'KeyObject', otherKeyObject);
      }

      const slots = getKeyObjectSlots(this);
      const otherSlots = getKeyObjectSlots(otherKeyObject);
      return slots[kKeyObjectSlotType] === otherSlots[kKeyObjectSlotType] &&
        slots[kKeyObjectSlotHandle].equals(
          otherSlots[kKeyObjectSlotHandle]);
    }

    static {
      isKeyObject = (key) => {
        if (key == null || typeof key !== 'object') return false;
        return #slots in key || isNativeKeyObject(key);
      };
      getKeyObjectTypeValue = (key) => {
        if (!key || typeof key !== 'object')
          throw new ERR_INVALID_THIS('KeyObject');
        if (#slots in key) {
          const cached = key.#slots;
          if (typeof cached === 'number') return cached;
          if (cached !== undefined) return cached[kKeyObjectSlotType];
        }
        return getKeyObjectSlots(key)[kKeyObjectSlotType];
      };

      getKeyObjectSlots = (key) => {
        if (!key || typeof key !== 'object')
          throw new ERR_INVALID_THIS('KeyObject');
        if (#slots in key) {
          const cached = key.#slots;
          if (cached !== undefined && typeof cached !== 'number') return cached;
        }
        const slots = nativeGetKeyObjectSlots(key);
        key.#slots = slots;
        return slots;
      };
    }
  }

  ObjectDefineProperties(KeyObject.prototype, {
    [SymbolToStringTag]: {
      __proto__: null,
      configurable: true,
      value: 'KeyObject',
    },
  });

  class SecretKeyObject extends KeyObject {
    constructor(handle) {
      super('secret', handle);
    }

    get symmetricKeySize() {
      return getKeyObjectSymmetricKeySize(this);
    }

    export(options) {
      const handle = getKeyObjectHandle(this);
      if (options !== undefined) {
        validateObject(options, 'options');
        validateOneOf(
          options.format, 'options.format', [undefined, 'buffer', 'jwk']);
        if (options.format === 'jwk') {
          return handle.exportJwk({}, false);
        }
      }
      return handle.export();
    }
  }

  class AsymmetricKeyObject extends KeyObject {
    get asymmetricKeyType() {
      return getKeyObjectAsymmetricKeyType(this);
    }

    get asymmetricKeyDetails() {
      return { ...getKeyObjectAsymmetricKeyDetails(this) };
    }
  }

  class PublicKeyObject extends AsymmetricKeyObject {
    constructor(handle) {
      super('public', handle);
    }

    export(options) {
      switch (options?.format) {
        case 'jwk':
          return getKeyObjectHandle(this).exportJwk({}, false);
        case 'raw-public': {
          const handle = getKeyObjectHandle(this);
          const asymmetricKeyType = getKeyObjectAsymmetricKeyType(this);
          if (asymmetricKeyType === 'ec') {
            const { type = 'uncompressed' } = options;
            validateOneOf(type, 'options.type', ['compressed', 'uncompressed']);
            const form = type === 'compressed' ?
              POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED;
            return handle.exportECPublicRaw(form);
          }
          return handle.rawPublicKey();
        }
        default: {
          const asymmetricKeyType = getKeyObjectAsymmetricKeyType(this);
          const handle = getKeyObjectHandle(this);
          const {
            format,
            type,
          } = parsePublicKeyEncoding(options, asymmetricKeyType);
          return handle.export(format, type);
        }
      }
    }
  }

  class PrivateKeyObject extends AsymmetricKeyObject {
    constructor(handle) {
      super('private', handle);
    }

    export(options) {
      if (options?.passphrase !== undefined &&
          options.format !== 'pem' && options.format !== 'der') {
        throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
          options.format, 'does not support encryption');
      }
      switch (options?.format) {
        case 'jwk':
          return getKeyObjectHandle(this).exportJwk({}, false);
        case 'raw-private': {
          const handle = getKeyObjectHandle(this);
          const asymmetricKeyType = getKeyObjectAsymmetricKeyType(this);
          if (asymmetricKeyType === 'ec') {
            return handle.exportECPrivateRaw();
          }
          return handle.rawPrivateKey();
        }
        case 'raw-seed':
          return getKeyObjectHandle(this).rawSeed();
        default: {
          const asymmetricKeyType = getKeyObjectAsymmetricKeyType(this);
          const handle = getKeyObjectHandle(this);
          const {
            format,
            type,
            cipher,
            passphrase,
          } = parsePrivateKeyEncoding(options, asymmetricKeyType);
          return handle.export(format, type, cipher, passphrase);
        }
      }
    }
  }

  return [KeyObject, SecretKeyObject, PublicKeyObject, PrivateKeyObject];
});

function parseKeyFormat(formatStr, defaultFormat, objName) {
  if (formatStr === undefined && defaultFormat !== undefined)
    return defaultFormat;
  else if (formatStr === 'pem')
    return kKeyFormatPEM;
  else if (formatStr === 'der')
    return kKeyFormatDER;
  else if (formatStr === 'jwk')
    return kKeyFormatJWK;
  else if (formatStr === 'raw-public')
    return kKeyFormatRawPublic;
  else if (formatStr === 'raw-private')
    return kKeyFormatRawPrivate;
  else if (formatStr === 'raw-seed')
    return kKeyFormatRawSeed;
  throw new ERR_INVALID_ARG_VALUE(option('format', objName), formatStr);
}

function parseKeyType(typeStr, required, keyType, isPublic, objName) {
  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(option('type', objName), typeStr);
}

function option(name, prefix) {
  return prefix === undefined ?
    `options.${name}` : `${prefix}.${name}`;
}

function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
  const { format: formatStr, type: typeStr } = enc;

  const isInput = keyType === undefined;
  const format = parseKeyFormat(formatStr,
                                isInput ? kKeyFormatPEM : undefined,
                                objName);

  if (format === kKeyFormatRawPublic) {
    if (isPublic === false) {
      throw new ERR_INVALID_ARG_VALUE(option('format', objName), 'raw-public');
    }
    let type;
    if (typeStr === undefined || typeStr === 'uncompressed') {
      type = POINT_CONVERSION_UNCOMPRESSED;
    } else if (typeStr === 'compressed') {
      type = POINT_CONVERSION_COMPRESSED;
    } else {
      throw new ERR_INVALID_ARG_VALUE(option('type', objName), typeStr);
    }
    return { format, type };
  }

  if (format === kKeyFormatRawPrivate || format === kKeyFormatRawSeed) {
    if (isPublic === true) {
      throw new ERR_INVALID_ARG_VALUE(
        option('format', objName),
        format === kKeyFormatRawPrivate ? 'raw-private' : 'raw-seed');
    }
    if (typeStr !== undefined) {
      throw new ERR_INVALID_ARG_VALUE(option('type', objName), typeStr);
    }
    return { format };
  }

  const isRequired = (!isInput ||
                      format === kKeyFormatDER) &&
                      format !== kKeyFormatJWK;
  const type = parseKeyType(typeStr,
                            isRequired,
                            keyType,
                            isPublic,
                            objName);
  return { format, type };
}

function isStringOrBuffer(val) {
  return typeof val === 'string' ||
         isArrayBufferView(val) ||
         isAnyArrayBuffer(val);
}

function encodeKeyString(value, encoding) {
  return Buffer.from(value, encoding === 'buffer' ? 'utf8' : encoding);
}

function parseKeyEncoding(enc, keyType, isPublic, objName) {
  validateObject(enc, 'options');

  const isInput = keyType === undefined;

  const {
    format,
    type,
  } = parseKeyFormatAndType(enc, keyType, isPublic, objName);

  let cipher, passphrase, encoding;
  if (isPublic !== true) {
    ({ cipher, passphrase, encoding } = enc);

    if (format === kKeyFormatRawPrivate || format === kKeyFormatRawSeed) {
      if (cipher != null || passphrase !== undefined) {
        throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
          'raw format', 'does not support encryption');
      }
      return { format, type };
    }

    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,
          'is required when a passphrase is specified');
      }
    }

    if ((isInput && passphrase !== undefined &&
         !isStringOrBuffer(passphrase)) ||
        (!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
      throw new ERR_INVALID_ARG_VALUE(option('passphrase', objName),
                                      passphrase);
    }
  }

  if (typeof passphrase === 'string')
    passphrase = encodeKeyString(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 validateAsymmetricKeyType(type, ctx, key) {
  if (ctx === kCreatePrivate) {
    throw new ERR_INVALID_ARG_TYPE(
      'key',
      getKeyTypes({ allowKeyObject: false, allowURL: true }),
      key,
    );
  }

  if (type !== kKeyTypePrivate) {
    if (ctx === kConsumePrivate || ctx === kCreatePublic)
      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(keyObjectTypeToString(type), 'private');
    if (type !== kKeyTypePublic) {
      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(keyObjectTypeToString(type),
                                                   'private or public');
    }
  }
}

const kKeyTypesBufferOnly = [
  'ArrayBuffer',
  'Buffer',
  'TypedArray',
  'DataView',
];

function getKeyTypes({ allowKeyObject, allowURL = false }) {
  return [
    ...kKeyTypesBufferOnly,
    'string',
    ...(allowKeyObject ? ['KeyObject'] : []),
    ...(allowURL ? ['URL'] : []),
  ];
}

function prepareStorePrivateKey(url, name, passphrase, encoding, properties) {
  // Read the canonical serialization from private URL state. Public accessors
  // can be overridden by subclasses or through prototype mutation.
  let uri = getURLHref(url);
  if (StringPrototypeStartsWith(uri, 'file:'))
    uri = fileURLToPath(uri);

  validateString(uri, name);
  // The URI is handed to OpenSSL as a NUL-terminated C string. Reject embedded
  // NUL bytes, which `fileURLToPath()` happily decodes from `%00`, rather than
  // letting the URI be silently truncated.
  if (StringPrototypeIncludes(uri, '\u0000')) {
    throw new ERR_INVALID_ARG_VALUE(
      name, url, 'must be a URL without null bytes');
  }

  if (passphrase !== undefined) {
    if (!isStringOrBuffer(passphrase)) {
      throw new ERR_INVALID_ARG_VALUE(option('passphrase', name), passphrase);
    }
    if (typeof passphrase === 'string') {
      passphrase = encodeKeyString(passphrase, encoding);
    }
  }

  if (properties !== undefined) {
    validateString(properties, option('properties', name));
    // Reaches OpenSSL as a NUL-terminated C string, same as the URI above.
    if (StringPrototypeIncludes(properties, '\u0000')) {
      throw new ERR_INVALID_ARG_VALUE(
        option('properties', name),
        properties,
        'must be a string without null bytes');
    }
  }

  return {
    data: {
      uri,
      properties: properties ?? null,
    },
    format: kKeyFormatStore,
    passphrase: passphrase ?? null,
  };
}

function prepareAsymmetricKey(key, ctx, name = 'key') {
  if (typeof key === 'string') {
    return { format: kKeyFormatPEM, data: encodeKeyString(key) };
  }
  if (isArrayBufferView(key)) {
    return { format: kKeyFormatPEM, data: key };
  }
  const isPrivateKeyInput = ctx === kConsumePrivate || ctx === kCreatePrivate;

  if (isKeyObject(key)) {
    // Best case: A key object, as simple as that.
    const slots = getKeyObjectSlots(key);
    validateAsymmetricKeyType(slots[kKeyObjectSlotType], ctx, key);
    return { data: slots[kKeyObjectSlotHandle] };
  }
  if (isPrivateKeyInput && isURLInstance(key)) {
    // A private key referenced by a URI for an OpenSSL STORE loader.
    return prepareStorePrivateKey(key, name);
  }
  if (isAnyArrayBuffer(key)) {
    return { format: kKeyFormatPEM, data: key };
  }
  if (typeof key === 'object') {
    return prepareAsymmetricKeyOptions(key, ctx, name, isPrivateKeyInput);
  }

  throw new ERR_INVALID_ARG_TYPE(
    name,
    getKeyTypes({
      allowKeyObject: ctx !== kCreatePrivate,
      allowURL: isPrivateKeyInput,
    }),
    key);
}

function prepareAsymmetricKeyOptions(key, ctx, name, isPrivateKeyInput) {
  const { key: data, encoding, format, properties } = key;
  let isData = typeof data === 'string' || isArrayBufferView(data);

  // The 'key' property can be a KeyObject as well to allow specifying
  // additional options such as padding along with the key.
  if (!isData && isKeyObject(data)) {
    const slots = getKeyObjectSlots(data);
    validateAsymmetricKeyType(slots[kKeyObjectSlotType], ctx, data);
    return { data: slots[kKeyObjectSlotHandle] };
  }
  isData ||= isAnyArrayBuffer(data);
  if (!isData && isPrivateKeyInput && isURLInstance(data)) {
    // A private key referenced by a URI for an OpenSSL STORE loader, with
    // an optional passphrase/PIN.
    return prepareStorePrivateKey(
      data,
      name,
      key.passphrase,
      encoding,
      properties);
  }
  if (format === 'jwk') {
    validateObject(data, `${name}.key`);
    return { data, format: kKeyFormatJWK };
  }
  if (format === 'raw-public' || format === 'raw-private' ||
               format === 'raw-seed') {
    if (isPrivateKeyInput && format === 'raw-public') {
      throw new ERR_INVALID_ARG_VALUE(`${name}.format`, format);
    }
    if (!isData || typeof data === 'string') {
      throw new ERR_INVALID_ARG_TYPE(
        `${name}.key`,
        ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
        data);
    }
    validateString(key.asymmetricKeyType, `${name}.asymmetricKeyType`);
    if (key.asymmetricKeyType === 'ec') {
      validateString(key.namedCurve, `${name}.namedCurve`);
    }
    const rawFormat = format === 'raw-public' ? kKeyFormatRawPublic :
      format === 'raw-private' ? kKeyFormatRawPrivate : kKeyFormatRawSeed;
    return {
      data,
      format: rawFormat,
      type: key.asymmetricKeyType,
      namedCurve: key.namedCurve ?? null,
    };
  }

  // Either PEM or DER using PKCS#1 or SPKI.
  if (!isData) {
    throw new ERR_INVALID_ARG_TYPE(
      `${name}.key`,
      getKeyTypes({
        allowKeyObject: ctx !== kCreatePrivate,
        allowURL: isPrivateKeyInput,
      }),
      data);
  }

  const isPublic = isPrivateKeyInput ? false : undefined;
  const keyData = typeof data === 'string' ?
    encodeKeyString(data, encoding) : data;
  const parsed = parseKeyEncoding(key, undefined, isPublic, name);
  return {
    data: keyData,
    format: parsed.format,
    type: parsed.type,
    cipher: parsed.cipher,
    passphrase: parsed.passphrase,
  };
}

function preparePrivateKey(key, name) {
  return prepareAsymmetricKey(key, kConsumePrivate, name);
}

function preparePublicOrPrivateKey(key, name) {
  return prepareAsymmetricKey(key, kConsumePublic, name);
}

function prepareSecretKey(key, encoding, bufferOnly = false) {
  if (!bufferOnly) {
    if (isKeyObject(key)) {
      const type = getKeyObjectType(key);
      if (type !== 'secret')
        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(type, 'secret');
      return getKeyObjectHandle(key);
    }
  }
  if (typeof key !== 'string' &&
      !isArrayBufferView(key) &&
      !isAnyArrayBuffer(key)) {
    throw new ERR_INVALID_ARG_TYPE(
      'key',
      bufferOnly ? kKeyTypesBufferOnly : getKeyTypes({ allowKeyObject: true }),
      key);
  }
  return getArrayBufferOrView(key, 'key', encoding);
}

function createSecretKey(key, encoding) {
  key = prepareSecretKey(key, encoding, true);
  const handle = new KeyObjectHandle();
  handle.init(kKeyTypeSecret, key);
  return new SecretKeyObject(handle);
}

function createPublicKey(key) {
  const { format, type, data, passphrase, namedCurve } =
    prepareAsymmetricKey(key, kCreatePublic);
  const handle = new KeyObjectHandle();
  handle.init(kKeyTypePublic, data, format ?? null,
              type ?? null, passphrase ?? null, namedCurve ?? null);
  return new PublicKeyObject(handle);
}

/**
 * Parses a PKCS#12 (.p12 / .pfx) bundle. Returns an object holding the first
 * private key as `privateKey`, the certificate associated with it as
 * `certificate`, and every other certificate in the bundle as an array in
 * `additionalCertificates`. `privateKey` and `certificate` are null when the
 * bundle contains none.
 * @param {ArrayBuffer|Buffer|TypedArray|DataView} bundle
 * @param {object} [options]
 * @returns {object}
 */
function parsePKCS12(bundle, options = kEmptyObject) {
  if (!isArrayBufferView(bundle) && !isAnyArrayBuffer(bundle)) {
    throw new ERR_INVALID_ARG_TYPE(
      'bundle',
      ['ArrayBuffer', 'TypedArray', 'DataView', 'Buffer'],
      bundle);
  }

  validateObject(options, 'options');
  const { passphrase } = options;

  // `undefined` means no passphrase, '' a zero-length one. OpenSSL accepts
  // either PKCS#12 password encoding for both, so they behave the same.
  let passBuf;
  if (passphrase !== undefined) {
    passBuf = getArrayBufferOrView(passphrase, 'options.passphrase', 'utf8');
    // OpenSSL takes the passphrase as a NUL-terminated C string, so one
    // containing a NUL byte would be truncated there and the bundle opened
    // with only the bytes before it. A PKCS#12 password cannot represent an
    // embedded NUL anyway, so reject it outright.
    if (TypedArrayPrototypeIncludes(getBufferSourceBytes(passBuf), 0)) {
      throw new ERR_INVALID_ARG_VALUE(
        'options.passphrase', passphrase, 'must not contain null bytes');
    }
    // The binding reads the passphrase as a view; wrap a bare ArrayBuffer.
    if (isAnyArrayBuffer(passBuf)) passBuf = Buffer.from(passBuf);
  }

  // Likewise, the binding reads the bundle as a view.
  const bundleBuf = isAnyArrayBuffer(bundle) ? Buffer.from(bundle) : bundle;

  const {
    0: keyHandle,
    1: certHandle,
    2: otherHandles,
  } = _parsePKCS12(bundleBuf, passBuf);

  // Required lazily: internal/crypto/x509 depends on this module.
  const { InternalX509Certificate } = require('internal/crypto/x509');

  return {
    privateKey: keyHandle === null ? null : new PrivateKeyObject(keyHandle),
    certificate:
      certHandle === null ? null : new InternalX509Certificate(certHandle),
    additionalCertificates:
      ArrayPrototypeMap(otherHandles, (h) => new InternalX509Certificate(h)),
  };
}

/**
 * Converts a secret KeyObjectHandle to a CryptoKey by dispatching to the
 * algorithm-specific Web Crypto import path.
 * @param {KeyObjectHandle} keyData
 * @param {object} algorithm
 * @param {boolean} extractable
 * @param {string[]} keyUsages
 * @returns {CryptoKey}
 */
function toCryptoKeySecret(
  keyData,
  algorithm,
  extractable,
  keyUsages,
) {
  let result;
  switch (algorithm.name) {
    case 'HMAC':
      // Fall through
    case 'KMAC128':
      // Fall through
    case 'KMAC256':
      result = require('internal/crypto/mac')
        .macImportKey('KeyObjectHandle', keyData, algorithm, extractable, keyUsages);
      break;
    case 'AES-CTR':
      // Fall through
    case 'AES-CBC':
      // Fall through
    case 'AES-GCM':
      // Fall through
    case 'AES-KW':
      // Fall through
    case 'AES-OCB':
      result = require('internal/crypto/aes')
        .aesImportKey(algorithm, 'KeyObjectHandle', keyData, extractable, keyUsages);
      break;
    case 'ChaCha20-Poly1305':
      result = require('internal/crypto/chacha20_poly1305')
        .c20pImportKey(algorithm, 'KeyObjectHandle', keyData, extractable, keyUsages);
      break;
    case 'HKDF':
      // Fall through
    case 'PBKDF2':
      // Fall through
    case 'Argon2d':
      // Fall through
    case 'Argon2i':
      // Fall through
    case 'Argon2id':
      result = importGenericSecretKey(
        algorithm,
        'KeyObjectHandle',
        keyData,
        extractable,
        keyUsages);
      break;
    default:
      throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
  }

  if (getCryptoKeyUsagesMask(result) === 0) {
    throw lazyDOMException(
      `Usages cannot be empty when importing a ${getCryptoKeyType(result)} key.`,
      'SyntaxError');
  }

  return result;
}

/**
 * Converts an asymmetric KeyObjectHandle to a CryptoKey by dispatching to
 * the algorithm-specific Web Crypto import path. This preserves the same
 * algorithm and usage validation used by SubtleCrypto.importKey().
 * @param {KeyObjectHandle} keyData
 * @param {object} algorithm
 * @param {boolean} extractable
 * @param {string[]} keyUsages
 * @returns {CryptoKey}
 */
function toCryptoKey(
  keyData,
  algorithm,
  extractable,
  keyUsages,
) {
  let result;
  switch (algorithm.name) {
    case 'RSASSA-PKCS1-v1_5':
      // Fall through
    case 'RSA-PSS':
      // Fall through
    case 'RSA-OAEP':
      result = require('internal/crypto/rsa')
        .rsaImportKey('KeyObjectHandle', keyData, algorithm, extractable, keyUsages);
      break;
    case 'ECDSA':
      // Fall through
    case 'ECDH':
      result = require('internal/crypto/ec')
        .ecImportKey('KeyObjectHandle', keyData, algorithm, extractable, keyUsages);
      break;
    case 'Ed25519':
      // Fall through
    case 'Ed448':
      // Fall through
    case 'X25519':
      // Fall through
    case 'X448':
      result = require('internal/crypto/cfrg')
        .cfrgImportKey('KeyObjectHandle', keyData, algorithm, extractable, keyUsages);
      break;
    case 'ML-DSA-44':
      // Fall through
    case 'ML-DSA-65':
      // Fall through
    case 'ML-DSA-87':
      result = require('internal/crypto/ml_dsa')
        .mlDsaImportKey('KeyObjectHandle', keyData, algorithm, extractable, keyUsages);
      break;
    case 'ML-KEM-512':
      // Fall through
    case 'ML-KEM-768':
      // Fall through
    case 'ML-KEM-1024':
      result = require('internal/crypto/ml_kem')
        .mlKemImportKey('KeyObjectHandle', keyData, algorithm, extractable, keyUsages);
      break;
    default:
      throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
  }

  const resultType = getCryptoKeyType(result);
  if (resultType === 'private' && getCryptoKeyUsagesMask(result) === 0) {
    throw lazyDOMException(
      `Usages cannot be empty when importing a ${resultType} key.`,
      'SyntaxError');
  }

  return result;
}

/**
 * Derives a public CryptoKey from a private CryptoKey. The resulting key uses
 * the private key's algorithm, is extractable, and validates the requested
 * public usages through the shared asymmetric import path.
 * @param {CryptoKey} key
 * @param {string[]} keyUsages
 * @returns {CryptoKey}
 */
function toPublicCryptoKey(key, keyUsages) {
  const handle = new KeyObjectHandle();
  handle.init(kKeyTypePublic, getCryptoKeyHandle(key),
              null, null, null, null);
  return toCryptoKey(
    handle,
    getCryptoKeyAlgorithm(key),
    true,
    keyUsages);
}

function createPrivateKey(key) {
  const { format, type, data, passphrase, namedCurve } =
    prepareAsymmetricKey(key, kCreatePrivate);
  const handle = new KeyObjectHandle();
  handle.init(kKeyTypePrivate, data, format ?? null,
              type ?? null, passphrase ?? null, namedCurve ?? null);
  return new PrivateKeyObject(handle);
}

function keyObjectTypeToString(type) {
  switch (type) {
    case kKeyTypeSecret: return 'secret';
    case kKeyTypePublic: return 'public';
    case kKeyTypePrivate: return 'private';
    default: {
      const assert = require('internal/assert');
      assert.fail('Unreachable code');
    }
  }
}

// The helpers below return a KeyObject's native-backed slot values,
// populating the per-instance cache on first access via a single native
// call. The public getters delegate to these helpers, and internal
// consumers use them directly to avoid user-replaceable public accessors.
// Derived metadata such as key size and asymmetric key details is expanded
// lazily from the cached KeyObjectHandle. The public asymmetric key details
// getter returns a clone so the cached details object stays internal.

/**
 * Returns the KeyObject's native type slot as a string.
 * @param {KeyObject} key
 * @returns {'secret' | 'public' | 'private'}
 */
function getKeyObjectType(key) {
  return keyObjectTypeToString(getKeyObjectTypeValue(key));
}

/**
 * Returns the KeyObjectHandle wrapping the KeyObject's underlying key
 * material.
 * @param {KeyObject} key
 * @returns {KeyObjectHandle}
 */
function getKeyObjectHandle(key) {
  return getKeyObjectSlots(key)[kKeyObjectSlotHandle];
}

/**
 * Returns the KeyObject's symmetric key size, bypassing the public
 * `symmetricKeySize` getter. The value is derived lazily from the cached
 * KeyObjectHandle.
 * @param {SecretKeyObject} key
 * @returns {number}
 */
function getKeyObjectSymmetricKeySize(key) {
  const slots = getKeyObjectSlots(key);
  if (slots[kKeyObjectSlotType] !== kKeyTypeSecret)
    throw new ERR_INVALID_THIS('SecretKeyObject');

  let cached = slots[kKeyObjectSlotSymmetricKeySize];
  if (cached === undefined) {
    cached = slots[kKeyObjectSlotHandle].getSymmetricKeySize();
    slots[kKeyObjectSlotSymmetricKeySize] = cached;
  }
  return cached;
}

/**
 * Returns the KeyObject's asymmetric key type, bypassing the public
 * `asymmetricKeyType` getter. The value is derived lazily from the cached
 * KeyObjectHandle.
 * @param {PublicKeyObject|PrivateKeyObject} key
 * @returns {string}
 */
function getKeyObjectAsymmetricKeyType(key) {
  const slots = getKeyObjectSlots(key);
  if (slots[kKeyObjectSlotType] === kKeyTypeSecret)
    throw new ERR_INVALID_THIS('AsymmetricKeyObject');

  let cached = slots[kKeyObjectSlotAsymmetricKeyType];
  if (cached === undefined) {
    cached = slots[kKeyObjectSlotHandle].getAsymmetricKeyType();
    slots[kKeyObjectSlotAsymmetricKeyType] = cached;
  }
  return cached;
}

/**
 * Returns the KeyObject's cached asymmetric key details, bypassing the
 * public `asymmetricKeyDetails` getter (which returns a cloned copy).
 * The value is derived lazily from the cached KeyObjectHandle.
 * @param {PublicKeyObject|PrivateKeyObject} key
 * @returns {object}
 */
function getKeyObjectAsymmetricKeyDetails(key) {
  const slots = getKeyObjectSlots(key);
  if (slots[kKeyObjectSlotType] === kKeyTypeSecret)
    throw new ERR_INVALID_THIS('AsymmetricKeyObject');

  let cached = slots[kKeyObjectSlotAsymmetricKeyDetails];
  if (cached === undefined) {
    let asymmetricKeyType = slots[kKeyObjectSlotAsymmetricKeyType];
    if (asymmetricKeyType === undefined) {
      asymmetricKeyType = slots[kKeyObjectSlotHandle].getAsymmetricKeyType();
      slots[kKeyObjectSlotAsymmetricKeyType] = asymmetricKeyType;
    }
    switch (asymmetricKeyType) {
      case 'rsa':
      case 'rsa-pss':
      case 'dsa':
      case 'ec':
        cached = normalizeKeyDetails(
          slots[kKeyObjectSlotHandle].keyDetail({ __proto__: null }),
        );
        break;
      default:
        cached = kEmptyObject;
        break;
    }
    slots[kKeyObjectSlotAsymmetricKeyDetails] = cached;
  }
  return cached;
}

// CryptoKey is a plain JS class whose prototype's [[Prototype]] is
// Object.prototype, as Web Crypto requires. Instance storage (type enum,
// extractable, algorithm, usages mask, the primary KeyObject handle, an
// optional Hybrid KEM-only secondary KeyObject handle, and optional Hybrid
// KEM-only seed data) lives
// on a C++ class, NativeCryptoKey, created by createCryptoKeyClass.
// InternalCryptoKey is the only constructor we expose to internal
// code; it extends NativeCryptoKey to get that storage and gives each
// instance the public interface prototype:
//   instance -> CryptoKey.prototype
//            -> Object.prototype
//
// Normal construction caches all native internal slots in a private class
// field on `InternalCryptoKey`. Partially initialized transferred keys read
// them from C++ in a single `getCryptoKeySlots` call on first access. The
// cache is invisible to reflection (`Object.getOwnPropertySymbols` etc.) and
// CryptoKey's hidden class pristine. The `getCryptoKey{Type,Extractable,
// Algorithm,Usages,Handle,SecondaryHandle,SeedData}` helpers index into that
// array and convert native enums/masks back to Web Crypto strings.
// The internal algorithm object is stored as a null-prototype clone
// so it cannot observe polluted Object.prototype properties.
// The public `algorithm` getter caches a cloned dictionary and the
// public `usages` getter caches a synthesized array (as Web Crypto
// requires repeat reads to return the same object so a consumer's
// mutation is visible next time).
let getSlots; // Populated by the createCryptoKeyClass callback below.
let isCryptoKey;

const kSlotType = 0;
const kSlotExtractable = 1;
const kSlotAlgorithm = 2;
const kSlotUsagesMask = 3;
const kSlotHandle = 4;
const kSlotSecondaryHandle = 5;
const kSlotSeedData = 6;
const kSlotClonedAlgorithm = 7;
const kSlotClonedUsages = 8;
const kSlotUsages = 9;

function cloneAlgorithm(raw) {
  const cloned = { ...raw };
  if (ObjectPrototypeHasOwnProperty(cloned, 'hash') &&
      cloned.hash !== undefined) {
    cloned.hash = { ...cloned.hash };
  }
  if (ObjectPrototypeHasOwnProperty(cloned, 'publicExponent') &&
      cloned.publicExponent !== undefined) {
    cloned.publicExponent = new Uint8Array(cloned.publicExponent);
  }
  return cloned;
}

function cloneInternalAlgorithm(raw) {
  const cloned = { __proto__: null, ...raw };
  if (ObjectPrototypeHasOwnProperty(cloned, 'hash') &&
      cloned.hash !== undefined) {
    cloned.hash = { __proto__: null, ...cloned.hash };
  }
  if (ObjectPrototypeHasOwnProperty(cloned, 'publicExponent') &&
      cloned.publicExponent !== undefined) {
    cloned.publicExponent = new Uint8Array(cloned.publicExponent);
  }
  return cloned;
}

const {
  0: CryptoKey,
  1: InternalCryptoKey,
} = createCryptoKeyClass((NativeCryptoKey) => {
  class CryptoKey {
    constructor() {
      throw new ERR_ILLEGAL_CONSTRUCTOR();
    }

    [kInspect](depth, options) {
      if (depth < 0)
        return this;

      if (!isCryptoKey(this))
        throw new ERR_INVALID_THIS('CryptoKey');

      const opts = {
        ...options,
        depth: options.depth == null ? null : options.depth - 1,
      };

      return `CryptoKey ${inspect({
        type: getCryptoKeyType(this),
        extractable: getCryptoKeyExtractable(this),
        algorithm: cloneAlgorithm(getCryptoKeyAlgorithm(this)),
        usages: getUsagesFromMask(getCryptoKeyUsagesMask(this)),
      }, opts)}`;
    }

    get type() {
      if (!isCryptoKey(this))
        throw new ERR_INVALID_THIS('CryptoKey');
      return getCryptoKeyType(this);
    }

    get extractable() {
      if (!isCryptoKey(this))
        throw new ERR_INVALID_THIS('CryptoKey');
      return getCryptoKeyExtractable(this);
    }

    get algorithm() {
      if (!isCryptoKey(this))
        throw new ERR_INVALID_THIS('CryptoKey');
      const slots = getSlots(this);
      let cached = slots[kSlotClonedAlgorithm];
      if (cached === undefined) {
        cached = cloneAlgorithm(slots[kSlotAlgorithm]);
        slots[kSlotClonedAlgorithm] = cached;
      }
      return cached;
    }

    get usages() {
      if (!isCryptoKey(this))
        throw new ERR_INVALID_THIS('CryptoKey');
      const slots = getSlots(this);
      let cached = slots[kSlotClonedUsages];
      if (cached === undefined) {
        cached = getUsagesFromMask(slots[kSlotUsagesMask]);
        slots[kSlotClonedUsages] = cached;
      }
      return cached;
    }
  }

  class InternalCryptoKey extends NativeCryptoKey {
    #slots;

    constructor(
      handle,
      algorithm,
      usagesMask,
      extractable,
      secondaryHandle,
      seedData) {
      if (algorithm !== undefined)
        algorithm = cloneInternalAlgorithm(algorithm);
      if (seedData !== undefined)
        seedData = new Uint8Array(seedData);
      super(
        handle,
        algorithm,
        usagesMask,
        extractable,
        secondaryHandle,
        seedData);
      ObjectSetPrototypeOf(this, CryptoKey.prototype);
      if (algorithm !== undefined) {
        this.#slots = [
          handle.getKeyType(),
          extractable,
          algorithm,
          usagesMask,
          handle,
          secondaryHandle,
          seedData,
        ];
      }
    }

    static {
      isCryptoKey = (key) => {
        if (key == null || typeof key !== 'object') return false;
        return #slots in key || isNativeCryptoKey(key);
      };
      getSlots = (key) => {
        const cached = key.#slots;
        if (cached !== undefined) return cached;
        const slots = nativeGetCryptoKeySlots(key);
        slots[kSlotAlgorithm] = cloneInternalAlgorithm(slots[kSlotAlgorithm]);
        if (slots[kSlotSeedData] !== undefined)
          slots[kSlotSeedData] = new Uint8Array(slots[kSlotSeedData]);
        key.#slots = slots;
        return slots;
      };
    }
  }
  // Hide NativeCryptoKey from user code.
  InternalCryptoKey.prototype.constructor = CryptoKey;
  ObjectSetPrototypeOf(InternalCryptoKey.prototype, CryptoKey.prototype);

  ObjectDefineProperties(CryptoKey.prototype, {
    type: kEnumerableProperty,
    extractable: kEnumerableProperty,
    algorithm: kEnumerableProperty,
    usages: kEnumerableProperty,
    [SymbolToStringTag]: {
      __proto__: null,
      configurable: true,
      value: 'CryptoKey',
    },
  });

  return [CryptoKey, InternalCryptoKey];
});

// The helpers below return a CryptoKey's internal slot value. The public
// `type` getter converts the native enum to the Web Crypto string. The
// `usages` helper converts the native usage mask to Web Crypto strings. The
// public `algorithm` / `usages` getters cache their returned objects.

/**
 * Returns the value of a CryptoKey's `[[type]]` internal slot.
 * @param {CryptoKey} key
 * @returns {'secret' | 'public' | 'private'}
 */
function getCryptoKeyType(key) {
  switch (getSlots(key)[kSlotType]) {
    case kKeyTypeSecret: return 'secret';
    case kKeyTypePublic: return 'public';
    case kKeyTypePrivate: return 'private';
    default: {
      const assert = require('internal/assert');
      assert.fail('Unreachable code');
    }
  }
}

/**
 * Returns the value of a CryptoKey's `[[extractable]]` internal slot.
 * @param {CryptoKey} key
 * @returns {boolean}
 */
function getCryptoKeyExtractable(key) {
  return getSlots(key)[kSlotExtractable];
}

/**
 * Returns the CryptoKey's `[[algorithm]]` internal slot, bypassing the
 * public `algorithm` getter (which returns a cloned copy).
 * @param {CryptoKey} key
 * @returns {object}
 */
function getCryptoKeyAlgorithm(key) {
  return getSlots(key)[kSlotAlgorithm];
}

/**
 * Returns the CryptoKey's native `[[usages]]` mask.
 * @param {CryptoKey} key
 * @returns {number}
 */
function getCryptoKeyUsagesMask(key) {
  return getSlots(key)[kSlotUsagesMask];
}

/**
 * Returns whether a CryptoKey's `[[usages]]` contains `usage`.
 * @param {CryptoKey} key
 * @param {string} usage
 * @returns {boolean}
 */
function hasCryptoKeyUsage(key, usage) {
  return hasUsage(getCryptoKeyUsagesMask(key), usage);
}

/**
 * Returns the CryptoKey's cached canonical usages array for internal
 * consumers, expanding it from the native usage mask on first access.
 * @param {Array} slots
 * @returns {string[]}
 */
function getCryptoKeyUsagesFromSlots(slots) {
  let usages = slots[kSlotUsages];
  if (usages === undefined) {
    usages = getUsagesFromMask(slots[kSlotUsagesMask]);
    slots[kSlotUsages] = usages;
  }
  return usages;
}

/**
 * Returns the CryptoKey's `[[usages]]` internal slot, bypassing the
 * public `usages` getter (which returns a cloned array). The internal
 * array is expanded lazily from the native usage mask.
 * @param {CryptoKey} key
 * @returns {string[]}
 */
function getCryptoKeyUsages(key) {
  return getCryptoKeyUsagesFromSlots(getSlots(key));
}

/**
 * Returns the KeyObjectHandle wrapping the CryptoKey's underlying
 * key material.
 * @param {CryptoKey} key
 * @returns {KeyObjectHandle}
 */
function getCryptoKeyHandle(key) {
  return getSlots(key)[kSlotHandle];
}

/**
 * Returns the optional Hybrid KEM-only secondary KeyObjectHandle wrapping a
 * CryptoKey's other underlying key material.
 * @param {CryptoKey} key
 * @returns {KeyObjectHandle | undefined}
 */
function getCryptoKeySecondaryHandle(key) {
  return getSlots(key)[kSlotSecondaryHandle];
}

/**
 * Returns the optional Hybrid KEM-only seed data stored with a CryptoKey.
 * @param {CryptoKey} key
 * @returns {Uint8Array | undefined}
 */
function getCryptoKeySeedData(key) {
  return getSlots(key)[kSlotSeedData];
}

function importGenericSecretKey(
  algorithm,
  format,
  keyData,
  extractable,
  keyUsages,
) {
  const usagesSet = toUsagesSet(keyUsages);
  const { name } = algorithm;
  if (extractable)
    throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');

  if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
    throw lazyDOMException(
      `Unsupported key usage for a ${name} key`,
      'SyntaxError');
  }

  let handle;
  switch (format) {
    case 'KeyObjectHandle': {
      handle = keyData;
      break;
    }
    case 'raw-secret':
    case 'raw': {
      handle = new KeyObjectHandle();
      handle.init(kKeyTypeSecret, keyData);
      break;
    }
    default:
      return undefined;
  }

  return new InternalCryptoKey(
    handle,
    { name },
    getUsagesMask(usagesSet),
    false);
}

module.exports = {
  // Public API.
  createSecretKey,
  createPublicKey,
  createPrivateKey,
  parsePKCS12,
  KeyObject,
  CryptoKey,
  InternalCryptoKey,

  // These are designed for internal use only and should not be exposed.
  parsePublicKeyEncoding,
  parsePrivateKeyEncoding,
  parseKeyEncoding,
  toPublicCryptoKey,
  prepareAsymmetricKey,
  kConsumePublic,
  kConsumePrivate,
  kCreatePublic,
  kCreatePrivate,
  preparePrivateKey,
  preparePublicOrPrivateKey,
  prepareSecretKey,
  SecretKeyObject,
  PublicKeyObject,
  PrivateKeyObject,
  isKeyObject,
  getKeyObjectType,
  getKeyObjectHandle,
  getKeyObjectSymmetricKeySize,
  getKeyObjectAsymmetricKeyType,
  getKeyObjectAsymmetricKeyDetails,
  isCryptoKey,
  getCryptoKeyType,
  getCryptoKeyExtractable,
  getCryptoKeyAlgorithm,
  getCryptoKeyUsages,
  getCryptoKeyUsagesMask,
  hasCryptoKeyUsage,
  getCryptoKeyHandle,
  getCryptoKeySecondaryHandle,
  getCryptoKeySeedData,
  importGenericSecretKey,
};