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