1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

[PM-18697] Add new symmetric key runtime representation and move encrypt service to it (#13578)

* Remove AES128CBC-HMAC encryption

* Increase test coverage

* Refactor symmetric keys and increase test coverage

* Re-add type 0 encryption

* Fix ts strict warning

* Re-add support for encrypt hmac-less aes

* Add comment about inner()

* Update comment

* Deduplicate encryption type check

* Undo test changes

* Lift out encryption type check to before splitting by encryption type

* Change null to undefined

* Fix test
This commit is contained in:
Bernd Schoolmann
2025-04-08 12:42:42 +02:00
committed by GitHub
parent 81350a2ee1
commit cf0e693caa
5 changed files with 283 additions and 149 deletions

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;