1 | import { getStrConverter, getOutputConverter } from "./converters";
|
2 |
|
3 | import {
|
4 | FormatType,
|
5 | EncodingType,
|
6 | FixedLengthOptionsEncodingType,
|
7 | FixedLengthOptionsNoEncodingType,
|
8 | FormatNoTextType,
|
9 | packedValue,
|
10 | GenericInputType,
|
11 | } from "./custom_types";
|
12 |
|
13 | export const TWO_PWR_32 = 4294967296;
|
14 |
|
15 |
|
16 | export const K_sha2 = [
|
17 | 0x428a2f98,
|
18 | 0x71374491,
|
19 | 0xb5c0fbcf,
|
20 | 0xe9b5dba5,
|
21 | 0x3956c25b,
|
22 | 0x59f111f1,
|
23 | 0x923f82a4,
|
24 | 0xab1c5ed5,
|
25 | 0xd807aa98,
|
26 | 0x12835b01,
|
27 | 0x243185be,
|
28 | 0x550c7dc3,
|
29 | 0x72be5d74,
|
30 | 0x80deb1fe,
|
31 | 0x9bdc06a7,
|
32 | 0xc19bf174,
|
33 | 0xe49b69c1,
|
34 | 0xefbe4786,
|
35 | 0x0fc19dc6,
|
36 | 0x240ca1cc,
|
37 | 0x2de92c6f,
|
38 | 0x4a7484aa,
|
39 | 0x5cb0a9dc,
|
40 | 0x76f988da,
|
41 | 0x983e5152,
|
42 | 0xa831c66d,
|
43 | 0xb00327c8,
|
44 | 0xbf597fc7,
|
45 | 0xc6e00bf3,
|
46 | 0xd5a79147,
|
47 | 0x06ca6351,
|
48 | 0x14292967,
|
49 | 0x27b70a85,
|
50 | 0x2e1b2138,
|
51 | 0x4d2c6dfc,
|
52 | 0x53380d13,
|
53 | 0x650a7354,
|
54 | 0x766a0abb,
|
55 | 0x81c2c92e,
|
56 | 0x92722c85,
|
57 | 0xa2bfe8a1,
|
58 | 0xa81a664b,
|
59 | 0xc24b8b70,
|
60 | 0xc76c51a3,
|
61 | 0xd192e819,
|
62 | 0xd6990624,
|
63 | 0xf40e3585,
|
64 | 0x106aa070,
|
65 | 0x19a4c116,
|
66 | 0x1e376c08,
|
67 | 0x2748774c,
|
68 | 0x34b0bcb5,
|
69 | 0x391c0cb3,
|
70 | 0x4ed8aa4a,
|
71 | 0x5b9cca4f,
|
72 | 0x682e6ff3,
|
73 | 0x748f82ee,
|
74 | 0x78a5636f,
|
75 | 0x84c87814,
|
76 | 0x8cc70208,
|
77 | 0x90befffa,
|
78 | 0xa4506ceb,
|
79 | 0xbef9a3f7,
|
80 | 0xc67178f2,
|
81 | ];
|
82 |
|
83 |
|
84 | export const H_trunc = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4];
|
85 |
|
86 |
|
87 | export const H_full = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
88 |
|
89 | export const sha_variant_error = "Chosen SHA variant is not supported";
|
90 | export const mac_rounds_error = "Cannot set numRounds with MAC";
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | export function packedLEConcat(a: packedValue, b: packedValue): packedValue {
|
100 | let i, arrOffset;
|
101 | const aByteLen = a["binLen"] >>> 3,
|
102 | bByteLen = b["binLen"] >>> 3,
|
103 | leftShiftAmount = aByteLen << 3,
|
104 | rightShiftAmount = (4 - aByteLen) << 3;
|
105 |
|
106 |
|
107 | if (aByteLen % 4 !== 0) {
|
108 | for (i = 0; i < bByteLen; i += 4) {
|
109 | arrOffset = (aByteLen + i) >>> 2;
|
110 |
|
111 | a["value"][arrOffset] |= b["value"][i >>> 2] << leftShiftAmount;
|
112 | a["value"].push(0);
|
113 | a["value"][arrOffset + 1] |= b["value"][i >>> 2] >>> rightShiftAmount;
|
114 | }
|
115 |
|
116 | |
117 |
|
118 |
|
119 | if ((a["value"].length << 2) - 4 >= bByteLen + aByteLen) {
|
120 | a["value"].pop();
|
121 | }
|
122 |
|
123 | return { value: a["value"], binLen: a["binLen"] + b["binLen"] };
|
124 | } else {
|
125 | return { value: a["value"].concat(b["value"]), binLen: a["binLen"] + b["binLen"] };
|
126 | }
|
127 | }
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | export function getOutputOpts(options?: {
|
137 | outputUpper?: boolean;
|
138 | b64Pad?: string;
|
139 | shakeLen?: number;
|
140 | outputLen?: number;
|
141 | }): { outputUpper: boolean; b64Pad: string; outputLen: number } {
|
142 | const retVal = { outputUpper: false, b64Pad: "=", outputLen: -1 },
|
143 | outputOptions: { outputUpper?: boolean; b64Pad?: string; shakeLen?: number; outputLen?: number } = options || {},
|
144 | lenErrstr = "Output length must be a multiple of 8";
|
145 |
|
146 | retVal["outputUpper"] = outputOptions["outputUpper"] || false;
|
147 |
|
148 | if (outputOptions["b64Pad"]) {
|
149 | retVal["b64Pad"] = outputOptions["b64Pad"];
|
150 | }
|
151 |
|
152 | if (outputOptions["outputLen"]) {
|
153 | if (outputOptions["outputLen"] % 8 !== 0) {
|
154 | throw new Error(lenErrstr);
|
155 | }
|
156 | retVal["outputLen"] = outputOptions["outputLen"];
|
157 | } else if (outputOptions["shakeLen"]) {
|
158 | if (outputOptions["shakeLen"] % 8 !== 0) {
|
159 | throw new Error(lenErrstr);
|
160 | }
|
161 | retVal["outputLen"] = outputOptions["shakeLen"];
|
162 | }
|
163 |
|
164 | if ("boolean" !== typeof retVal["outputUpper"]) {
|
165 | throw new Error("Invalid outputUpper formatting option");
|
166 | }
|
167 |
|
168 | if ("string" !== typeof retVal["b64Pad"]) {
|
169 | throw new Error("Invalid b64Pad formatting option");
|
170 | }
|
171 |
|
172 | return retVal;
|
173 | }
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 | export function parseInputOption(
|
184 | key: string,
|
185 | value: GenericInputType | undefined,
|
186 | bigEndianMod: -1 | 1,
|
187 | fallback?: packedValue
|
188 | ): packedValue {
|
189 | const errStr = key + " must include a value and format";
|
190 | if (!value) {
|
191 | if (!fallback) {
|
192 | throw new Error(errStr);
|
193 | }
|
194 | return fallback;
|
195 | }
|
196 |
|
197 | if (typeof value["value"] === "undefined" || !value["format"]) {
|
198 | throw new Error(errStr);
|
199 | }
|
200 |
|
201 | return getStrConverter(
|
202 | value["format"],
|
203 |
|
204 |
|
205 | value["encoding"] || "UTF8",
|
206 | bigEndianMod
|
207 | )(value["value"]);
|
208 | }
|
209 |
|
210 | export abstract class jsSHABase<StateT, VariantT> {
|
211 | |
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | protected readonly shaVariant: VariantT;
|
218 | protected readonly inputFormat: FormatType;
|
219 | protected readonly utfType: EncodingType;
|
220 | protected readonly numRounds: number;
|
221 |
|
222 |
|
223 | protected abstract intermediateState: StateT;
|
224 | protected keyWithIPad: number[];
|
225 | protected keyWithOPad: number[];
|
226 | protected remainder: number[];
|
227 | protected remainderLen: number;
|
228 | protected updateCalled: boolean;
|
229 | protected processedLen: number;
|
230 | protected macKeySet: boolean;
|
231 |
|
232 |
|
233 | protected abstract readonly variantBlockSize: number;
|
234 | protected abstract readonly bigEndianMod: -1 | 1;
|
235 | protected abstract readonly outputBinLen: number;
|
236 | protected abstract readonly isVariableLen: boolean;
|
237 | protected abstract readonly HMACSupported: boolean;
|
238 |
|
239 |
|
240 |
|
241 | protected abstract readonly converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
|
242 | protected abstract readonly roundFunc: (block: number[], H: StateT) => StateT;
|
243 | protected abstract readonly finalizeFunc: (
|
244 | remainder: number[],
|
245 | remainderBinLen: number,
|
246 | processedBinLen: number,
|
247 | H: StateT,
|
248 | outputLen: number
|
249 | ) => number[];
|
250 | protected abstract readonly stateCloneFunc: (state: StateT) => StateT;
|
251 | protected abstract readonly newStateFunc: (variant: VariantT) => StateT;
|
252 | protected abstract readonly getMAC: ((options: { outputLen: number }) => number[]) | null;
|
253 |
|
254 | protected constructor(variant: VariantT, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
|
255 | protected constructor(variant: VariantT, inputFormat: FormatNoTextType, options?: FixedLengthOptionsNoEncodingType);
|
256 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
257 | protected constructor(variant: any, inputFormat: any, options?: any) {
|
258 | const inputOptions = options || {};
|
259 | this.inputFormat = inputFormat;
|
260 |
|
261 | this.utfType = inputOptions["encoding"] || "UTF8";
|
262 | this.numRounds = inputOptions["numRounds"] || 1;
|
263 |
|
264 | /* eslint-disable-next-line @typescript-eslint/ban-ts-ignore */
|
265 | // @ts-ignore - The spec actually says ToString is called on the first parseInt argument so it's OK to use it here
|
266 | // to check if an arugment is an integer. This cheat would break if it's used to get the value of the argument.
|
267 | if (isNaN(this.numRounds) || this.numRounds !== parseInt(this.numRounds, 10) || 1 > this.numRounds) {
|
268 | throw new Error("numRounds must a integer >= 1");
|
269 | }
|
270 |
|
271 | this.shaVariant = variant;
|
272 | this.remainder = [];
|
273 | this.remainderLen = 0;
|
274 | this.updateCalled = false;
|
275 | this.processedLen = 0;
|
276 | this.macKeySet = false;
|
277 | this.keyWithIPad = [];
|
278 | this.keyWithOPad = [];
|
279 | }
|
280 |
|
281 | /**
|
282 | * Hashes as many blocks as possible. Stores the rest for either a future update or getHash call.
|
283 | *
|
284 | * @param srcString The input to be hashed.
|
285 | */
|
286 | update(srcString: string | ArrayBuffer | Uint8Array): void {
|
287 | let i,
|
288 | updateProcessedLen = 0;
|
289 | const variantBlockIntInc = this.variantBlockSize >>> 5,
|
290 | convertRet = this.converterFunc(srcString, this.remainder, this.remainderLen),
|
291 | chunkBinLen = convertRet["binLen"],
|
292 | chunk = convertRet["value"],
|
293 | chunkIntLen = chunkBinLen >>> 5;
|
294 |
|
295 | for (i = 0; i < chunkIntLen; i += variantBlockIntInc) {
|
296 | if (updateProcessedLen + this.variantBlockSize <= chunkBinLen) {
|
297 | this.intermediateState = this.roundFunc(chunk.slice(i, i + variantBlockIntInc), this.intermediateState);
|
298 | updateProcessedLen += this.variantBlockSize;
|
299 | }
|
300 | }
|
301 | this.processedLen += updateProcessedLen;
|
302 | this.remainder = chunk.slice(updateProcessedLen >>> 5);
|
303 | this.remainderLen = chunkBinLen % this.variantBlockSize;
|
304 | this.updateCalled = true;
|
305 | }
|
306 |
|
307 | /**
|
308 | * Returns the desired SHA hash of the input fed in via `update` calls.
|
309 | *
|
310 | * @param format The desired output formatting
|
311 | * @param options Hashmap of output formatting options. `outputLen` must be specified for variable length hashes.
|
312 | * `outputLen` replaces the now deprecated `shakeLen` key.
|
313 | * @returns The hash in the format specified.
|
314 | */
|
315 | getHash(format: "HEX", options?: { outputUpper?: boolean; outputLen?: number; shakeLen?: number }): string;
|
316 | getHash(format: "B64", options?: { b64Pad?: string; outputLen?: number; shakeLen?: number }): string;
|
317 | getHash(format: "BYTES", options?: { outputLen?: number; shakeLen?: number }): string;
|
318 | getHash(format: "UINT8ARRAY", options?: { outputLen?: number; shakeLen?: number }): Uint8Array;
|
319 | getHash(format: "ARRAYBUFFER", options?: { outputLen?: number; shakeLen?: number }): ArrayBuffer;
|
320 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
321 | getHash(format: any, options?: any): any {
|
322 | let i,
|
323 | finalizedState,
|
324 | outputBinLen = this.outputBinLen;
|
325 |
|
326 | const outputOptions = getOutputOpts(options);
|
327 |
|
328 | if (this.isVariableLen) {
|
329 | if (outputOptions["outputLen"] === -1) {
|
330 | throw new Error("Output length must be specified in options");
|
331 | }
|
332 | outputBinLen = outputOptions["outputLen"];
|
333 | }
|
334 |
|
335 | const formatFunc = getOutputConverter(format, outputBinLen, this.bigEndianMod, outputOptions);
|
336 | if (this.macKeySet && this.getMAC) {
|
337 | return formatFunc(this.getMAC(outputOptions));
|
338 | }
|
339 |
|
340 | finalizedState = this.finalizeFunc(
|
341 | this.remainder.slice(),
|
342 | this.remainderLen,
|
343 | this.processedLen,
|
344 | this.stateCloneFunc(this.intermediateState),
|
345 | outputBinLen
|
346 | );
|
347 | for (i = 1; i < this.numRounds; i += 1) {
|
348 | /* Need to mask out bits that should be zero due to output not being a multiple of 32 */
|
349 | if (this.isVariableLen && outputBinLen % 32 !== 0) {
|
350 | finalizedState[finalizedState.length - 1] &= 0x00ffffff >>> (24 - (outputBinLen % 32));
|
351 | }
|
352 | finalizedState = this.finalizeFunc(
|
353 | finalizedState,
|
354 | outputBinLen,
|
355 | 0,
|
356 | this.newStateFunc(this.shaVariant),
|
357 | outputBinLen
|
358 | );
|
359 | }
|
360 |
|
361 | return formatFunc(finalizedState);
|
362 | }
|
363 |
|
364 | /**
|
365 | * Sets the HMAC key for an eventual `getHMAC` call. Must be called immediately after jsSHA object instantiation.
|
366 | *
|
367 | * @param key The key used to calculate the HMAC
|
368 | * @param inputFormat The format of key.
|
369 | * @param options Hashmap of extra input options.
|
370 | */
|
371 | setHMACKey(key: string, inputFormat: "TEXT", options?: { encoding?: EncodingType }): void;
|
372 | setHMACKey(key: string, inputFormat: "B64" | "HEX" | "BYTES"): void;
|
373 | setHMACKey(key: ArrayBuffer, inputFormat: "ARRAYBUFFER"): void;
|
374 | setHMACKey(key: Uint8Array, inputFormat: "UINT8ARRAY"): void;
|
375 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
376 | setHMACKey(key: any, inputFormat: any, options?: any): void {
|
377 | if (!this.HMACSupported) {
|
378 | throw new Error("Variant does not support HMAC");
|
379 | }
|
380 |
|
381 | if (this.updateCalled) {
|
382 | throw new Error("Cannot set MAC key after calling update");
|
383 | }
|
384 |
|
385 | const keyOptions = options || {},
|
386 | keyConverterFunc = getStrConverter(inputFormat, keyOptions["encoding"] || "UTF8", this.bigEndianMod);
|
387 |
|
388 | this._setHMACKey(keyConverterFunc(key));
|
389 | }
|
390 |
|
391 | /**
|
392 | * Internal function that sets the MAC key.
|
393 | *
|
394 | * @param key The packed MAC key to use
|
395 | */
|
396 | protected _setHMACKey(key: packedValue): void {
|
397 | const blockByteSize = this.variantBlockSize >>> 3,
|
398 | lastArrayIndex = blockByteSize / 4 - 1;
|
399 | let i;
|
400 | if (this.numRounds !== 1) {
|
401 | throw new Error(mac_rounds_error);
|
402 | }
|
403 |
|
404 | if (this.macKeySet) {
|
405 | throw new Error("MAC key already set");
|
406 | }
|
407 |
|
408 | /* Figure out what to do with the key based on its size relative to
|
409 | * the hash's block size */
|
410 | if (blockByteSize < key["binLen"] / 8) {
|
411 | key["value"] = this.finalizeFunc(
|
412 | key["value"],
|
413 | key["binLen"],
|
414 | 0,
|
415 | this.newStateFunc(this.shaVariant),
|
416 | this.outputBinLen
|
417 | );
|
418 | }
|
419 | while (key["value"].length <= lastArrayIndex) {
|
420 | key["value"].push(0);
|
421 | }
|
422 | /* Create ipad and opad */
|
423 | for (i = 0; i <= lastArrayIndex; i += 1) {
|
424 | this.keyWithIPad[i] = key["value"][i] ^ 0x36363636;
|
425 | this.keyWithOPad[i] = key["value"][i] ^ 0x5c5c5c5c;
|
426 | }
|
427 |
|
428 | this.intermediateState = this.roundFunc(this.keyWithIPad, this.intermediateState);
|
429 | this.processedLen = this.variantBlockSize;
|
430 |
|
431 | this.macKeySet = true;
|
432 | }
|
433 |
|
434 | /**
|
435 | * Returns the the HMAC in the specified format using the key given by a previous `setHMACKey` call.
|
436 | *
|
437 | * @param format The desired output formatting.
|
438 | * @param options Hashmap of extra outputs options.
|
439 | * @returns The HMAC in the format specified.
|
440 | */
|
441 | getHMAC(format: "HEX", options?: { outputUpper?: boolean }): string;
|
442 | getHMAC(format: "B64", options?: { b64Pad?: string }): string;
|
443 | getHMAC(format: "BYTES"): string;
|
444 | getHMAC(format: "UINT8ARRAY"): Uint8Array;
|
445 | getHMAC(format: "ARRAYBUFFER"): ArrayBuffer;
|
446 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
447 | getHMAC(format: any, options?: any): any {
|
448 | const outputOptions = getOutputOpts(options),
|
449 | formatFunc = getOutputConverter(format, this.outputBinLen, this.bigEndianMod, outputOptions);
|
450 |
|
451 | return formatFunc(this._getHMAC());
|
452 | }
|
453 |
|
454 | /**
|
455 | * Internal function that returns the "raw" HMAC
|
456 | */
|
457 | protected _getHMAC(): number[] {
|
458 | let finalizedState;
|
459 |
|
460 | if (!this.macKeySet) {
|
461 | throw new Error("Cannot call getHMAC without first setting MAC key");
|
462 | }
|
463 |
|
464 | const firstHash = this.finalizeFunc(
|
465 | this.remainder.slice(),
|
466 | this.remainderLen,
|
467 | this.processedLen,
|
468 | this.stateCloneFunc(this.intermediateState),
|
469 | this.outputBinLen
|
470 | );
|
471 | finalizedState = this.roundFunc(this.keyWithOPad, this.newStateFunc(this.shaVariant));
|
472 | finalizedState = this.finalizeFunc(
|
473 | firstHash,
|
474 | this.outputBinLen,
|
475 | this.variantBlockSize,
|
476 | finalizedState,
|
477 | this.outputBinLen
|
478 | );
|
479 |
|
480 | return finalizedState;
|
481 | }
|
482 | }
|
483 |
|
\ | No newline at end of file |