keys.ts (3060B)
1 import { as_array_buffer } from "@radroots/utils"; 2 import { cl_crypto_error } from "./error.js"; 3 4 const KEY_ID_BYTES_LENGTH = 16; 5 const WRAP_IV_LENGTH = 12; 6 7 const bytes_to_hex = (bytes: Uint8Array): string => { 8 let out = ""; 9 for (let i = 0; i < bytes.length; i++) { 10 const part = bytes[i].toString(16).padStart(2, "0"); 11 out += part; 12 } 13 return out; 14 }; 15 16 export const crypto_key_id_create = (): string => { 17 if (!globalThis.crypto) throw new Error(cl_crypto_error.crypto_undefined); 18 const bytes = new Uint8Array(KEY_ID_BYTES_LENGTH); 19 crypto.getRandomValues(bytes); 20 return bytes_to_hex(bytes); 21 }; 22 23 export const crypto_key_generate = async (): Promise<CryptoKey> => { 24 if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_crypto_error.crypto_undefined); 25 return await crypto.subtle.generateKey( 26 { 27 name: "AES-GCM", 28 length: 256 29 }, 30 true, 31 ["encrypt", "decrypt"] 32 ); 33 }; 34 35 export const crypto_key_export_raw = async (key: CryptoKey): Promise<Uint8Array> => { 36 if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_crypto_error.crypto_undefined); 37 const raw = await crypto.subtle.exportKey("raw", key); 38 return new Uint8Array(raw); 39 }; 40 41 export const crypto_key_import_raw = async (raw: Uint8Array): Promise<CryptoKey> => { 42 if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_crypto_error.crypto_undefined); 43 return await crypto.subtle.importKey( 44 "raw", 45 as_array_buffer(raw), 46 "AES-GCM", 47 false, 48 ["encrypt", "decrypt"] 49 ); 50 }; 51 52 export const crypto_key_wrap = async ( 53 kek: CryptoKey, 54 raw_key: Uint8Array 55 ): Promise<{ wrapped_key: Uint8Array; wrap_iv: Uint8Array; }> => { 56 if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_crypto_error.crypto_undefined); 57 try { 58 const wrap_iv = new Uint8Array(WRAP_IV_LENGTH); 59 crypto.getRandomValues(wrap_iv); 60 const cipher_buf = await crypto.subtle.encrypt( 61 { 62 name: "AES-GCM", 63 iv: as_array_buffer(wrap_iv) 64 }, 65 kek, 66 as_array_buffer(raw_key) 67 ); 68 const wrapped_key = new Uint8Array(cipher_buf); 69 raw_key.fill(0); 70 return { wrapped_key, wrap_iv }; 71 } catch { 72 throw new Error(cl_crypto_error.wrap_failure); 73 } 74 }; 75 76 export const crypto_key_unwrap = async ( 77 kek: CryptoKey, 78 wrapped_key: Uint8Array, 79 wrap_iv: Uint8Array 80 ): Promise<CryptoKey> => { 81 if (!globalThis.crypto || !globalThis.crypto.subtle) throw new Error(cl_crypto_error.crypto_undefined); 82 try { 83 const raw = await crypto.subtle.decrypt( 84 { 85 name: "AES-GCM", 86 iv: as_array_buffer(wrap_iv) 87 }, 88 kek, 89 as_array_buffer(wrapped_key) 90 ); 91 return await crypto_key_import_raw(new Uint8Array(raw)); 92 } catch { 93 throw new Error(cl_crypto_error.unwrap_failure); 94 } 95 };