import { getIV } from "encryption/low-level/util"; import { b64Decode, b64Encode } from "encoding/base64"; export interface EncryptedKey { iv: string; data: string; } export interface AESKeyEncryptorI { decryptKey( encryptedKey: EncryptedKey, decryptedKeyAlgorithm: AesKeyAlgorithm | RsaHashedImportParams, extractable: boolean, format: "jwk" | "pkcs8", usages: KeyUsage[] ): Promise; encryptKey( dataEncryptionKey: CryptoKey, format: "jwk" | "pkcs8" ): Promise; } /** * Creates an instance of a AESKeyEncryptor. * This is used to encrypt and decrypt data encryption keys. * * Note: You probably don't want to instantiate this directly, but rather * through a KeyEncryptionConfig. * * @constructor * @param {object} algorithm The raw SubtleCrypto algorithm object. */ function AESKeyEncryptor( algorithm: AesKeyAlgorithm, keyEncryptionKey: CryptoKey ): AESKeyEncryptorI { const defaultExportFormat = "jwk"; // For AES-GCM we will use 12 bytes // https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes/41610 const IVLength = 12; const getAlgorithm = (iv: Uint8Array): AesGcmParams => Object.assign({}, algorithm, { iv: iv }); return { async decryptKey( encryptedKey: EncryptedKey, decryptedKeyAlgorithm: AesKeyAlgorithm | RsaHashedImportParams, extractable = false, format = defaultExportFormat, usages: KeyUsage[] ): Promise { try { return await crypto.subtle.unwrapKey( format, b64Decode(encryptedKey.data), keyEncryptionKey, getAlgorithm(b64Decode(encryptedKey.iv)), decryptedKeyAlgorithm, extractable, usages ); } catch (err) { console.log(err); console.log(err.message); throw new Error("Failed to decrypt key with supplied key"); } }, async encryptKey( dataEncryptionKey: CryptoKey, format = defaultExportFormat ): Promise { const iv = getIV(IVLength); const data = await crypto.subtle.wrapKey( format, dataEncryptionKey, keyEncryptionKey, getAlgorithm(iv) ); return { iv: b64Encode(iv), data: b64Encode(data) }; } }; } export interface KeyEncryptionConfigParams { name: "AES-GCM"; length: 256; } export interface KeyEncryptionConfigI { toJSON(): KeyEncryptionConfigParams; getAlgorithm(): AesKeyAlgorithm; buildKeyEncryptor(key: CryptoKey): AESKeyEncryptorI; } /** * Creates an instance of a KeyEncryptionConfig. * This is used represent the config required to build a KeyEncryptor to encrypt * or decrypt a key. * * If a JSON (decoded) config is provided, then the config will be loaded from it. * If no config is provided, then a config will be generated. This can be exported * with the toJSON method. * * @constructor * @param {object} configJSON A JSON (decoded) config that should be loaded */ export function KeyEncryptionConfig( configJSON?: KeyEncryptionConfigParams ): KeyEncryptionConfigI { const defaultAlgorithm = { name: "AES-GCM", length: 256 }; function importAlgorithm( algorithmJSON: KeyEncryptionConfigParams ): KeyEncryptionConfigParams { return Object.assign({}, algorithmJSON); } function exportAlgorithm( algorithmJSON: KeyEncryptionConfigParams ): KeyEncryptionConfigParams { return Object.assign({}, algorithmJSON); } const algorithm = configJSON !== undefined ? importAlgorithm(configJSON) : defaultAlgorithm; return { toJSON(): KeyEncryptionConfigParams { return exportAlgorithm(algorithm as KeyEncryptionConfigParams); }, getAlgorithm(): AesKeyAlgorithm { return algorithm; }, buildKeyEncryptor(encryptionKey: CryptoKey): AESKeyEncryptorI { return AESKeyEncryptor(algorithm, encryptionKey); } }; }