• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PeculiarVentures / webcrypto-core / 25156410664

30 Apr 2026 08:51AM UTC coverage: 93.53%. First build
25156410664

Pull #70

github

web-flow
Merge 96df2b527 into 5f5cf1a85
Pull Request #70: Modernize tooling and Node runtime imports

246 of 281 branches covered (87.54%)

120 of 124 new or added lines in 26 files covered. (96.77%)

665 of 711 relevant lines covered (93.53%)

29.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.55
/src/subtle.ts
1
import * as bytes from "@peculiar/utils/bytes";
2
import * as encoding from "@peculiar/utils/encoding";
3
import { AlgorithmError } from "./errors";
4
import { ProviderCrypto } from "./provider";
5
import { ProviderStorage } from "./storage";
6
import { HashedAlgorithm } from "./types";
7
import { CryptoKey } from "./crypto_key";
8

9
const keyFormatMap: Record<KeyFormat, KeyType[]> = {
24✔
10
  jwk: ["private", "public", "secret"],
11
  pkcs8: ["private"],
12
  spki: ["public"],
13
  raw: ["secret", "public"],
14
};
15

16
const sourceBufferKeyFormats = ["pkcs8", "spki", "raw"];
24✔
17
export class SubtleCrypto implements globalThis.SubtleCrypto {
18
  public static isHashedAlgorithm(data: any): data is HashedAlgorithm {
19
    return data
10✔
20
      && typeof data === "object"
21
      && "name" in data
22
      && "hash" in data
23
      ? true
24
      : false;
25
  }
26

27
  public providers = new ProviderStorage();
6✔
28

29
  // @internal
30
  public readonly [Symbol.toStringTag] = "SubtleCrypto";
6✔
31

32
  public async digest(algorithm: AlgorithmIdentifier, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
33
  public async digest(...args: any[]): Promise<ArrayBuffer> {
34
    this.checkRequiredArguments(args, 2, "digest");
12✔
35
    const [algorithm, data, ...params] = args;
12✔
36

37
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
12✔
38
    const preparedData = bytes.toArrayBuffer(data);
12✔
39

40
    const provider = this.getProvider(preparedAlgorithm.name);
12✔
41
    const result = await provider.digest(preparedAlgorithm, preparedData, ...params);
12✔
42

43
    return result;
6✔
44
  }
45

46
  public async generateKey(algorithm: "X25519", extractable: boolean, keyUsages: readonly ("deriveBits" | "deriveKey")[], ...args: any[]): Promise<CryptoKeyPair>;
47
  public async generateKey(algorithm: "Ed25519", extractable: boolean, keyUsages: readonly ("sign" | "verify")[], ...args: any[]): Promise<CryptoKeyPair>;
48
  public async generateKey(algorithm: RsaHashedKeyGenParams | EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[], ...args: any[]): Promise<globalThis.CryptoKeyPair>;
49
  public async generateKey(algorithm: AesKeyGenParams | HmacKeyGenParams | Pbkdf2Params, extractable: boolean, keyUsages: KeyUsage[], ...args: any[]): Promise<globalThis.CryptoKey>;
50
  public async generateKey(algorithm: AlgorithmIdentifier, extractable: boolean, keyUsages: Iterable<KeyUsage>, ...args: any[]): Promise<globalThis.CryptoKeyPair | globalThis.CryptoKey>;
51
  public async generateKey(...args: any[]): Promise<globalThis.CryptoKeyPair | globalThis.CryptoKey> {
52
    this.checkRequiredArguments(args, 3, "generateKey");
2✔
53
    const [algorithm, extractable, keyUsages, ...params] = args;
2✔
54

55
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
2✔
56

57
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
58
    const result = await provider.generateKey({
2✔
59
      ...preparedAlgorithm, name: provider.name,
60
    }, extractable, keyUsages, ...params);
61

62
    return result;
2✔
63
  }
64

65
  public async sign(algorithm: AlgorithmIdentifier, key: globalThis.CryptoKey, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
66
  public async sign(...args: any[]): Promise<ArrayBuffer> {
67
    this.checkRequiredArguments(args, 3, "sign");
2✔
68
    const [algorithm, key, data, ...params] = args;
2✔
69
    this.checkCryptoKey(key);
2✔
70

71
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
2✔
72
    const preparedData = bytes.toArrayBuffer(data);
2✔
73

74
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
75
    const result = await provider.sign({
2✔
76
      ...preparedAlgorithm, name: provider.name,
77
    }, key, preparedData, ...params);
78

79
    return result;
2✔
80
  }
81

82
  public async verify(algorithm: AlgorithmIdentifier, key: globalThis.CryptoKey, signature: BufferSource, data: BufferSource, ...args: any[]): Promise<boolean>;
83
  public async verify(...args: any[]): Promise<boolean> {
84
    this.checkRequiredArguments(args, 4, "verify");
2✔
85
    const [algorithm, key, signature, data, ...params] = args;
2✔
86
    this.checkCryptoKey(key);
2✔
87

88
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
2✔
89
    const preparedData = bytes.toArrayBuffer(data);
2✔
90
    const preparedSignature = bytes.toArrayBuffer(signature);
2✔
91

92
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
93
    const result = await provider.verify({
2✔
94
      ...preparedAlgorithm, name: provider.name,
95
    }, key, preparedSignature, preparedData, ...params);
96

97
    return result;
2✔
98
  }
99

100
  public async encrypt(algorithm: AlgorithmIdentifier, key: globalThis.CryptoKey, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
101
  public async encrypt(...args: any[]): Promise<ArrayBuffer> {
102
    this.checkRequiredArguments(args, 3, "encrypt");
2✔
103
    const [algorithm, key, data, ...params] = args;
2✔
104
    this.checkCryptoKey(key);
2✔
105

106
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
2✔
107
    const preparedData = bytes.toArrayBuffer(data);
2✔
108

109
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
110
    const result = await provider.encrypt({
2✔
111
      ...preparedAlgorithm, name: provider.name,
112
    }, key, preparedData, { keyUsage: true }, ...params);
113

114
    return result;
2✔
115
  }
116

117
  public async decrypt(algorithm: AlgorithmIdentifier, key: globalThis.CryptoKey, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
118
  public async decrypt(...args: any[]): Promise<ArrayBuffer> {
119
    this.checkRequiredArguments(args, 3, "decrypt");
2✔
120
    const [algorithm, key, data, ...params] = args;
2✔
121
    this.checkCryptoKey(key);
2✔
122

123
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
2✔
124
    const preparedData = bytes.toArrayBuffer(data);
2✔
125

126
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
127
    const result = await provider.decrypt({
2✔
128
      ...preparedAlgorithm, name: provider.name,
129
    }, key, preparedData, { keyUsage: true }, ...params);
130

131
    return result;
2✔
132
  }
133

134
  public async deriveBits(algorithm: AlgorithmIdentifier, baseKey: globalThis.CryptoKey, length: number, ...args: any[]): Promise<ArrayBuffer>;
135
  public async deriveBits(...args: any[]): Promise<ArrayBuffer> {
136
    this.checkRequiredArguments(args, 3, "deriveBits");
2✔
137
    const [algorithm, baseKey, length, ...params] = args;
2✔
138
    this.checkCryptoKey(baseKey);
2✔
139

140
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
2✔
141

142
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
143
    const result = await provider.deriveBits({
2✔
144
      ...preparedAlgorithm, name: provider.name,
145
    }, baseKey, length, { keyUsage: true }, ...params);
146

147
    return result;
2✔
148
  }
149

150
  public async deriveKey(
151
    algorithm: AlgorithmIdentifier,
152
    baseKey: globalThis.CryptoKey,
153
    derivedKeyType: AlgorithmIdentifier,
154
    extractable: boolean,
155
    keyUsages: KeyUsage[],
156
    ...args: any[]): Promise<globalThis.CryptoKey>;
157
  public async deriveKey(...args: any[]): Promise<globalThis.CryptoKey> {
158
    this.checkRequiredArguments(args, 5, "deriveKey");
2✔
159
    const [algorithm, baseKey, derivedKeyType, extractable, keyUsages, ...params] = args;
2✔
160
    // check derivedKeyType
161
    const preparedDerivedKeyType = this.prepareAlgorithm(derivedKeyType);
2✔
162
    const importProvider = this.getProvider(preparedDerivedKeyType.name);
2✔
163
    importProvider.checkDerivedKeyParams(preparedDerivedKeyType);
2✔
164

165
    // derive bits
166
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
2✔
167
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
168
    provider.checkCryptoKey(baseKey, "deriveKey");
2✔
169
    const derivedBits = await provider.deriveBits({
2✔
170
      ...preparedAlgorithm, name: provider.name,
171
    }, baseKey, (derivedKeyType as any).length || 512, { keyUsage: false }, ...params);
2!
172

173
    // import derived key
174
    return this.importKey("raw", derivedBits, derivedKeyType, extractable, keyUsages, ...params);
2✔
175
  }
176

177
  public async exportKey(format: "raw" | "spki" | "pkcs8", key: globalThis.CryptoKey, ...args: any[]): Promise<ArrayBuffer>;
178
  public async exportKey(format: "jwk", key: globalThis.CryptoKey, ...args: any[]): Promise<JsonWebKey>;
179
  public async exportKey(format: KeyFormat, key: globalThis.CryptoKey, ...args: any[]): Promise<JsonWebKey | ArrayBuffer>;
180
  public async exportKey(...args: any[]): Promise<JsonWebKey | ArrayBuffer> {
181
    this.checkRequiredArguments(args, 2, "exportKey");
8✔
182
    const [format, key, ...params] = args;
8✔
183
    this.checkCryptoKey(key);
8✔
184

185
    if (!keyFormatMap[format as KeyFormat]) {
8✔
186
      throw new TypeError("Invalid keyFormat argument");
2✔
187
    }
188

189
    if (!keyFormatMap[format as KeyFormat].includes(key.type)) {
6✔
190
      throw new DOMException("The key is not of the expected type");
2✔
191
    }
192

193
    const provider = this.getProvider(key.algorithm.name);
4✔
194
    const result = await provider.exportKey(format, key, ...params);
4✔
195

196
    return result;
4✔
197
  }
198

199
  public async importKey(...args: any[]): Promise<globalThis.CryptoKey> {
200
    this.checkRequiredArguments(args, 5, "importKey");
20✔
201
    const [format, keyData, algorithm, extractable, keyUsages, ...params] = args;
20✔
202

203
    const preparedAlgorithm = this.prepareAlgorithm(algorithm);
20✔
204
    const provider = this.getProvider(preparedAlgorithm.name);
20✔
205

206
    if (format === "jwk") {
20✔
207
      if (typeof keyData !== "object" || !(keyData as JsonWebKey).kty) {
4✔
208
        throw new TypeError("Key data must be an object for JWK import");
2✔
209
      }
210
    } else if (sourceBufferKeyFormats.includes(format)) {
16✔
211
      if (!bytes.isBufferSource(keyData)) {
14✔
212
        throw new TypeError("Key data must be a BufferSource for non-JWK formats");
2✔
213
      }
214
    } else {
215
      throw new TypeError("The provided value is not of type '(ArrayBuffer or ArrayBufferView or JsonWebKey)'");
2✔
216
    }
217

218
    return provider.importKey(format, keyData, {
14✔
219
      ...preparedAlgorithm, name: provider.name,
220
    }, extractable, keyUsages, ...params);
221
  }
222

223
  public async wrapKey(format: KeyFormat, key: globalThis.CryptoKey, wrappingKey: globalThis.CryptoKey, wrapAlgorithm: AlgorithmIdentifier, ...args: any[]): Promise<ArrayBuffer> {
224
    let keyData = await this.exportKey(format, key, ...args);
2✔
225
    if (format === "jwk") {
2!
226
      const json = JSON.stringify(keyData);
×
NEW
227
      keyData = bytes.toArrayBuffer(encoding.utf8.encode(json));
×
228
    }
229

230
    // encrypt key data
231
    const preparedAlgorithm = this.prepareAlgorithm(wrapAlgorithm);
2✔
232
    const preparedData = bytes.toArrayBuffer(keyData as ArrayBuffer);
2✔
233
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
234
    return provider.encrypt({
2✔
235
      ...preparedAlgorithm, name: provider.name,
236
    }, wrappingKey, preparedData, { keyUsage: false }, ...args);
237
  }
238

239
  public async unwrapKey(
240
    format: KeyFormat,
241
    wrappedKey: BufferSource,
242
    unwrappingKey: globalThis.CryptoKey,
243
    unwrapAlgorithm: AlgorithmIdentifier,
244
    unwrappedKeyAlgorithm: AlgorithmIdentifier,
245
    extractable: boolean,
246
    keyUsages: KeyUsage[],
247
    ...args: any[]): Promise<globalThis.CryptoKey> {
248
    // decrypt wrapped key
249
    const preparedAlgorithm = this.prepareAlgorithm(unwrapAlgorithm);
2✔
250
    const preparedData = bytes.toArrayBuffer(wrappedKey);
2✔
251
    const provider = this.getProvider(preparedAlgorithm.name);
2✔
252
    let keyData = await provider.decrypt({
2✔
253
      ...preparedAlgorithm, name: provider.name,
254
    }, unwrappingKey, preparedData, { keyUsage: false }, ...args);
255
    if (format === "jwk") {
2!
256
      try {
×
NEW
257
        keyData = JSON.parse(encoding.utf8.decode(keyData));
×
258
      } catch (e) {
259
        const error = new TypeError("wrappedKey: Is not a JSON");
×
260
        (error as any).internal = e;
×
261
        throw error;
×
262
      }
263
    }
264

265
    // import key
266
    return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages, ...args);
2✔
267
  }
268

269
  protected checkRequiredArguments(args: any[], size: number, methodName: string): void {
270
    if (args.length < size) {
54✔
271
      throw new TypeError(`Failed to execute '${methodName}' on 'SubtleCrypto': ${size} arguments required, but only ${args.length} present`);
2✔
272
    }
273
  }
274

275
  protected prepareAlgorithm(algorithm: AlgorithmIdentifier): Algorithm | HashedAlgorithm {
276
    if (typeof algorithm === "string") {
54✔
277
      return { name: algorithm } as Algorithm;
44✔
278
    }
279
    if (SubtleCrypto.isHashedAlgorithm(algorithm)) {
10✔
280
      const preparedAlgorithm = { ...algorithm };
4✔
281
      preparedAlgorithm.hash = this.prepareAlgorithm(algorithm.hash);
4✔
282
      return preparedAlgorithm as HashedAlgorithm;
4✔
283
    }
284
    return { ...algorithm };
6✔
285
  }
286

287
  protected getProvider(name: string): ProviderCrypto {
288
    const provider = this.providers.get(name);
52✔
289
    if (!provider) {
52✔
290
      throw new AlgorithmError("Unrecognized name");
2✔
291
    }
292
    return provider;
50✔
293
  }
294

295
  protected checkCryptoKey(key: globalThis.CryptoKey): asserts key is CryptoKey {
296
    if (!(key instanceof CryptoKey)) {
18!
NEW
297
      throw new TypeError("Key is not of type 'CryptoKey'");
×
298
    }
299
  }
300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc