File
Blob: src/worker/services/crypto.ts
| 1 | export const timingSafeEqual = (left: Uint8Array, right: Uint8Array): boolean => { |
| 2 | const lengthsMatch = left.byteLength === right.byteLength; |
| 3 | // subtle.timingSafeEqual throws when inputs differ in length. |
| 4 | // Compare left against itself to keep execution time constant, then |
| 5 | // negate to produce the expected false for mismatched lengths. |
| 6 | return lengthsMatch ? crypto.subtle.timingSafeEqual(left, right) : !crypto.subtle.timingSafeEqual(left, left); |
| 7 | }; |
| 8 | |
| 9 | export const encodeHex = (value: Uint8Array): string => |
| 10 | Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join(""); |
| 11 | |
| 12 | export const encodeBase64 = (value: Uint8Array): string => { |
| 13 | let binary = ""; |
| 14 | for (const byte of value) { |
| 15 | binary += String.fromCharCode(byte); |
| 16 | } |
| 17 | |
| 18 | return btoa(binary); |
| 19 | }; |
| 20 | |
| 21 | export const decodeBase64 = (value: string): Uint8Array => { |
| 22 | let normalized = value.trim().replace(/-/gu, "+").replace(/_/gu, "/"); |
| 23 | const remainder = normalized.length % 4; |
| 24 | if (remainder > 0) { |
| 25 | normalized = normalized.padEnd(normalized.length + (4 - remainder), "="); |
| 26 | } |
| 27 | |
| 28 | const binary = atob(normalized); |
| 29 | const output = new Uint8Array(binary.length); |
| 30 | for (let index = 0; index < binary.length; index += 1) { |
| 31 | output[index] = binary.charCodeAt(index); |
| 32 | } |
| 33 | |
| 34 | return output; |
| 35 | }; |
| 36 | |
| 37 | export const encodeBase64Url = (value: Uint8Array): string => |
| 38 | encodeBase64(value).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, ""); |
| 39 | |
| 40 | export const decodeBase64Url = (value: string): Uint8Array => decodeBase64(value); |
| 41 | |
| 42 | export const toArrayBuffer = (value: Uint8Array): ArrayBuffer => |
| 43 | value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength) as ArrayBuffer; |