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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user