char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport webcrypto extension to typescriptdbb5c40

main
39.6 KiB1331 linesraw
1import {
2  Array,
3  ArrayBuffer,
4  ArrayBufferPrototypeGetResizable as resizable,
5  ArrayFrom,
6  getter,
7  Object,
8  ObjectCreate,
9  ObjectDefineProperty as define,
10  ObjectFreeze as freeze,
11  ReflectApply as apply,
12  Set,
13  SetPrototypeHas as has,
14  String,
15  SymbolIterator,
16  TypedArrayPrototypeGetBuffer as typedBuffer,
17  TypedArrayPrototypeGetByteLength as byteLength,
18  TypedArrayPrototypeGetByteOffset as byteOffset,
19  TypedArrayPrototypeGetTag as tag,
20  TypeError,
21  Uint8Array,
22  Uint8ArrayPrototypeSet as setBytes,
23  uncurryThis,
24  WeakMap,
25  WeakMapPrototypeSet as set,
26} from "ext:core/primordials";
27import {
28  op_crypto_derive_key as deriveKeyBytes,
29  op_crypto_digest as digestBytes,
30  op_crypto_key_clone as cloneKey,
31  op_crypto_key_deserialize as deserializeKey,
32  op_crypto_key_dispose as disposeKey,
33  op_crypto_key_export as exportKeyBytes,
34  op_crypto_key_export_jwk as exportKeyJwk,
35  op_crypto_key_generate as generateKeyBytes,
36  op_crypto_key_import as importKeyBytes,
37  op_crypto_key_import_asymmetric as importAsymmetric,
38  op_crypto_key_import_jwk as importJwk,
39  op_crypto_key_pair_generate as generateKeyPairBytes,
40  op_crypto_key_serialize as serializeKey,
41  op_crypto_random as randomBytes,
42  op_crypto_run as runCrypto,
43  op_crypto_unwrap_key as unwrapKeyBytes,
44  op_crypto_uuid as uuid,
45  op_crypto_wrap_key as wrapKeyBytes,
46} from "ext:core/ops";
47import { installInterface } from "ext:web/interface.js";
48import { createException } from "ext:web/events.js";
49import { array, dictionary, required, state, string } from "ext:web/idl.js";
50import { registerSerializable, registerWire } from "ext:web/serialization.js";
51import type {
52  CryptoBuffer,
53  CryptoKeyFormat,
54  CryptoKeyType,
55  CryptoKeyUsage,
56  CryptoOperation,
57  CryptoView,
58} from "./types.js";
59type KeyMeta = {
60  name: string;
61  length?: number;
62  hash?: string;
63  usages: readonly string[];
64  publicUsages?: readonly string[];
65  extractable: boolean;
66  type: CryptoKeyType;
67  curve?: string;
68  exponent?: CryptoBuffer;
69};
70type KeyState = {
71  id: number;
72  meta: KeyMeta & { publicUsages: readonly string[] };
73  algorithm: Record<string, unknown>;
74};
75type AlgorithmRecord = Record<string, unknown>;
76type RunParams = {
77  hash?: string;
78  iv?: CryptoBuffer;
79  counter?: CryptoBuffer;
80  additionalData?: CryptoBuffer;
81  tagLength?: number;
82  iterations?: number;
83  salt?: CryptoBuffer;
84  info?: CryptoBuffer;
85  peerId?: number;
86};
87type CryptoStates = { subtle: SubtleCrypto };
88const cryptoStates = new WeakMap<object, CryptoStates>(),
89  subtleStates = new WeakMap<object, true>(),
90  keyStates = new WeakMap<object, KeyState>();
91const charCodeAt = uncurryThis(String.prototype.charCodeAt);
92const fromCharCode = String.fromCharCode;
93const ceil = Math.ceil;
94const subarray = uncurryThis(Uint8Array.prototype.subarray);
95const registerFinalizer = uncurryThis(FinalizationRegistry.prototype.register);
96type Secret = { readonly __secret: unique symbol };
97const secret = ObjectCreate(null) as Secret;
98function exception(message: string, name: string): Error {
99  return createException({ message, name });
100}
101const isView = ArrayBuffer.isView;
102const getArrayBufferLength = getter(ArrayBuffer.prototype, "byteLength");
103const dataViewBuffer = getter(DataView.prototype, "buffer");
104const dataViewOffset = getter(DataView.prototype, "byteOffset");
105const dataViewLength = getter(DataView.prototype, "byteLength");
106const arrayPush: <T>(array: T[], ...items: T[]) => number = uncurryThis(
107  Array.prototype.push,
108);
109const arrayIncludes: <T>(
110  array: readonly T[],
111  value: unknown,
112  fromIndex?: number,
113) => boolean = uncurryThis(Array.prototype.includes);
114function arraySlice<T>(value: readonly T[]): T[] {
115  const copy: T[] = array();
116  for (let i = 0; i < value.length; i++) copy[i] = value[i];
117  return copy;
118}
119const arraySome: <T>(
120  array: readonly T[],
121  callback: (value: T, index: number, array: readonly T[]) => unknown,
122) => boolean = uncurryThis(Array.prototype.some);
123const arrayFilter: <T>(
124  array: readonly T[],
125  callback: (value: T, index: number, array: readonly T[]) => unknown,
126) => T[] = uncurryThis(Array.prototype.filter);
127const stringSlice = uncurryThis(String.prototype.slice);
128const stringIndexOf = uncurryThis(String.prototype.indexOf);
129const stringIncludes = uncurryThis(String.prototype.includes);
130const startsWith = uncurryThis(String.prototype.startsWith);
131const Number = globalThis.Number;
132const numberIsInteger = Number.isInteger,
133  numberIsFinite = Number.isFinite,
134  trunc = Math.trunc;
135const Boolean = globalThis.Boolean;
136const integerTags = new Set([
137  "Int8Array",
138  "Uint8Array",
139  "Uint8ClampedArray",
140  "Int16Array",
141  "Uint16Array",
142  "Int32Array",
143  "Uint32Array",
144  "BigInt64Array",
145  "BigUint64Array",
146]);
147const digestNames = new Set(["SHA-1", "SHA-256", "SHA-384", "SHA-512"]);
148const aesNames = new Set(["AES-CBC", "AES-CTR", "AES-GCM", "AES-KW"]);
149const asymmetricNames = new Set([
150  "RSA-OAEP",
151  "RSA-PSS",
152  "RSASSA-PKCS1-V1_5",
153  "ECDSA",
154  "ECDH",
155  "ED25519",
156  "X25519",
157]);
158const keyUsages = new Set([
159  "encrypt",
160  "decrypt",
161  "sign",
162  "verify",
163  "deriveKey",
164  "deriveBits",
165  "wrapKey",
166  "unwrapKey",
167]);
168const assign = Object.assign;
169const empty = new Uint8Array(0);
170const own = (value: ArrayLike<number>): CryptoBuffer => new Uint8Array(value);
171function peerKeyId(algorithm: AlgorithmRecord, baseKey: KeyState): number {
172  const publicKey = algorithm.public;
173  if (publicKey === undefined) {
174    throw new TypeError("ECDH public key is required");
175  }
176  const peer = keyState(publicKey);
177  if (
178    peer.meta.type !== "public" || peer.meta.name !== baseKey.meta.name ||
179    peer.meta.curve !== baseKey.meta.curve
180  ) {
181    throw exception(
182      "ECDH public key does not match the base key",
183      "InvalidAccessError",
184    );
185  }
186  return peer.id;
187}
188const keyFinalizer = new FinalizationRegistry<number>((id) => disposeKey(id));
189
190function sourceBytes(value: unknown): CryptoBuffer {
191  if (isView(value)) {
192    let buffer;
193    try {
194      buffer = dataViewBuffer(value);
195    } catch {}
196    try {
197      if (buffer !== undefined) {
198        if (getArrayBufferLength(buffer) === 0) return own(empty);
199        return own(
200          new Uint8Array(buffer, dataViewOffset(value), dataViewLength(value)),
201        );
202      }
203      getArrayBufferLength(typedBuffer(value));
204      const length = byteLength(value);
205      if (length === 0) return own(empty);
206      return own(new Uint8Array(typedBuffer(value), byteOffset(value), length));
207    } catch {
208      throw new TypeError("Expected an attached BufferSource");
209    }
210  }
211  try {
212    const length = getArrayBufferLength(value);
213    return length === 0
214      ? own(empty)
215      : own(new Uint8Array(value as ArrayBuffer, 0, length));
216  } catch {
217    throw new TypeError("Expected a non-shared BufferSource");
218  }
219}
220function algorithm(value: unknown): string {
221  let name;
222  if (
223    value !== null && (typeof value === "object" || typeof value === "function")
224  ) {
225    name = dictionary(value).name;
226    if (name === undefined) throw new TypeError("Algorithm.name is required");
227  } else name = value;
228  const input = string(name);
229  let result = "";
230  for (let i = 0; i < input.length; i++) {
231    const code = charCodeAt(input, i);
232    result += fromCharCode(code >= 97 && code <= 122 ? code - 32 : code);
233  }
234  return result;
235}
236function usageList(value: unknown, name: string): CryptoKeyUsage[] {
237  if (
238    value === null || value === undefined ||
239    typeof (value as { [SymbolIterator]?: unknown })[SymbolIterator] !==
240      "function"
241  ) throw new TypeError("Expected an iterable of key usages");
242  const sequence = ArrayFrom(value as Iterable<unknown>);
243  const usages = array();
244  for (let i = 0; i < sequence.length; i++) {
245    const usage = string(sequence[i]);
246    if (!has(keyUsages, usage)) throw new TypeError("Invalid key usage");
247    if (!arrayIncludes(usages, usage)) arrayPush(usages, usage);
248  }
249  const allowed = name === "HMAC" || name === "ECDSA" || name === "ED25519" ||
250      name === "RSA-PSS" || name === "RSASSA-PKCS1-V1_5"
251    ? ["sign", "verify"]
252    : name === "PBKDF2" || name === "HKDF" || name === "ECDH" ||
253        name === "X25519"
254    ? ["deriveBits", "deriveKey"]
255    : name === "RSA-OAEP"
256    ? ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
257    : name === "AES-KW"
258    ? ["wrapKey", "unwrapKey"]
259    : ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
260  if (arraySome(usages, (usage) => !arrayIncludes(allowed, usage))) {
261    throw exception(
262      "The requested key usages are not valid for this algorithm",
263      "SyntaxError",
264    );
265  }
266  return usages as CryptoKeyUsage[];
267}
268function hashName(value: unknown): string {
269  const name = algorithm(value);
270  if (!has(digestNames, name)) {
271    throw exception("Unsupported hash algorithm", "NotSupportedError");
272  }
273  return name;
274}
275function hmacLength(hash: string): number {
276  return hash === "SHA-384" || hash === "SHA-512" ? 1024 : 512;
277}
278function metadata(
279  name: string,
280  length: number | undefined,
281  hash: string | undefined,
282  usages: readonly string[],
283  extractable: boolean,
284  type: CryptoKeyType = "secret",
285  curve?: string,
286  exponent?: CryptoBuffer | null,
287): KeyMeta {
288  return {
289    name,
290    length,
291    hash,
292    usages: freeze(arraySlice(usages)),
293    extractable,
294    type,
295    curve,
296    exponent: exponent ? own(exponent) : undefined,
297  };
298}
299function makeKey(id: number, meta: KeyMeta): CryptoKey {
300  meta.usages = freeze(arraySlice(meta.usages));
301  meta.publicUsages = freeze(ArrayFrom(meta.usages));
302  const value = ObjectCreate(CryptoKey.prototype);
303  const internal = {
304    id,
305    meta: meta as KeyState["meta"],
306    algorithm: freshAlgorithm(meta),
307  };
308  set(keyStates, value, internal);
309  registerFinalizer(keyFinalizer, value, id);
310  return value;
311}
312registerSerializable(keyStates, (internal) =>
313  makeKey(cloneKey(internal.id), {
314    name: internal.meta.name,
315    length: internal.meta!.length,
316    hash: internal.meta.hash,
317    curve: internal.meta.curve,
318    exponent: internal.meta.exponent,
319    usages: arraySlice(internal.meta.usages),
320    extractable: internal.meta.extractable,
321    type: internal.meta.type,
322  }));
323registerWire({
324  name: "CryptoKey",
325  states: keyStates,
326  encode: (
327    internal: KeyState,
328  ): [number, KeyMeta] => [serializeKey(internal.id), internal.meta],
329  decode: (packet: [number, KeyMeta]) =>
330    makeKey(deserializeKey(packet[0]), packet[1]),
331});
332function keyState(value: unknown): KeyState {
333  return state(keyStates, value);
334}
335function freshAlgorithm(meta: KeyMeta): Record<string, unknown> {
336  const result: Record<string, unknown> = ObjectCreate(null);
337  result.name = meta.name === "ED25519"
338    ? "Ed25519"
339    : meta.name === "RSASSA-PKCS1-V1_5"
340    ? "RSASSA-PKCS1-v1_5"
341    : meta.name;
342  if (meta!.length !== undefined) {
343    if (
344      meta.name === "RSA-OAEP" || meta.name === "RSA-PSS" ||
345      meta.name === "RSASSA-PKCS1-V1_5"
346    ) result.modulusLength = meta!.length;
347    else if (has(aesNames, meta.name) || meta.name === "HMAC") {
348      result.length = meta!.length;
349    }
350  }
351  if (meta.curve !== undefined) result.namedCurve = meta.curve;
352  if (meta.exponent !== undefined) result.publicExponent = own(meta.exponent);
353  if (meta.hash !== undefined) {
354    const hash: { name: string } = ObjectCreate(null);
355    hash.name = meta.hash;
356    result.hash = hash;
357  }
358  return result;
359}
360function operationError(error: unknown, fallback: string): Error {
361  const message = (error as { message?: string } | null | undefined)?.message ??
362    "The cryptographic operation failed";
363  const codes = [
364    "NotSupportedError",
365    "InvalidAccessError",
366    "DataError",
367    "SyntaxError",
368    "OperationError",
369  ];
370  for (let i = 0; i < codes.length; i++) {
371    const code = codes[i];
372    if (startsWith(message, `${code}: `)) {
373      return exception(stringSlice(message, code.length + 2), code);
374    }
375  }
376  const name =
377    stringIncludes(message, "JWK") || message === "Invalid CryptoKey length"
378      ? "DataError"
379      : stringIncludes(message, "usage") &&
380            !stringIncludes(message, "does not permit") ||
381          stringIncludes(message, "must be non-extractable") ||
382          message === "Invalid asymmetric key"
383      ? "SyntaxError"
384      : stringIncludes(message, "does not permit") ||
385          stringIncludes(message, "not extractable")
386      ? "InvalidAccessError"
387      : fallback;
388  return exception(message, name);
389}
390async function settled<T>(
391  promise: Promise<T>,
392  fallback = "OperationError",
393): Promise<T> {
394  try {
395    return await promise;
396  } catch (error) {
397    throw operationError(error, fallback);
398  }
399}
400async function settledBytes(
401  promise: Promise<CryptoBuffer>,
402): Promise<ArrayBuffer> {
403  return typedBuffer(await settled(promise)) as ArrayBuffer;
404}
405function emptyRun(
406  operation: CryptoOperation,
407  id: number,
408  input: CryptoBuffer = empty,
409  params: RunParams | undefined = undefined,
410) {
411  params = assign(ObjectCreate(null), params) as RunParams;
412  return runCrypto([
413    operation,
414    id,
415    params.hash ?? "",
416    params.iv ?? empty,
417    params.counter ?? empty,
418    params.additionalData ?? empty,
419    params.tagLength ?? 128,
420    params.iterations ?? 0,
421    params.salt ?? empty,
422    params.info ?? empty,
423    input,
424    params.peerId ?? 0,
425  ]);
426}
427function pssSaltLength(value: unknown): number {
428  const number = +(value as number);
429  if (!numberIsFinite(number)) {
430    throw new TypeError("Invalid RSA-PSS saltLength");
431  }
432  const integer = trunc(number);
433  if (integer < 0 || integer > 0xffffffff) {
434    throw new TypeError("Invalid RSA-PSS saltLength");
435  }
436  return integer;
437}
438function algorithmParams(
439  value: unknown,
440  forKey = false,
441): AlgorithmRecord & {
442  dictionary: AlgorithmRecord | undefined;
443  name: string;
444  length?: number;
445  hash?: string;
446  exponent?: CryptoBuffer;
447  saltLength?: number;
448  curve?: string;
449} {
450  const dictionaryValue = typeof value === "string"
451    ? undefined
452    : dictionary(value) as AlgorithmRecord;
453  const name = algorithm(value);
454  const result: AlgorithmRecord & {
455    dictionary: AlgorithmRecord | undefined;
456    name: string;
457    length?: number;
458    hash?: string;
459    exponent?: CryptoBuffer;
460    saltLength?: number;
461    curve?: string;
462  } = { dictionary: dictionaryValue, name };
463  if (startsWith(name, "AES-")) {
464    if (forKey) result.length = dictionaryValue?.length as number | undefined;
465  } else if (name === "HMAC") {
466    if (forKey) {
467      const hash = dictionaryValue?.hash;
468      result.hash = hash === undefined ? undefined : hashName(hash);
469      result.length = dictionaryValue?.length as number | undefined;
470    }
471  } else if (name === "PBKDF2" || name === "HKDF") {
472  } else if (
473    name === "RSA-OAEP" || name === "RSA-PSS" || name === "RSASSA-PKCS1-V1_5"
474  ) {
475    if (forKey) {
476      const hash = dictionaryValue?.hash;
477      result.hash = hash === undefined ? undefined : hashName(hash);
478      result.length = dictionaryValue?.modulusLength as number | undefined;
479      const exponent = dictionaryValue?.publicExponent;
480      result.exponent = exponent === undefined
481        ? undefined
482        : sourceBytes(exponent);
483      if (!result.hash) {
484        throw new TypeError("RSA key algorithms require a hash");
485      }
486    } else if (name === "RSA-PSS") {
487      result.saltLength = pssSaltLength(dictionaryValue?.saltLength);
488    }
489  } else if (name === "ECDSA") {
490    if (forKey) {
491      result.curve = dictionaryValue?.namedCurve as string | undefined;
492    } else {
493      const hash = dictionaryValue?.hash;
494      result.hash = hash === undefined ? undefined : hashName(hash);
495    }
496  } else if (name === "ECDH") {
497    if (forKey) {
498      result.curve = dictionaryValue?.namedCurve as string | undefined;
499    }
500  } else if (!has(asymmetricNames, name)) {
501    throw exception(
502      "The requested algorithm is not supported",
503      "NotSupportedError",
504    );
505  }
506  return result;
507}
508
509function checkKeyOperation(
510  key: unknown,
511  usage: string,
512  algorithmName: string,
513): KeyState {
514  const internal = keyState(key);
515  if (
516    internal.meta.name !== algorithmName ||
517    !arrayIncludes(internal.meta.usages, usage)
518  ) {
519    throw exception(
520      "The CryptoKey does not permit this operation",
521      "InvalidAccessError",
522    );
523  }
524  return internal;
525}
526function normalizeInputAlgorithm(
527  value: unknown,
528): { name: string; params: RunParams } {
529  const a: AlgorithmRecord | undefined = typeof value === "string"
530      ? undefined
531      : dictionary(value) as AlgorithmRecord,
532    name = algorithm(value),
533    params = ObjectCreate(null);
534  if (name === "AES-CBC") {
535    params.iv = sourceBytes(a!.iv);
536    if (params.iv.length !== 16) {
537      throw exception("AES-CBC requires a 16-byte IV", "OperationError");
538    }
539  } else if (name === "AES-CTR") {
540    params.counter = sourceBytes(a!.counter);
541    if (params.counter.length !== 16) {
542      throw exception("AES-CTR requires a 16-byte counter", "OperationError");
543    }
544    const bits = Number(a!.length);
545    if (!numberIsInteger(bits) || bits < 1 || bits > 128) {
546      throw exception("Invalid AES-CTR counter length", "OperationError");
547    }
548    params.tagLength = bits;
549  } else if (name === "AES-GCM") {
550    params.iv = sourceBytes(a!.iv);
551    if (!params.iv.length) {
552      throw exception("AES-GCM IV must not be empty", "OperationError");
553    }
554    params.additionalData = a!.additionalData === undefined
555      ? empty
556      : sourceBytes(a!.additionalData);
557    params.tagLength = a!.tagLength === undefined ? 128 : Number(a!.tagLength);
558    if (!arrayIncludes([32, 64, 96, 104, 112, 120, 128], params.tagLength)) {
559      throw exception("Invalid AES-GCM tag length", "OperationError");
560    }
561  } else if (name === "RSA-OAEP") {
562    params.iv = a?.label === undefined ? empty : sourceBytes(a.label);
563  } else if (name === "RSA-PSS") {
564    params.tagLength = pssSaltLength(a?.saltLength);
565  } else if (name !== "AES-KW") {
566    throw exception(
567      "The requested algorithm is not yet implemented",
568      "NotSupportedError",
569    );
570  }
571  return { name, params };
572}
573function keyFormat(value: unknown): CryptoKeyFormat {
574  const format = string(value);
575  if (!arrayIncludes(["raw", "jwk", "spki", "pkcs8"], format)) {
576    throw new TypeError("Invalid key format");
577  }
578  return format as CryptoKeyFormat;
579}
580const jwkMembers = [
581  "alg",
582  "crv",
583  "d",
584  "dp",
585  "dq",
586  "e",
587  "ext",
588  "k",
589  "key_ops",
590  "kty",
591  "n",
592  "oth",
593  "p",
594  "q",
595  "qi",
596  "use",
597  "x",
598  "y",
599];
600function jwkSequence(
601  value: unknown,
602  convert: (value: unknown) => unknown,
603): unknown[] {
604  const iterator = (value as { [SymbolIterator]?: unknown } | null | undefined)
605    ?.[SymbolIterator];
606  if (typeof iterator !== "function") {
607    throw new TypeError("Expected an iterable JWK sequence");
608  }
609  const iterable: Iterable<unknown> = ObjectCreate(null);
610  iterable[SymbolIterator] = () =>
611    apply(iterator, value, []) as Iterator<unknown>;
612  return arraySlice(ArrayFrom(iterable, convert));
613}
614function jwkDictionary(value: unknown): Record<string, unknown> {
615  value = dictionary(value);
616  const result: Record<string, unknown> = ObjectCreate(null);
617  for (let index = 0; index < jwkMembers.length; index++) {
618    const name = jwkMembers[index]!,
619      member = (value as Record<string, unknown>)[name];
620    if (member === undefined) continue;
621    if (name === "ext") result[name] = Boolean(member);
622    else if (name === "key_ops") result[name] = jwkSequence(member, string);
623    else if (name === "oth") {
624      result[name] = jwkSequence(member, (value) => {
625        value = dictionary(value);
626        const info: Record<string, unknown> = ObjectCreate(null),
627          members = ["d", "r", "t"];
628        for (let index = 0; index < members.length; index++) {
629          const name = members[index]!,
630            member = (value as Record<string, unknown>)[name];
631          if (member !== undefined) info[name] = string(member);
632        }
633        return info;
634      });
635    } else result[name] = string(member);
636  }
637  return result;
638}
639
640class CryptoKey {
641  constructor() {
642    throw new TypeError("CryptoKey cannot be constructed directly");
643  }
644  get type() {
645    return keyState(this).meta.type;
646  }
647  get extractable() {
648    return keyState(this).meta.extractable;
649  }
650  get algorithm() {
651    return keyState(this).algorithm;
652  }
653  get usages() {
654    return keyState(this).meta.publicUsages;
655  }
656}
657
658class SubtleCrypto {
659  constructor(token: typeof secret) {
660    if (token !== secret) throw new TypeError("Illegal constructor");
661    set(subtleStates, this, true);
662  }
663
664  async digest(algorithmIdentifier: unknown, data: unknown) {
665    required(arguments.length, 2);
666    state(subtleStates, this);
667    const name = algorithm(algorithmIdentifier);
668    if (!has(digestNames, name)) {
669      throw exception(
670        "The digest algorithm is not supported",
671        "NotSupportedError",
672      );
673    }
674    return settledBytes(digestBytes([name, sourceBytes(data)]));
675  }
676
677  async generateKey(
678    algorithmIdentifier: unknown,
679    extractable: unknown,
680    usages: unknown,
681  ) {
682    required(arguments.length, 3);
683    state(subtleStates, this);
684    const p = algorithmParams(algorithmIdentifier, true);
685    if (p.name === "HMAC" && !p.hash!) {
686      throw new TypeError("HMAC requires a hash algorithm");
687    }
688    const keyUsage = usageList(usages, p.name),
689      isExtractable = Boolean(extractable);
690    if (has(asymmetricNames, p.name)) {
691      let length = p.length ?? 0,
692        curve = p.curve ?? "",
693        exponent = p.exponent ?? empty;
694      if (startsWith(p.name, "RSA-") || startsWith(p.name, "RSASSA-")) {
695        if (
696          !numberIsInteger(length) || length < 512 || length > 8192 ||
697          !exponent?.length
698        ) throw exception("Invalid RSA key parameters", "DataError");
699        if (
700          exponent[exponent.length - 1] % 2 === 0 ||
701          (exponent.length === 1 && exponent[0] < 3)
702        ) throw exception("Invalid RSA public exponent", "DataError");
703      } else if (p.name === "ECDSA" || p.name === "ECDH") {
704        if (!arrayIncludes(["P-256", "P-384", "P-521"], curve)) {
705          throw exception("Unsupported elliptic curve", "NotSupportedError");
706        }
707        length = curve === "P-256" ? 256 : curve === "P-384" ? 384 : 521;
708      } else {
709        length = 256;
710      }
711      const result = await settled(
712        generateKeyPairBytes([
713          p.name,
714          p.hash ?? "",
715          length,
716          exponent,
717          curve,
718          keyUsage,
719          isExtractable,
720        ]),
721      );
722      const publicId = result[0],
723        privateId = result[1],
724        actualLength = result[2],
725        actualExponent = result[3];
726      const publicUsages = p.name === "RSA-OAEP"
727        ? arrayFilter(keyUsage, (u) => u === "encrypt" || u === "wrapKey")
728        : p.name === "ECDH" || p.name === "X25519"
729        ? []
730        : arrayFilter(keyUsage, (u) => u === "verify");
731      const privateUsages = p.name === "RSA-OAEP"
732        ? arrayFilter(keyUsage, (u) => u === "decrypt" || u === "unwrapKey")
733        : p.name === "ECDH" || p.name === "X25519"
734        ? keyUsage
735        : arrayFilter(keyUsage, (u) => u === "sign");
736      return {
737        publicKey: makeKey(
738          publicId,
739          metadata(
740            p.name,
741            actualLength,
742            p.hash,
743            publicUsages,
744            true,
745            "public",
746            curve || undefined,
747            actualExponent,
748          ),
749        ),
750        privateKey: makeKey(
751          privateId,
752          metadata(
753            p.name,
754            actualLength,
755            p.hash,
756            privateUsages,
757            isExtractable,
758            "private",
759            curve || undefined,
760            actualExponent,
761          ),
762        ),
763      };
764    }
765    if (!has(aesNames, p.name) && p.name !== "HMAC") {
766      throw exception(
767        "Key generation is not supported for this algorithm",
768        "NotSupportedError",
769      );
770    }
771    let length: number | undefined = p.length;
772    if (p.name === "HMAC" && length === undefined) length = hmacLength(p.hash!);
773    if (
774      !numberIsInteger(length) ||
775      (startsWith(p.name, "AES-") &&
776        !arrayIncludes([128, 192, 256], length!)) ||
777      (p.name === "HMAC" && (length! < 1 || length! > 65536))
778    ) {
779      throw exception("Invalid key length", "OperationError");
780    }
781    const hash = p.hash ?? "";
782    const id = await settled(
783      generateKeyBytes([p.name, length!, hash, keyUsage, isExtractable]),
784    );
785    return makeKey(
786      id,
787      metadata(p.name, length, hash || undefined, keyUsage, isExtractable),
788    );
789  }
790
791  async importKey(
792    format: unknown,
793    keyData: unknown,
794    algorithmIdentifier: unknown,
795    extractable: unknown,
796    usages: unknown,
797  ) {
798    required(arguments.length, 5);
799    state(subtleStates, this);
800    const formatName = keyFormat(format);
801    const jwk = formatName === "jwk" ? jwkDictionary(keyData) : undefined;
802    const p = algorithmParams(algorithmIdentifier, true),
803      keyUsage = usageList(usages, p.name),
804      isExtractable = Boolean(extractable);
805    if (p.name === "HMAC" && !p.hash!) {
806      throw new TypeError("HMAC requires a hash algorithm");
807    }
808    if (has(asymmetricNames, p.name)) {
809      if (formatName === "pkcs8" && keyUsage.length === 0) {
810        throw exception("Private key usages must not be empty", "SyntaxError");
811      }
812      if (formatName === "jwk") {
813        const result = await settled(
814          importJwk([
815            jwk!,
816            p.name,
817            p.hash ?? "",
818            p.curve ?? "",
819            keyUsage,
820            isExtractable,
821            p.name === "HMAC" ? p.length ?? 0 : 0,
822          ]),
823        );
824        return makeKey(
825          result.id,
826          metadata(
827            p.name,
828            result.length,
829            p.hash,
830            keyUsage,
831            isExtractable,
832            result.type,
833            p.curve,
834            result.exponent,
835          ),
836        );
837      }
838      const data = sourceBytes(keyData);
839      const result = await settled(
840        importAsymmetric([
841          formatName,
842          data,
843          p.name,
844          p.hash ?? "",
845          p.curve ?? "",
846          keyUsage,
847          isExtractable,
848        ]),
849      );
850      return makeKey(
851        result.id,
852        metadata(
853          p.name,
854          result.length,
855          p.hash,
856          keyUsage,
857          isExtractable,
858          result.type,
859          p.curve,
860          result.exponent,
861        ),
862      );
863    }
864    if (formatName !== "raw" && formatName !== "jwk") {
865      throw exception("Key format is not supported", "NotSupportedError");
866    }
867    let raw = formatName === "raw" ? sourceBytes(keyData) : undefined;
868    let length = raw ? raw.length * 8 : undefined;
869    if (raw && p.name === "HMAC") {
870      length = p.length ?? length;
871      if (
872        !numberIsInteger(length) || length! <= 0 || length! > raw.length * 8 ||
873        length! <= raw.length * 8 - 8
874      ) {
875        throw exception("Invalid HMAC key length", "DataError");
876      }
877      raw = own(subarray(raw, 0, ceil(length! / 8)));
878      if (length! % 8 !== 0) raw[raw.length - 1] &= 0xff << (8 - length! % 8);
879    }
880    if (
881      raw && startsWith(p.name, "AES-") &&
882      !arrayIncludes([128, 192, 256], length)
883    ) throw exception("Invalid AES key length", "DataError");
884    if (raw && p.name === "HMAC" && !raw.length) {
885      throw exception("Invalid HMAC key", "DataError");
886    }
887    const hash = p.hash ?? "";
888    if (formatName === "raw") {
889      const id = await settled(
890        importKeyBytes([
891          p.name,
892          raw!,
893          length ?? 0,
894          hash,
895          keyUsage,
896          isExtractable,
897        ]),
898      );
899      return makeKey(
900        id,
901        metadata(p.name, length, hash || undefined, keyUsage, isExtractable),
902      );
903    }
904    const idAndLength = await settled(
905      importJwk([
906        jwk!,
907        p.name,
908        hash,
909        p.curve ?? "",
910        keyUsage,
911        isExtractable,
912        p.name === "HMAC" ? p.length ?? 0 : 0,
913      ]),
914    );
915    const { id, length: jwkLength } = idAndLength;
916    return makeKey(
917      id,
918      metadata(p.name, jwkLength, hash || undefined, keyUsage, isExtractable),
919    );
920  }
921
922  async exportKey(format: unknown, key: unknown) {
923    required(arguments.length, 2);
924    state(subtleStates, this);
925    const internal = keyState(key), formatName = keyFormat(format);
926    if (!internal.meta.extractable) {
927      throw exception("CryptoKey is not extractable", "InvalidAccessError");
928    }
929    if (
930      formatName === "raw" || formatName === "spki" || formatName === "pkcs8"
931    ) return settledBytes(exportKeyBytes([internal.id, formatName]));
932    return settled(exportKeyJwk(internal.id));
933  }
934
935  async encrypt(algorithmIdentifier: unknown, key: unknown, data: unknown) {
936    required(arguments.length, 3);
937    state(subtleStates, this);
938    const p = normalizeInputAlgorithm(algorithmIdentifier),
939      k = checkKeyOperation(key, "encrypt", p.name);
940    if (p.name === "AES-KW") {
941      throw exception(
942        "AES-KW is only available through wrapKey and unwrapKey",
943        "InvalidAccessError",
944      );
945    }
946    p.params.hash = k.meta.hash ?? "";
947    return settledBytes(emptyRun("encrypt", k.id, sourceBytes(data), p.params));
948  }
949  async decrypt(algorithmIdentifier: unknown, key: unknown, data: unknown) {
950    required(arguments.length, 3);
951    state(subtleStates, this);
952    const p = normalizeInputAlgorithm(algorithmIdentifier),
953      k = checkKeyOperation(key, "decrypt", p.name);
954    if (p.name === "AES-KW") {
955      throw exception(
956        "AES-KW is only available through wrapKey and unwrapKey",
957        "InvalidAccessError",
958      );
959    }
960    p.params.hash = k.meta.hash ?? "";
961    return settledBytes(emptyRun("decrypt", k.id, sourceBytes(data), p.params));
962  }
963  async sign(algorithmIdentifier: unknown, key: unknown, data: unknown) {
964    required(arguments.length, 3);
965    state(subtleStates, this);
966    const p = algorithmParams(algorithmIdentifier),
967      k = checkKeyOperation(key, "sign", p.name);
968    if (
969      !arrayIncludes([
970        "HMAC",
971        "RSA-PSS",
972        "RSASSA-PKCS1-V1_5",
973        "ECDSA",
974        "ED25519",
975      ], p.name)
976    ) {
977      throw exception(
978        "The requested signing algorithm is not supported",
979        "NotSupportedError",
980      );
981    }
982    if (p.name === "ECDSA" && !p.hash!) {
983      throw new TypeError("ECDSA requires a hash");
984    }
985    if (k.meta.hash && p.hash && p.hash !== k.meta.hash) {
986      throw exception(
987        "Operation hash does not match the key",
988        "InvalidAccessError",
989      );
990    }
991    return settledBytes(
992      emptyRun("sign", k.id, sourceBytes(data), {
993        hash: k.meta.hash ?? p.hash ?? "",
994        tagLength: p.name === "RSA-PSS" ? p.saltLength : 0,
995      }),
996    );
997  }
998  async verify(
999    algorithmIdentifier: unknown,
1000    key: unknown,
1001    signature: unknown,
1002    data: unknown,
1003  ) {
1004    required(arguments.length, 4);
1005    state(subtleStates, this);
1006    const p = algorithmParams(algorithmIdentifier),
1007      k = checkKeyOperation(key, "verify", p.name);
1008    if (
1009      !arrayIncludes([
1010        "HMAC",
1011        "RSA-PSS",
1012        "RSASSA-PKCS1-V1_5",
1013        "ECDSA",
1014        "ED25519",
1015      ], p.name)
1016    ) {
1017      throw exception(
1018        "The requested signing algorithm is not supported",
1019        "NotSupportedError",
1020      );
1021    }
1022    if (p.name === "ECDSA" && !p.hash!) {
1023      throw new TypeError("ECDSA requires a hash");
1024    }
1025    if (k.meta.hash && p.hash && p.hash !== k.meta.hash) {
1026      throw exception(
1027        "Operation hash does not match the key",
1028        "InvalidAccessError",
1029      );
1030    }
1031    const sig = sourceBytes(signature),
1032      result = await settled(
1033        emptyRun("verify", k.id, sourceBytes(data), {
1034          hash: k.meta.hash ?? p.hash ?? "",
1035          tagLength: p.name === "RSA-PSS" ? p.saltLength : 0,
1036          additionalData: sig,
1037        }),
1038      );
1039    return result[0] === 1;
1040  }
1041  async deriveBits(
1042    algorithmIdentifier: unknown,
1043    baseKey: unknown,
1044    length: unknown = null,
1045  ) {
1046    required(arguments.length, 2);
1047    state(subtleStates, this);
1048    const p = dictionary(algorithmIdentifier),
1049      name = algorithm(p),
1050      k = checkKeyOperation(baseKey, "deriveBits", name);
1051    if (name === "ECDH" || name === "X25519") {
1052      const maximum = ceil(k.meta!.length! / 8) * 8;
1053      const bitLength = length == null ? maximum : Number(length);
1054      if (
1055        !numberIsInteger(bitLength) || bitLength < 0 ||
1056        bitLength > maximum
1057      ) throw exception("Invalid derived bit length", "OperationError");
1058      return settledBytes(
1059        emptyRun("deriveBits", k.id, empty, {
1060          peerId: peerKeyId(p, k),
1061          tagLength: bitLength,
1062        }),
1063      );
1064    }
1065    if (name !== "PBKDF2" && name !== "HKDF") {
1066      throw exception(
1067        "The requested derivation algorithm is not yet implemented",
1068        "NotSupportedError",
1069      );
1070    }
1071    const bitLength = Number(length);
1072    if (
1073      length === null || !numberIsInteger(bitLength) || bitLength < 0 ||
1074      bitLength % 8
1075    ) {
1076      throw exception("Invalid derived bit length", "OperationError");
1077    }
1078    const params = {
1079      hash: hashName(p.hash!),
1080      salt: sourceBytes(p.salt),
1081      info: name === "HKDF" ? sourceBytes(p.info) : empty,
1082      iterations: name === "PBKDF2" ? Number(p.iterations) : 0,
1083      tagLength: bitLength,
1084    };
1085    if (
1086      name === "PBKDF2" &&
1087      (!numberIsInteger(params.iterations) || params.iterations <= 0)
1088    ) throw exception("Invalid PBKDF2 iteration count", "OperationError");
1089    return settledBytes(emptyRun("deriveBits", k.id, empty, params));
1090  }
1091  async deriveKey(
1092    algorithmIdentifier: unknown,
1093    baseKey: unknown,
1094    derivedKeyType: unknown,
1095    extractable: unknown,
1096    usages: unknown,
1097  ) {
1098    required(arguments.length, 5);
1099    state(subtleStates, this);
1100    const source = dictionary(algorithmIdentifier),
1101      sourceName = algorithm(source);
1102    const base = checkKeyOperation(baseKey, "deriveKey", sourceName);
1103    if (
1104      sourceName !== "PBKDF2" && sourceName !== "HKDF" &&
1105      sourceName !== "ECDH" && sourceName !== "X25519"
1106    ) {
1107      throw exception(
1108        "The requested derivation algorithm is not yet implemented",
1109        "NotSupportedError",
1110      );
1111    }
1112    const target = algorithmParams(derivedKeyType, true);
1113    if (!has(aesNames, target.name) && target.name !== "HMAC") {
1114      throw exception(
1115        "Derived key algorithm is not supported",
1116        "NotSupportedError",
1117      );
1118    }
1119    const length = target.length ??
1120      (target.name === "HMAC" ? hmacLength(target.hash!) : undefined);
1121    if (
1122      !numberIsInteger(length) ||
1123      (startsWith(target.name, "AES-") &&
1124        !arrayIncludes([128, 192, 256], length!)) ||
1125      (target.name === "HMAC" && (length! < 1 || length! > 65536))
1126    ) throw exception("Invalid derived key length", "OperationError");
1127    const keyUsage = usageList(usages, target.name),
1128      isExtractable = Boolean(extractable);
1129    const params = sourceName === "ECDH" || sourceName === "X25519"
1130      ? { hash: "", salt: empty, info: empty, iterations: 0 }
1131      : {
1132        hash: hashName(source.hash),
1133        salt: sourceBytes(source.salt),
1134        info: sourceName === "HKDF" ? sourceBytes(source.info) : empty,
1135        iterations: sourceName === "PBKDF2" ? Number(source.iterations) : 0,
1136      };
1137    if (
1138      sourceName === "PBKDF2" &&
1139      (!numberIsInteger(params.iterations) || params.iterations <= 0)
1140    ) throw exception("Invalid PBKDF2 iteration count", "OperationError");
1141    const targetHash = target.hash ?? "";
1142    const peerId = sourceName === "ECDH" || sourceName === "X25519"
1143      ? peerKeyId(source, base)
1144      : 0;
1145    const id = await settled(
1146      deriveKeyBytes([
1147        base.id,
1148        params.hash,
1149        params.salt,
1150        params.iterations,
1151        params.info,
1152        length!,
1153        target.name,
1154        targetHash,
1155        keyUsage,
1156        isExtractable,
1157        peerId,
1158      ]),
1159    );
1160    return makeKey(
1161      id,
1162      metadata(
1163        target.name,
1164        length,
1165        targetHash || undefined,
1166        keyUsage,
1167        isExtractable,
1168      ),
1169    );
1170  }
1171  async wrapKey(
1172    format: unknown,
1173    key: unknown,
1174    wrappingKey: unknown,
1175    wrapAlgorithm: unknown,
1176  ) {
1177    required(arguments.length, 4);
1178    state(subtleStates, this);
1179    if (!keyState(key).meta.extractable) {
1180      throw exception("CryptoKey is not extractable", "InvalidAccessError");
1181    }
1182    const p = normalizeInputAlgorithm(wrapAlgorithm),
1183      wrapping = checkKeyOperation(wrappingKey, "wrapKey", p.name);
1184    if (
1185      !arrayIncludes(
1186        ["AES-KW", "AES-CBC", "AES-CTR", "AES-GCM", "RSA-OAEP"],
1187        p.name,
1188      )
1189    ) {
1190      throw exception(
1191        "The wrapping algorithm is not supported",
1192        "NotSupportedError",
1193      );
1194    }
1195    p.params.hash = wrapping.meta.hash ?? "";
1196    const formatName = keyFormat(format);
1197    return settledBytes(
1198      wrapKeyBytes([
1199        wrapping.id,
1200        keyState(key).id,
1201        formatName,
1202        p.name,
1203        p.params.hash,
1204        p.params.iv ?? empty,
1205        p.params.counter ?? empty,
1206        p.params.additionalData ?? empty,
1207        p.params.tagLength ?? 128,
1208      ]),
1209    );
1210  }
1211  async unwrapKey(
1212    format: unknown,
1213    wrappedKey: unknown,
1214    unwrappingKey: unknown,
1215    unwrapAlgorithm: unknown,
1216    unwrappedKeyAlgorithm: unknown,
1217    extractable: unknown,
1218    usages: unknown,
1219  ) {
1220    required(arguments.length, 7);
1221    state(subtleStates, this);
1222    const p = normalizeInputAlgorithm(unwrapAlgorithm),
1223      wrapping = checkKeyOperation(unwrappingKey, "unwrapKey", p.name);
1224    if (
1225      !arrayIncludes(
1226        ["AES-KW", "AES-CBC", "AES-CTR", "AES-GCM", "RSA-OAEP"],
1227        p.name,
1228      )
1229    ) {
1230      throw exception(
1231        "The unwrapping algorithm is not supported",
1232        "NotSupportedError",
1233      );
1234    }
1235    p.params.hash = wrapping.meta.hash ?? "";
1236    const formatName = keyFormat(format);
1237    const target = algorithmParams(unwrappedKeyAlgorithm, true),
1238      keyUsage = usageList(usages, target.name);
1239    const result = await settled(
1240      unwrapKeyBytes([
1241        wrapping.id,
1242        formatName,
1243        sourceBytes(wrappedKey),
1244        target.name,
1245        target.hash ?? "",
1246        target.curve ?? "",
1247        keyUsage,
1248        Boolean(extractable),
1249        p.params.iv ?? empty,
1250        p.params.counter ?? empty,
1251        p.params.additionalData ?? empty,
1252        p.params.tagLength ?? 128,
1253        target.length ?? 0,
1254      ]),
1255    );
1256    return makeKey(
1257      result.id,
1258      metadata(
1259        target.name,
1260        result.length,
1261        target.hash,
1262        keyUsage,
1263        Boolean(extractable),
1264        result.type,
1265        target.curve,
1266        result.exponent,
1267      ),
1268    );
1269  }
1270}
1271
1272class Crypto {
1273  constructor(token: typeof secret) {
1274    if (token !== secret) throw new TypeError("Illegal constructor");
1275    set(cryptoStates, this, { subtle: new SubtleCrypto(secret) });
1276  }
1277  get subtle() {
1278    return state(cryptoStates, this).subtle;
1279  }
1280  getRandomValues<T extends CryptoView>(array: T): T {
1281    required(arguments.length, 1);
1282    state(cryptoStates, this);
1283    if (!isView(array)) throw new TypeError("Expected an ArrayBufferView");
1284    let source;
1285    try {
1286      source = typedBuffer(array);
1287    } catch {
1288      source = dataViewBuffer(array);
1289    }
1290    try {
1291      getArrayBufferLength(source);
1292    } catch {
1293      throw new TypeError("Shared buffers are not allowed");
1294    }
1295    if (!has(integerTags, tag(array))) {
1296      throw exception("Expected an integer TypedArray", "TypeMismatchError");
1297    }
1298    const length = byteLength(array);
1299    if (length > 65536) {
1300      throw exception(
1301        "The requested length exceeds 65536 bytes",
1302        "QuotaExceededError",
1303      );
1304    }
1305    if (length === 0) return array;
1306    if (resizable(source)) {
1307      const generated = new Uint8Array(length);
1308      randomBytes(generated);
1309      setBytes(new Uint8Array(source, byteOffset(array), length), generated);
1310    } else {
1311      randomBytes(array);
1312    }
1313    return array;
1314  }
1315  randomUUID() {
1316    state(cryptoStates, this);
1317    return uuid(null);
1318  }
1319}
1320
1321export type { Crypto, CryptoKey, SubtleCrypto };
1322
1323installInterface("CryptoKey", CryptoKey);
1324installInterface("SubtleCrypto", SubtleCrypto);
1325installInterface("Crypto", Crypto);
1326const crypto = new Crypto(secret);
1327define(globalThis, "crypto", {
1328  value: crypto,
1329  writable: true,
1330  configurable: true,
1331});