const { subtle } = crypto; export const randomUUID = () => crypto.randomUUID(); export const getRandomValues = crypto.getRandomValues.bind(crypto); interface IAesGcmParams extends AesGcmParams { name: 'AES-GCM'; } interface IAesCbcParams extends AesCbcParams { name: 'AES-CBC'; } interface IAesCtrParams extends AesCtrParams { name: 'AES-CTR'; } interface IRsaOaepParams extends RsaOaepParams { name: 'RSA-OAEP'; } type ParamsMap = { 'AES-GCM': IAesGcmParams; 'AES-CTR': IAesCtrParams; 'AES-CBC': IAesCbcParams; 'RSA-OAEP': IRsaOaepParams; }; type ParamsOf = TKey['algorithm'] extends { name: infer TName extends keyof ParamsMap } ? ParamsMap[TName] : never; type HasUsage = TUsage extends TKey['usages'][number] ? TKey : TKey & `The provided key cannot be used for ${TUsage}`; export const encryptBuffer = >( key: HasUsage, params: TParams, data: BufferSource, ): Promise => subtle.encrypt(params, key, data); export const decryptBuffer = ( key: HasUsage, params: ParamsOf, data: BufferSource, ): Promise => subtle.decrypt(params, key, data); export const deriveBits = (...args: Parameters) => subtle.deriveBits(...args); type AesParams = { name: 'AES-CBC' | 'AES-GCM' | 'AES-CTR'; length: 128 | 256; }; type ModeOf = T extends `AES-${infer Mode}` ? Mode : never; type Permutations = T extends [infer First] ? [First] : T extends [infer First, infer Second] ? [First, Second] | [Second, First] : never; export interface IKey< TAlgorithm extends CryptoKey['algorithm'] = CryptoKey['algorithm'], TExtractable extends CryptoKey['extractable'] = CryptoKey['extractable'], TType extends CryptoKey['type'] = CryptoKey['type'], TUsages extends CryptoKey['usages'] = CryptoKey['usages'], > extends CryptoKey { readonly algorithm: TAlgorithm; readonly extractable: TExtractable; readonly type: TType; readonly usages: Permutations; } export async function generateKey< const T extends AesParams, const TExtractable extends boolean = boolean, const TUsages extends KeyUsage[] = KeyUsage[], >(algorithm: T, extractable: TExtractable, keyUsages: TUsages): Promise> { const key = await subtle.generateKey(algorithm, extractable, keyUsages); return key as IKey; } type Filter = TArray extends [ infer First extends KeyUsage, ...infer Rest extends KeyUsage[], ] ? First extends TItem ? Filter : Filter : Out; export interface IKeyPair< T extends KeyAlgorithm = KeyAlgorithm, TExtractable extends boolean = boolean, TUsages extends KeyUsage[] = KeyUsage[], > extends CryptoKeyPair { publicKey: IKey>; privateKey: IKey>; } type RsaParams = { name: 'RSA-OAEP'; modulusLength: 2048; publicExponent: Uint8Array; hash: { name: 'SHA-256' }; }; export async function generateKeyPair< const T extends RsaParams, const TExtractable extends boolean = boolean, const TUsages extends KeyUsage[] = KeyUsage[], >(algorithm: T, extractable: TExtractable, keyUsages: TUsages): Promise> { const keyPair = await subtle.generateKey(algorithm, extractable, keyUsages); return keyPair as IKeyPair; } type KeyToJwk = T['extractable'] extends false ? never : T['algorithm'] extends AesParams ? { kty: 'oct'; alg: `A${T['algorithm']['length']}${ModeOf}`; ext: true; k: string; key_ops: T['usages']; } : [T['algorithm'], T['type']] extends [RsaParams, 'public'] ? { kty: 'RSA'; alg: 'RSA-OAEP-256'; e: string; ext: true; key_ops: T['usages']; n: string; } : [T['algorithm'], T['type']] extends [RsaParams, 'private'] ? { kty: 'RSA'; alg: 'RSA-OAEP-256'; e: string; ext: true; d: string; dp: string; dq: string; key_ops: T['usages']; n: string; p: string; q: string; qi: string; } : never; export type Exported = TFormat extends 'jwk' ? KeyToJwk : ArrayBuffer; export async function exportKey( format: TFormat, key: TKey, ): Promise> { const exportedKey = await subtle.exportKey(format, key); return exportedKey as Exported; } type KtyParams = { oct: AesParams; RSA: RsaParams; }; export async function importJwk< const TKeyData extends JsonWebKey, const TAlgorithm extends (TKeyData extends { kty: infer Kty extends keyof KtyParams } ? KtyParams[Kty] : KeyAlgorithm), const TExtractable extends boolean = boolean, const TUsages extends KeyUsage[] = KeyUsage[], const TKeyType extends KeyType = TKeyData extends { kty: 'oct' } ? 'secret' : TKeyData extends { kty: 'RSA'; key_ops: [infer Op] } ? Op extends 'encrypt' ? 'public' : 'private' : KeyType, >( jwk: TKeyData, algorithm: TAlgorithm, extractable: TExtractable, keyUsages: TUsages, ): Promise> { const key = await subtle.importKey('jwk', jwk, algorithm, extractable, keyUsages); return key as IKey; } export async function importRaw< const TAlgorithm extends KeyAlgorithm = KeyAlgorithm, const TExtractable extends boolean = boolean, const TUsages extends KeyUsage[] = KeyUsage[], >( rawKey: BufferSource, algorithm: TAlgorithm, extractable: TExtractable, keyUsages: TUsages, ): Promise> { const key = await subtle.importKey('raw', rawKey, algorithm, extractable, keyUsages); return key as IKey; }