diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index 7c5bc15a511..901e42d8f89 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -13,7 +13,11 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncryptedObject } from "@bitwarden/common/platform/models/domain/encrypted-object"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { + Aes256CbcHmacKey, + Aes256CbcKey, + SymmetricCryptoKey, +} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { ServerConfig } from "../../../platform/abstractions/config/server-config"; import { EncryptService } from "../abstractions/encrypt.service"; @@ -46,11 +50,19 @@ export class EncryptServiceImplementation implements EncryptService { plainBuf = plainValue; } - const encObj = await this.aesEncrypt(plainBuf, key); - const iv = Utils.fromBufferToB64(encObj.iv); - const data = Utils.fromBufferToB64(encObj.data); - const mac = encObj.mac != null ? Utils.fromBufferToB64(encObj.mac) : null; - return new EncString(encObj.key.encType, data, iv, mac); + const innerKey = key.inner(); + if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { + const encObj = await this.aesEncrypt(plainBuf, innerKey); + const iv = Utils.fromBufferToB64(encObj.iv); + const data = Utils.fromBufferToB64(encObj.data); + const mac = Utils.fromBufferToB64(encObj.mac); + return new EncString(innerKey.type, data, iv, mac); + } else if (innerKey.type === EncryptionType.AesCbc256_B64) { + const encObj = await this.aesEncryptLegacy(plainBuf, innerKey); + const iv = Utils.fromBufferToB64(encObj.iv); + const data = Utils.fromBufferToB64(encObj.data); + return new EncString(innerKey.type, data, iv); + } } async encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise { @@ -58,21 +70,26 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("No encryption key provided."); } - const encValue = await this.aesEncrypt(plainValue, key); - let macLen = 0; - if (encValue.mac != null) { - macLen = encValue.mac.byteLength; - } - - const encBytes = new Uint8Array(1 + encValue.iv.byteLength + macLen + encValue.data.byteLength); - encBytes.set([encValue.key.encType]); - encBytes.set(new Uint8Array(encValue.iv), 1); - if (encValue.mac != null) { + const innerKey = key.inner(); + if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { + const encValue = await this.aesEncrypt(plainValue, innerKey); + const macLen = encValue.mac.length; + const encBytes = new Uint8Array( + 1 + encValue.iv.byteLength + macLen + encValue.data.byteLength, + ); + encBytes.set([innerKey.type]); + encBytes.set(new Uint8Array(encValue.iv), 1); encBytes.set(new Uint8Array(encValue.mac), 1 + encValue.iv.byteLength); + encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen); + return new EncArrayBuffer(encBytes); + } else if (innerKey.type === EncryptionType.AesCbc256_B64) { + const encValue = await this.aesEncryptLegacy(plainValue, innerKey); + const encBytes = new Uint8Array(1 + encValue.iv.byteLength + encValue.data.byteLength); + encBytes.set([innerKey.type]); + encBytes.set(new Uint8Array(encValue.iv), 1); + encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength); + return new EncArrayBuffer(encBytes); } - - encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen); - return new EncArrayBuffer(encBytes); } async decryptToUtf8( @@ -84,36 +101,25 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("No key provided for decryption."); } - // DO NOT REMOVE OR MOVE. This prevents downgrade to mac-less CBC, which would compromise integrity and confidentiality. - if (key.macKey != null && encString?.mac == null) { - this.logService.error( - "[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " + - encryptionTypeName(key.encType) + - "Payload type " + - encryptionTypeName(encString.encryptionType), - "Decrypt context: " + decryptContext, + const innerKey = key.inner(); + if (encString.encryptionType !== innerKey.type) { + this.logDecryptError( + "Key encryption type does not match payload encryption type", + key.encType, + encString.encryptionType, + decryptContext, ); return null; } - if (key.encType !== encString.encryptionType) { - this.logService.error( - "[Encrypt service] Key encryption type does not match payload encryption type. Key type " + - encryptionTypeName(key.encType) + - "Payload type " + - encryptionTypeName(encString.encryptionType), - "Decrypt context: " + decryptContext, + if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { + const fastParams = this.cryptoFunctionService.aesDecryptFastParameters( + encString.data, + encString.iv, + encString.mac, + key, ); - return null; - } - const fastParams = this.cryptoFunctionService.aesDecryptFastParameters( - encString.data, - encString.iv, - encString.mac, - key, - ); - if (fastParams.macKey != null && fastParams.mac != null) { const computedMac = await this.cryptoFunctionService.hmacFast( fastParams.macData, fastParams.macKey, @@ -122,18 +128,31 @@ export class EncryptServiceImplementation implements EncryptService { const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); if (!macsEqual) { this.logMacFailed( - "[Encrypt service] decryptToUtf8 MAC comparison failed. Key or payload has changed. Key type " + - encryptionTypeName(key.encType) + - "Payload type " + - encryptionTypeName(encString.encryptionType) + - " Decrypt context: " + - decryptContext, + "decryptToUtf8 MAC comparison failed. Key or payload has changed.", + key.encType, + encString.encryptionType, + decryptContext, ); return null; } + return await this.cryptoFunctionService.aesDecryptFast({ + mode: "cbc", + parameters: fastParams, + }); + } else if (innerKey.type === EncryptionType.AesCbc256_B64) { + const fastParams = this.cryptoFunctionService.aesDecryptFastParameters( + encString.data, + encString.iv, + undefined, + key, + ); + return await this.cryptoFunctionService.aesDecryptFast({ + mode: "cbc", + parameters: fastParams, + }); + } else { + throw new Error(`Unsupported encryption type`); } - - return await this.cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters: fastParams }); } async decryptToBytes( @@ -149,72 +168,52 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("Nothing provided for decryption."); } - // DO NOT REMOVE OR MOVE. This prevents downgrade to mac-less CBC, which would compromise integrity and confidentiality. - if (key.macKey != null && encThing.macBytes == null) { - this.logService.error( - "[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " + - encryptionTypeName(key.encType) + - " Payload type " + - encryptionTypeName(encThing.encryptionType) + - " Decrypt context: " + - decryptContext, + const inner = key.inner(); + if (encThing.encryptionType !== inner.type) { + this.logDecryptError( + "Encryption key type mismatch", + key.encType, + encThing.encryptionType, + decryptContext, ); return null; } - if (key.encType !== encThing.encryptionType) { - this.logService.error( - "[Encrypt service] Key encryption type does not match payload encryption type. Key type " + - encryptionTypeName(key.encType) + - " Payload type " + - encryptionTypeName(encThing.encryptionType) + - " Decrypt context: " + - decryptContext, - ); - return null; - } + if (inner.type === EncryptionType.AesCbc256_HmacSha256_B64) { + if (encThing.macBytes == null) { + this.logDecryptError("Mac missing", key.encType, encThing.encryptionType, decryptContext); + return null; + } - if (key.macKey != null && encThing.macBytes != null) { const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength); macData.set(new Uint8Array(encThing.ivBytes), 0); macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength); const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256"); - if (computedMac === null) { - this.logMacFailed( - "[Encrypt service#decryptToBytes] Failed to compute MAC." + - " Key type " + - encryptionTypeName(key.encType) + - " Payload type " + - encryptionTypeName(encThing.encryptionType) + - " Decrypt context: " + - decryptContext, - ); - return null; - } - const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac); if (!macsMatch) { this.logMacFailed( - "[Encrypt service#decryptToBytes]: MAC comparison failed. Key or payload has changed." + - " Key type " + - encryptionTypeName(key.encType) + - " Payload type " + - encryptionTypeName(encThing.encryptionType) + - " Decrypt context: " + - decryptContext, + "MAC comparison failed. Key or payload has changed.", + key.encType, + encThing.encryptionType, + decryptContext, ); return null; } + + return await this.cryptoFunctionService.aesDecrypt( + encThing.dataBytes, + encThing.ivBytes, + key.encKey, + "cbc", + ); + } else if (inner.type === EncryptionType.AesCbc256_B64) { + return await this.cryptoFunctionService.aesDecrypt( + encThing.dataBytes, + encThing.ivBytes, + key.encKey, + "cbc", + ); } - - const result = await this.cryptoFunctionService.aesDecrypt( - encThing.dataBytes, - encThing.ivBytes, - key.encKey, - "cbc", - ); - - return result ?? null; } async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise { @@ -279,25 +278,48 @@ export class EncryptServiceImplementation implements EncryptService { return Utils.fromBufferToB64(hashArray); } - private async aesEncrypt(data: Uint8Array, key: SymmetricCryptoKey): Promise { + private async aesEncrypt(data: Uint8Array, key: Aes256CbcHmacKey): Promise { const obj = new EncryptedObject(); - obj.key = key; obj.iv = await this.cryptoFunctionService.randomBytes(16); - obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, obj.key.encKey); + obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, key.encryptionKey); - if (obj.key.macKey != null) { - const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength); - macData.set(new Uint8Array(obj.iv), 0); - macData.set(new Uint8Array(obj.data), obj.iv.byteLength); - obj.mac = await this.cryptoFunctionService.hmac(macData, obj.key.macKey, "sha256"); - } + const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength); + macData.set(new Uint8Array(obj.iv), 0); + macData.set(new Uint8Array(obj.data), obj.iv.byteLength); + obj.mac = await this.cryptoFunctionService.hmac(macData, key.authenticationKey, "sha256"); return obj; } - private logMacFailed(msg: string) { + /** + * @deprecated Removed once AesCbc256_B64 support is removed + */ + private async aesEncryptLegacy(data: Uint8Array, key: Aes256CbcKey): Promise { + const obj = new EncryptedObject(); + obj.iv = await this.cryptoFunctionService.randomBytes(16); + obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, key.encryptionKey); + return obj; + } + + private logDecryptError( + msg: string, + keyEncType: EncryptionType, + dataEncType: EncryptionType, + decryptContext: string, + ) { + this.logService.error( + `[Encrypt service] ${msg} Key type ${encryptionTypeName(keyEncType)} Payload type ${encryptionTypeName(dataEncType)} Decrypt context: ${decryptContext}`, + ); + } + + private logMacFailed( + msg: string, + keyEncType: EncryptionType, + dataEncType: EncryptionType, + decryptContext: string, + ) { if (this.logMacFailures) { - this.logService.error(msg); + this.logDecryptError(msg, keyEncType, dataEncType, decryptContext); } } } diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts index 3ce0d5883d2..7b173ab3eb2 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts @@ -150,7 +150,7 @@ describe("EncryptService", () => { ); }); - it("decrypts data with provided key for Aes256Cbc", async () => { + it("decrypts data with provided key for Aes256CbcHmac", async () => { const decryptedBytes = makeStaticByteArray(10, 200); cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1)); @@ -257,7 +257,7 @@ describe("EncryptService", () => { ); }); - it("decrypts data with provided key for Aes256Cbc_HmacSha256", async () => { + it("decrypts data with provided key for AesCbc256_HmacSha256", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({ @@ -277,10 +277,14 @@ describe("EncryptService", () => { ); }); - it("decrypts data with provided key for Aes256Cbc", async () => { + it("decrypts data with provided key for AesCbc256", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({} as any); + cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({ + macData: makeStaticByteArray(32, 0), + macKey: makeStaticByteArray(32, 0), + mac: makeStaticByteArray(32, 0), + } as any); cryptoFunctionService.hmacFast.mockResolvedValue(makeStaticByteArray(32, 0)); cryptoFunctionService.compareFast.mockResolvedValue(true); cryptoFunctionService.aesDecryptFast.mockResolvedValue("data"); @@ -290,7 +294,7 @@ describe("EncryptService", () => { expect(cryptoFunctionService.compareFast).not.toHaveBeenCalled(); }); - it("returns null if key is Aes256Cbc_HmacSha256 but EncString is Aes256Cbc", async () => { + it("returns null if key is AesCbc256_HMAC but encstring is AesCbc256", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); @@ -299,7 +303,7 @@ describe("EncryptService", () => { expect(logService.error).toHaveBeenCalled(); }); - it("returns null if key is Aes256Cbc but encstring is AesCbc256_HmacSha256", async () => { + it("returns null if key is AesCbc256 but encstring is AesCbc256_HMAC", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); @@ -332,10 +336,7 @@ describe("EncryptService", () => { ); }); it("returns null if key is mac key but encstring has no mac", async () => { - const key = new SymmetricCryptoKey( - makeStaticByteArray(64, 0), - EncryptionType.AesCbc256_HmacSha256_B64, - ); + const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); const actual = await encryptService.decryptToUtf8(encString, key); diff --git a/libs/common/src/platform/models/domain/encrypted-object.ts b/libs/common/src/platform/models/domain/encrypted-object.ts index 92153b27636..3caa7ae68d1 100644 --- a/libs/common/src/platform/models/domain/encrypted-object.ts +++ b/libs/common/src/platform/models/domain/encrypted-object.ts @@ -1,10 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; export class EncryptedObject { iv: Uint8Array; data: Uint8Array; mac: Uint8Array; - key: SymmetricCryptoKey; } diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts index 58c902ebab6..cce99b847bb 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts @@ -1,5 +1,6 @@ import { makeStaticByteArray } from "../../../../spec"; import { EncryptionType } from "../../enums"; +import { Utils } from "../../misc/utils"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; @@ -24,6 +25,11 @@ describe("SymmetricCryptoKey", () => { key: key, keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", macKey: null, + macKeyB64: undefined, + innerKey: { + type: EncryptionType.AesCbc256_B64, + encryptionKey: key, + }, }); }); @@ -40,6 +46,11 @@ describe("SymmetricCryptoKey", () => { "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==", macKey: key.slice(32, 64), macKeyB64: "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=", + innerKey: { + type: EncryptionType.AesCbc256_HmacSha256_B64, + encryptionKey: key.slice(0, 32), + authenticationKey: key.slice(32), + }, }); }); @@ -48,7 +59,7 @@ describe("SymmetricCryptoKey", () => { new SymmetricCryptoKey(makeStaticByteArray(30)); }; - expect(t).toThrowError("Unable to determine encType."); + expect(t).toThrowError(`Unsupported encType/key length 30`); }); }); @@ -69,6 +80,41 @@ describe("SymmetricCryptoKey", () => { expect(actual).toBeInstanceOf(SymmetricCryptoKey); }); + it("inner returns inner key", () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const actual = key.inner(); + + expect(actual).toEqual({ + type: EncryptionType.AesCbc256_HmacSha256_B64, + encryptionKey: key.encKey, + authenticationKey: key.macKey, + }); + }); + + it("toEncoded returns encoded key for AesCbc256_B64", () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(32)); + const actual = key.toEncoded(); + + expect(actual).toEqual(key.encKey); + }); + + it("toEncoded returns encoded key for AesCbc256_HmacSha256_B64", () => { + const keyBytes = makeStaticByteArray(64); + const key = new SymmetricCryptoKey(keyBytes); + const actual = key.toEncoded(); + + expect(actual).toEqual(keyBytes); + }); + + it("toBase64 returns base64 encoded key", () => { + const keyBytes = makeStaticByteArray(64); + const keyB64 = Utils.fromBufferToB64(keyBytes); + const key = new SymmetricCryptoKey(keyBytes); + const actual = key.toBase64(); + + expect(actual).toEqual(keyB64); + }); + describe("fromString", () => { it("null string returns null", () => { const actual = SymmetricCryptoKey.fromString(null); diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index 372b869fd9c..45e15c1f602 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -5,7 +5,25 @@ import { Jsonify } from "type-fest"; import { Utils } from "../../../platform/misc/utils"; import { EncryptionType } from "../../enums"; +export type Aes256CbcHmacKey = { + type: EncryptionType.AesCbc256_HmacSha256_B64; + encryptionKey: Uint8Array; + authenticationKey: Uint8Array; +}; + +export type Aes256CbcKey = { + type: EncryptionType.AesCbc256_B64; + encryptionKey: Uint8Array; +}; + +/** + * A symmetric crypto key represents a symmetric key usable for symmetric encryption and decryption operations. + * The specific algorithm used is private to the key, and should only be exposed to encrypt service implementations. + * This can be done via `inner()`. + */ export class SymmetricCryptoKey { + private innerKey: Aes256CbcHmacKey | Aes256CbcKey; + key: Uint8Array; encKey: Uint8Array; macKey?: Uint8Array; @@ -17,38 +35,45 @@ export class SymmetricCryptoKey { meta: any; - constructor(key: Uint8Array, encType?: EncryptionType) { + /** + * @param key The key in one of the permitted serialization formats + */ + constructor(key: Uint8Array) { if (key == null) { throw new Error("Must provide key"); } - if (encType == null) { - if (key.byteLength === 32) { - encType = EncryptionType.AesCbc256_B64; - } else if (key.byteLength === 64) { - encType = EncryptionType.AesCbc256_HmacSha256_B64; - } else { - throw new Error("Unable to determine encType."); - } - } + if (key.byteLength === 32) { + this.innerKey = { + type: EncryptionType.AesCbc256_B64, + encryptionKey: key, + }; + this.encType = EncryptionType.AesCbc256_B64; + this.key = key; + this.keyB64 = Utils.fromBufferToB64(this.key); - this.key = key; - this.encType = encType; - - if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) { this.encKey = key; - this.macKey = null; - } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) { - this.encKey = key.slice(0, 32); - this.macKey = key.slice(32, 64); - } else { - throw new Error("Unsupported encType/key length."); - } + this.encKeyB64 = Utils.fromBufferToB64(this.encKey); - this.keyB64 = Utils.fromBufferToB64(this.key); - this.encKeyB64 = Utils.fromBufferToB64(this.encKey); - if (this.macKey != null) { + this.macKey = null; + this.macKeyB64 = undefined; + } else if (key.byteLength === 64) { + this.innerKey = { + type: EncryptionType.AesCbc256_HmacSha256_B64, + encryptionKey: key.slice(0, 32), + authenticationKey: key.slice(32), + }; + this.encType = EncryptionType.AesCbc256_HmacSha256_B64; + this.key = key; + this.keyB64 = Utils.fromBufferToB64(this.key); + + this.encKey = key.slice(0, 32); + this.encKeyB64 = Utils.fromBufferToB64(this.encKey); + + this.macKey = key.slice(32); this.macKeyB64 = Utils.fromBufferToB64(this.macKey); + } else { + throw new Error(`Unsupported encType/key length ${key.byteLength}`); } } @@ -57,6 +82,48 @@ export class SymmetricCryptoKey { return { keyB64: this.keyB64 }; } + /** + * It is preferred not to work with the raw key where possible. + * Only use this method if absolutely necessary. + * + * @returns The inner key instance that can be directly used for encryption primitives + */ + inner(): Aes256CbcHmacKey | Aes256CbcKey { + return this.innerKey; + } + + /** + * @returns The serialized key in base64 format + */ + toBase64(): string { + return Utils.fromBufferToB64(this.toEncoded()); + } + + /** + * Serializes the key to a format that can be written to state or shared + * The currently permitted format is: + * - AesCbc256_B64: 32 bytes (the raw key) + * - AesCbc256_HmacSha256_B64: 64 bytes (32 bytes encryption key, 32 bytes authentication key, concatenated) + * + * @returns The serialized key that can be written to state or encrypted and then written to state / shared + */ + toEncoded(): Uint8Array { + if (this.innerKey.type === EncryptionType.AesCbc256_B64) { + return this.innerKey.encryptionKey; + } else if (this.innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { + const encodedKey = new Uint8Array(64); + encodedKey.set(this.innerKey.encryptionKey, 0); + encodedKey.set(this.innerKey.authenticationKey, 32); + return encodedKey; + } else { + throw new Error("Unsupported encryption type."); + } + } + + /** + * @param s The serialized key in base64 format + * @returns A SymmetricCryptoKey instance + */ static fromString(s: string): SymmetricCryptoKey { if (s == null) { return null;