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

[PM-20567] Add new encrypt service functions (#14398)

* Add new encrypt service functions

* Undo changes

* Cleanup

* Fix build

* Fix comments
This commit is contained in:
Bernd Schoolmann
2025-04-29 17:04:47 +02:00
committed by GitHub
parent 9cd08e8a9f
commit 3694903a2a
3 changed files with 271 additions and 56 deletions

View File

@@ -8,17 +8,99 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr
export abstract class EncryptService {
/**
* @deprecated
* Encrypts a string or Uint8Array to an EncString
* @param plainValue - The value to encrypt
* @param key - The key to encrypt the value with
*/
abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString>;
/**
* @deprecated
* Encrypts a value to a Uint8Array
* @param plainValue - The value to encrypt
* @param key - The key to encrypt the value with
*/
abstract encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncArrayBuffer>;
/**
* @deprecated
* Decrypts an EncString to a string
* @param encString - The EncString to decrypt
* @param key - The key to decrypt the EncString with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted string
*/
abstract decryptToUtf8(
encString: EncString,
key: SymmetricCryptoKey,
decryptTrace?: string,
): Promise<string>;
/**
* @deprecated
* Decrypts an Encrypted object to a Uint8Array
* @param encThing - The Encrypted object to decrypt
* @param key - The key to decrypt the Encrypted object with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted Uint8Array
*/
abstract decryptToBytes(
encThing: Encrypted,
key: SymmetricCryptoKey,
decryptTrace?: string,
): Promise<Uint8Array | null>;
/**
* @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed
* @param items The items to decrypt
* @param key The key to decrypt the items with
*/
abstract decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]>;
/**
* Encrypts a string to an EncString
* @param plainValue - The value to encrypt
* @param key - The key to encrypt the value with
*/
abstract encryptString(plainValue: string, key: SymmetricCryptoKey): Promise<EncString>;
/**
* Encrypts bytes to an EncString
* @param plainValue - The value to encrypt
* @param key - The key to encrypt the value with
* @deprecated Bytes are not the right abstraction to encrypt in. Use e.g. key wrapping or file encryption instead
*/
abstract encryptBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncString>;
/**
* Encrypts a value to a Uint8Array
* @param plainValue - The value to encrypt
* @param key - The key to encrypt the value with
*/
abstract encryptFileData(
plainValue: Uint8Array,
key: SymmetricCryptoKey,
): Promise<EncArrayBuffer>;
/**
* Decrypts an EncString to a string
* @param encString - The EncString containing the encrypted string.
* @param key - The key to decrypt the value with
*/
abstract decryptString(encString: EncString, key: SymmetricCryptoKey): Promise<string>;
/**
* Decrypts an EncString to a Uint8Array
* @param encString - The EncString containing the encrypted bytes.
* @param key - The key to decrypt the value with
* @deprecated Bytes are not the right abstraction to encrypt in. Use e.g. key wrapping or file encryption instead
*/
abstract decryptBytes(encString: EncString, key: SymmetricCryptoKey): Promise<Uint8Array>;
/**
* Decrypts an EncArrayBuffer to a Uint8Array
* @param encBuffer - The EncArrayBuffer containing the encrypted file bytes.
* @param key - The key to decrypt the value with
*/
abstract decryptFileData(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<Uint8Array>;
/**
* Wraps a decapsulation key (Private key) with a symmetric key
@@ -52,31 +134,35 @@ export abstract class EncryptService {
): Promise<EncString>;
/**
* Decrypts an EncString to a string
* @param encString - The EncString to decrypt
* @param key - The key to decrypt the EncString with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted string
* Unwraps a decapsulation key (Private key) with a symmetric key
* @see {@link https://en.wikipedia.org/wiki/Key_wrap}
* @param decapsulationKeyPcks8 - The private key in PKCS8 format
* @param wrappingKey - The symmetric key to wrap the private key with
*/
abstract decryptToUtf8(
encString: EncString,
key: SymmetricCryptoKey,
decryptTrace?: string,
): Promise<string>;
abstract unwrapDecapsulationKey(
wrappedDecapsulationKey: EncString,
wrappingKey: SymmetricCryptoKey,
): Promise<Uint8Array>;
/**
* Decrypts an Encrypted object to a Uint8Array
* @param encThing - The Encrypted object to decrypt
* @param key - The key to decrypt the Encrypted object with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted Uint8Array
* Wraps an encapsulation key (Public key) with a symmetric key
* @see {@link https://en.wikipedia.org/wiki/Key_wrap}
* @param encapsulationKeySpki - The public key in SPKI format
* @param wrappingKey - The symmetric key to wrap the public key with
*/
abstract decryptToBytes(
encThing: Encrypted,
key: SymmetricCryptoKey,
decryptTrace?: string,
): Promise<Uint8Array | null>;
abstract unwrapEncapsulationKey(
wrappedEncapsulationKey: EncString,
wrappingKey: SymmetricCryptoKey,
): Promise<Uint8Array>;
/**
* Unwraps a symmetric key with another symmetric key
* @see {@link https://en.wikipedia.org/wiki/Key_wrap}
* @param keyToBeWrapped - The symmetric key to wrap
* @param wrappingKey - The symmetric key to wrap the encapsulated key with
*/
abstract unwrapSymmetricKey(
keyToBeUnwrapped: EncString,
wrappingKey: SymmetricCryptoKey,
): Promise<SymmetricCryptoKey>;
/**
* Encapsulates a symmetric key with an asymmetric public key
@@ -100,6 +186,7 @@ export abstract class EncryptService {
encryptedSharedKey: EncString,
decapsulationKey: Uint8Array,
): Promise<SymmetricCryptoKey>;
/**
* @deprecated Use @see {@link encapsulateKeyUnsigned} instead
* @param data - The data to encrypt
@@ -112,15 +199,7 @@ export abstract class EncryptService {
* @param privateKey - The privateKey to decrypt with
*/
abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise<Uint8Array>;
/**
* @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed
* @param items The items to decrypt
* @param key The key to decrypt the items with
*/
abstract decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]>;
/**
* Generates a base64-encoded hash of the given value
* @param value The value to hash

View File

@@ -36,31 +36,29 @@ export class EncryptServiceImplementation implements EncryptService {
protected logMacFailures: boolean,
) {}
// Handle updating private properties to turn on/off feature flags.
onServerConfigChange(newConfig: ServerConfig): void {
this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0);
// Proxy functions; Their implementation are temporary before moving at this level to the SDK
async encryptString(plainValue: string, key: SymmetricCryptoKey): Promise<EncString> {
return this.encrypt(plainValue, key);
}
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
if (key == null) {
throw new Error("No encryption key provided.");
}
async encryptBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
return this.encrypt(plainValue, key);
}
if (this.blockType0) {
if (key.inner().type === EncryptionType.AesCbc256_B64) {
throw new Error("Type 0 encryption is not supported.");
}
}
async encryptFileData(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
return this.encryptToBytes(plainValue, key);
}
if (plainValue == null) {
return Promise.resolve(null);
}
async decryptString(encString: EncString, key: SymmetricCryptoKey): Promise<string> {
return this.decryptToUtf8(encString, key);
}
if (typeof plainValue === "string") {
return this.encryptUint8Array(Utils.fromUtf8ToArray(plainValue), key);
} else {
return this.encryptUint8Array(plainValue, key);
}
async decryptBytes(encString: EncString, key: SymmetricCryptoKey): Promise<Uint8Array> {
return this.decryptToBytes(encString, key);
}
async decryptFileData(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<Uint8Array> {
return this.decryptToBytes(encBuffer, key);
}
async wrapDecapsulationKey(
@@ -108,6 +106,57 @@ export class EncryptServiceImplementation implements EncryptService {
return await this.encryptUint8Array(keyToBeWrapped.toEncoded(), wrappingKey);
}
async unwrapDecapsulationKey(
wrappedDecapsulationKey: EncString,
wrappingKey: SymmetricCryptoKey,
): Promise<Uint8Array> {
return this.decryptBytes(wrappedDecapsulationKey, wrappingKey);
}
async unwrapEncapsulationKey(
wrappedEncapsulationKey: EncString,
wrappingKey: SymmetricCryptoKey,
): Promise<Uint8Array> {
return this.decryptBytes(wrappedEncapsulationKey, wrappingKey);
}
async unwrapSymmetricKey(
keyToBeUnwrapped: EncString,
wrappingKey: SymmetricCryptoKey,
): Promise<SymmetricCryptoKey> {
return new SymmetricCryptoKey(await this.decryptBytes(keyToBeUnwrapped, wrappingKey));
}
async hash(value: string | Uint8Array, algorithm: "sha1" | "sha256" | "sha512"): Promise<string> {
const hashArray = await this.cryptoFunctionService.hash(value, algorithm);
return Utils.fromBufferToB64(hashArray);
}
// Handle updating private properties to turn on/off feature flags.
onServerConfigChange(newConfig: ServerConfig): void {
this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0);
}
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
if (key == null) {
throw new Error("No encryption key provided.");
}
if (this.blockType0) {
if (key.inner().type === EncryptionType.AesCbc256_B64) {
throw new Error("Type 0 encryption is not supported.");
}
}
if (plainValue == null) {
return Promise.resolve(null);
}
if (typeof plainValue === "string") {
return this.encryptUint8Array(Utils.fromUtf8ToArray(plainValue), key);
} else {
return this.encryptUint8Array(plainValue, key);
}
}
private async encryptUint8Array(
plainValue: Uint8Array,
key: SymmetricCryptoKey,
@@ -339,11 +388,6 @@ export class EncryptServiceImplementation implements EncryptService {
return results;
}
async hash(value: string | Uint8Array, algorithm: "sha1" | "sha256" | "sha512"): Promise<string> {
const hashArray = await this.cryptoFunctionService.hash(value, algorithm);
return Utils.fromBufferToB64(hashArray);
}
private async aesEncrypt(data: Uint8Array, key: Aes256CbcHmacKey): Promise<EncryptedObject> {
const obj = new EncryptedObject();
obj.iv = await this.cryptoFunctionService.randomBytes(16);

View File

@@ -538,6 +538,98 @@ describe("EncryptService", () => {
});
});
describe("encryptString", () => {
it("is a proxy to encrypt", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const plainValue = "data";
encryptService.encrypt = jest.fn();
await encryptService.encryptString(plainValue, key);
expect(encryptService.encrypt).toHaveBeenCalledWith(plainValue, key);
});
});
describe("encryptBytes", () => {
it("is a proxy to encrypt", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const plainValue = makeStaticByteArray(16, 1);
encryptService.encrypt = jest.fn();
await encryptService.encryptBytes(plainValue, key);
expect(encryptService.encrypt).toHaveBeenCalledWith(plainValue, key);
});
});
describe("encryptFileData", () => {
it("is a proxy to encryptToBytes", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const plainValue = makeStaticByteArray(16, 1);
encryptService.encryptToBytes = jest.fn();
await encryptService.encryptFileData(plainValue, key);
expect(encryptService.encryptToBytes).toHaveBeenCalledWith(plainValue, key);
});
});
describe("decryptString", () => {
it("is a proxy to decryptToUtf8", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const encString = new EncString(EncryptionType.AesCbc256_B64, "data");
encryptService.decryptToUtf8 = jest.fn();
await encryptService.decryptString(encString, key);
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, key);
});
});
describe("decryptBytes", () => {
it("is a proxy to decryptToBytes", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const encString = new EncString(EncryptionType.AesCbc256_B64, "data");
encryptService.decryptToBytes = jest.fn();
await encryptService.decryptBytes(encString, key);
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(encString, key);
});
});
describe("decryptFileData", () => {
it("is a proxy to decrypt", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const encString = new EncArrayBuffer(makeStaticByteArray(60, EncryptionType.AesCbc256_B64));
encryptService.decryptToBytes = jest.fn();
await encryptService.decryptFileData(encString, key);
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(encString, key);
});
});
describe("unwrapDecapsulationKey", () => {
it("is a proxy to decryptBytes", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const encString = new EncString(EncryptionType.AesCbc256_B64, "data");
encryptService.decryptBytes = jest.fn();
await encryptService.unwrapDecapsulationKey(encString, key);
expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key);
});
});
describe("unwrapEncapsulationKey", () => {
it("is a proxy to decryptBytes", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const encString = new EncString(EncryptionType.AesCbc256_B64, "data");
encryptService.decryptBytes = jest.fn();
await encryptService.unwrapEncapsulationKey(encString, key);
expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key);
});
});
describe("unwrapSymmetricKey", () => {
it("is a proxy to decryptBytes", async () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const encString = new EncString(EncryptionType.AesCbc256_B64, "data");
const jestFn = jest.fn();
jestFn.mockResolvedValue(new Uint8Array(64));
encryptService.decryptBytes = jestFn;
await encryptService.unwrapSymmetricKey(encString, key);
expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key);
});
});
describe("rsa", () => {
const data = makeStaticByteArray(64, 100);
const testKey = new SymmetricCryptoKey(data);