mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 18:23:31 +00:00
[PM-22623] Remove most TS encryption code, remove service workers (#15153)
* Add new encrypt service functions * Undo changes * Cleanup * Fix build * Fix comments * Switch encrypt service to use SDK functions * Move remaining functions to PureCrypto * Tests * Increase test coverage * Enforce sdk.ready and drop unused codepaths * Delete unused code * Delete unused code * Delete more code * Add forgotten sdk init logic * Fix build * Fix cli * Fix tests * Fix build * Fix browser build * Remove compare and add more comments / warnings * Run prettier * Remove unused feature flags * Add hazmat warning to aesDecrypt * Fix build * Fix comment * Fix test
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
|
||||
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
export abstract class BulkEncryptService {
|
||||
abstract decryptItems<T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<T[]>;
|
||||
|
||||
abstract onServerConfigChange(newConfig: ServerConfig): void;
|
||||
}
|
||||
@@ -6,12 +6,20 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr
|
||||
import { CsprngArray } from "../../../types/csprng";
|
||||
|
||||
export abstract class CryptoFunctionService {
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract pbkdf2(
|
||||
password: string | Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
algorithm: "sha256" | "sha512",
|
||||
iterations: number,
|
||||
): Promise<Uint8Array>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract hkdf(
|
||||
ikm: Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
@@ -19,51 +27,76 @@ export abstract class CryptoFunctionService {
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512",
|
||||
): Promise<Uint8Array>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract hkdfExpand(
|
||||
prk: Uint8Array,
|
||||
info: string | Uint8Array,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512",
|
||||
): Promise<Uint8Array>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract hash(
|
||||
value: string | Uint8Array,
|
||||
algorithm: "sha1" | "sha256" | "sha512" | "md5",
|
||||
): Promise<Uint8Array>;
|
||||
abstract hmac(
|
||||
value: Uint8Array,
|
||||
key: Uint8Array,
|
||||
algorithm: "sha1" | "sha256" | "sha512",
|
||||
): Promise<Uint8Array>;
|
||||
abstract compare(a: Uint8Array, b: Uint8Array): Promise<boolean>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract hmacFast(
|
||||
value: Uint8Array | string,
|
||||
key: Uint8Array | string,
|
||||
algorithm: "sha1" | "sha256" | "sha512",
|
||||
): Promise<Uint8Array | string>;
|
||||
abstract compareFast(a: Uint8Array | string, b: Uint8Array | string): Promise<boolean>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract aesDecryptFastParameters(
|
||||
data: string,
|
||||
iv: string,
|
||||
mac: string,
|
||||
key: SymmetricCryptoKey,
|
||||
): CbcDecryptParameters<Uint8Array | string>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract aesDecryptFast({
|
||||
mode,
|
||||
parameters,
|
||||
}:
|
||||
| { mode: "cbc"; parameters: CbcDecryptParameters<Uint8Array | string> }
|
||||
| { mode: "ecb"; parameters: EcbDecryptParameters<Uint8Array | string> }): Promise<string>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Only used by DDG integration until DDG uses PKCS#7 padding, and by lastpass importer.
|
||||
*/
|
||||
abstract aesDecrypt(
|
||||
data: Uint8Array,
|
||||
iv: Uint8Array,
|
||||
key: Uint8Array,
|
||||
mode: "cbc" | "ecb",
|
||||
): Promise<Uint8Array>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract rsaEncrypt(
|
||||
data: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256",
|
||||
): Promise<Uint8Array>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
|
||||
*/
|
||||
abstract rsaDecrypt(
|
||||
data: Uint8Array,
|
||||
privateKey: Uint8Array,
|
||||
@@ -77,7 +110,6 @@ export abstract class CryptoFunctionService {
|
||||
abstract aesGenerateKey(bitLength: 128 | 192 | 256 | 512): Promise<CsprngArray>;
|
||||
/**
|
||||
* Generates a random array of bytes of the given length. Uses a cryptographically secure random number generator.
|
||||
*
|
||||
* Do not use this for generating encryption keys. Use aesGenerateKey or rsaGenerateKeyPair instead.
|
||||
*/
|
||||
abstract randomBytes(length: number): Promise<CsprngArray>;
|
||||
|
||||
@@ -1,51 +1,8 @@
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
|
||||
import { Encrypted } from "../../../platform/interfaces/encrypted";
|
||||
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
|
||||
import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { EncString } from "../models/enc-string";
|
||||
|
||||
export abstract class EncryptService {
|
||||
/**
|
||||
* @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
|
||||
@@ -188,12 +145,6 @@ export abstract class EncryptService {
|
||||
decapsulationKey: Uint8Array,
|
||||
): Promise<SymmetricCryptoKey>;
|
||||
|
||||
/**
|
||||
* @deprecated Use @see {@link encapsulateKeyUnsigned} instead
|
||||
* @param data - The data to encrypt
|
||||
* @param publicKey - The public key to encrypt with
|
||||
*/
|
||||
abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString>;
|
||||
/**
|
||||
* @deprecated Use @see {@link decapsulateKeyUnsigned} instead
|
||||
* @param data - The ciphertext to decrypt
|
||||
@@ -210,6 +161,4 @@ export abstract class EncryptService {
|
||||
value: string | Uint8Array,
|
||||
algorithm: "sha1" | "sha256" | "sha512",
|
||||
): Promise<string>;
|
||||
|
||||
abstract onServerConfigChange(newConfig: ServerConfig): void;
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { DefaultFeatureFlagValue, FeatureFlag } from "../../../enums/feature-flag.enum";
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
|
||||
/**
|
||||
* @deprecated Will be deleted in an immediate subsequent PR
|
||||
*/
|
||||
export class BulkEncryptServiceImplementation implements BulkEncryptService {
|
||||
protected useSDKForDecryption: boolean = DefaultFeatureFlagValue[FeatureFlag.UseSDKForDecryption];
|
||||
|
||||
constructor(
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected logService: LogService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Decrypts items using a web worker if the environment supports it.
|
||||
* Will fall back to the main thread if the window object is not available.
|
||||
*/
|
||||
async decryptItems<T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<T[]> {
|
||||
if (key == null) {
|
||||
throw new Error("No encryption key provided.");
|
||||
}
|
||||
|
||||
if (items == null || items.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
results.push(await items[i].decrypt(key));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
onServerConfigChange(newConfig: ServerConfig): void {}
|
||||
}
|
||||
@@ -5,15 +5,11 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-st
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
||||
import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted";
|
||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { PureCrypto } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
|
||||
export class EncryptServiceImplementation implements EncryptService {
|
||||
@@ -23,7 +19,6 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
protected logMacFailures: boolean,
|
||||
) {}
|
||||
|
||||
// Proxy functions; Their implementation are temporary before moving at this level to the SDK
|
||||
async encryptString(plainValue: string, key: SymmetricCryptoKey): Promise<EncString> {
|
||||
if (plainValue == null) {
|
||||
this.logService.warning(
|
||||
@@ -171,36 +166,6 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
return Utils.fromBufferToB64(hashArray);
|
||||
}
|
||||
|
||||
// Handle updating private properties to turn on/off feature flags.
|
||||
onServerConfigChange(newConfig: ServerConfig): void {}
|
||||
|
||||
async decryptToUtf8(
|
||||
encString: EncString,
|
||||
key: SymmetricCryptoKey,
|
||||
_decryptContext: string = "no context",
|
||||
): Promise<string> {
|
||||
await SdkLoadService.Ready;
|
||||
return PureCrypto.symmetric_decrypt(encString.encryptedString, key.toEncoded());
|
||||
}
|
||||
|
||||
async decryptToBytes(
|
||||
encThing: Encrypted,
|
||||
key: SymmetricCryptoKey,
|
||||
_decryptContext: string = "no context",
|
||||
): Promise<Uint8Array | null> {
|
||||
if (encThing.encryptionType == null || encThing.ivBytes == null || encThing.dataBytes == null) {
|
||||
throw new Error("Cannot decrypt, missing type, IV, or data bytes.");
|
||||
}
|
||||
const buffer = EncArrayBuffer.fromParts(
|
||||
encThing.encryptionType,
|
||||
encThing.ivBytes,
|
||||
encThing.dataBytes,
|
||||
encThing.macBytes,
|
||||
).buffer;
|
||||
await SdkLoadService.Ready;
|
||||
return PureCrypto.symmetric_decrypt_array_buffer(buffer, key.toEncoded());
|
||||
}
|
||||
|
||||
async encapsulateKeyUnsigned(
|
||||
sharedKey: SymmetricCryptoKey,
|
||||
encapsulationKey: Uint8Array,
|
||||
@@ -228,45 +193,14 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
throw new Error("No decapsulationKey provided for decapsulation");
|
||||
}
|
||||
|
||||
await SdkLoadService.Ready;
|
||||
const keyBytes = PureCrypto.decapsulate_key_unsigned(
|
||||
encryptedSharedKey.encryptedString,
|
||||
decapsulationKey,
|
||||
);
|
||||
await SdkLoadService.Ready;
|
||||
return new SymmetricCryptoKey(keyBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Replaced by BulkEncryptService (PM-4154)
|
||||
*/
|
||||
async decryptItems<T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<T[]> {
|
||||
if (items == null || items.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// don't use promise.all because this task is not io bound
|
||||
const results = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
results.push(await items[i].decrypt(key));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString> {
|
||||
if (data == null) {
|
||||
throw new Error("No data provided for encryption.");
|
||||
}
|
||||
|
||||
if (publicKey == null) {
|
||||
throw new Error("No public key provided for encryption.");
|
||||
}
|
||||
const encrypted = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, "sha1");
|
||||
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encrypted));
|
||||
}
|
||||
|
||||
async rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise<Uint8Array> {
|
||||
if (data == null) {
|
||||
throw new Error("[Encrypt service] rsaDecrypt: No data provided for decryption.");
|
||||
|
||||
@@ -303,12 +303,6 @@ describe("EncryptService", () => {
|
||||
const actual = await encryptService.encapsulateKeyUnsigned(testKey, publicKey);
|
||||
expect(actual).toEqual(new EncString("encapsulated_key_unsigned"));
|
||||
});
|
||||
|
||||
it("throws if no data was provided", () => {
|
||||
return expect(encryptService.rsaEncrypt(null, new Uint8Array(32))).rejects.toThrow(
|
||||
"No data provided for encryption",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("decapsulateKeyUnsigned", () => {
|
||||
@@ -338,23 +332,4 @@ describe("EncryptService", () => {
|
||||
expect(cryptoFunctionService.hash).toHaveBeenCalledWith("test", "sha256");
|
||||
});
|
||||
});
|
||||
|
||||
describe("decryptItems", () => {
|
||||
it("returns empty array if no items are provided", async () => {
|
||||
const key = mock<SymmetricCryptoKey>();
|
||||
const actual = await encryptService.decryptItems(null, key);
|
||||
expect(actual).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns items decrypted with provided key", async () => {
|
||||
const key = mock<SymmetricCryptoKey>();
|
||||
const decryptable = {
|
||||
decrypt: jest.fn().mockResolvedValue("decrypted"),
|
||||
};
|
||||
const items = [decryptable];
|
||||
const actual = await encryptService.decryptItems(items as any, key);
|
||||
expect(actual).toEqual(["decrypted"]);
|
||||
expect(decryptable.decrypt).toHaveBeenCalledWith(key);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { ConsoleLogService } from "../../../platform/services/console-log.service";
|
||||
import { ContainerService } from "../../../platform/services/container.service";
|
||||
import { getClassInitializer } from "../../../platform/services/cryptography/get-class-initializer";
|
||||
import {
|
||||
DECRYPT_COMMAND,
|
||||
SET_CONFIG_COMMAND,
|
||||
ParsedDecryptCommandData,
|
||||
} from "../types/worker-command.type";
|
||||
|
||||
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
|
||||
import { WebCryptoFunctionService } from "./web-crypto-function.service";
|
||||
|
||||
const workerApi: Worker = self as any;
|
||||
|
||||
let inited = false;
|
||||
let encryptService: EncryptServiceImplementation;
|
||||
let logService: LogService;
|
||||
|
||||
/**
|
||||
* Bootstrap the worker environment with services required for decryption
|
||||
*/
|
||||
export function init() {
|
||||
const cryptoFunctionService = new WebCryptoFunctionService(self);
|
||||
logService = new ConsoleLogService(false);
|
||||
encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true);
|
||||
|
||||
const bitwardenContainerService = new ContainerService(null, encryptService);
|
||||
bitwardenContainerService.attachToGlobal(self);
|
||||
|
||||
inited = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for messages and decrypt their contents
|
||||
*/
|
||||
workerApi.addEventListener("message", async (event: { data: string }) => {
|
||||
if (!inited) {
|
||||
init();
|
||||
}
|
||||
|
||||
const request: {
|
||||
command: string;
|
||||
} = JSON.parse(event.data);
|
||||
|
||||
switch (request.command) {
|
||||
case DECRYPT_COMMAND:
|
||||
return await handleDecrypt(request as unknown as ParsedDecryptCommandData);
|
||||
case SET_CONFIG_COMMAND: {
|
||||
const newConfig = (request as unknown as { newConfig: Jsonify<ServerConfig> }).newConfig;
|
||||
return await handleSetConfig(newConfig);
|
||||
}
|
||||
default:
|
||||
logService.error(`[EncryptWorker] unknown worker command`, request.command, request);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleDecrypt(request: ParsedDecryptCommandData) {
|
||||
const key = SymmetricCryptoKey.fromJSON(request.key);
|
||||
const items = request.items.map((jsonItem) => {
|
||||
const initializer = getClassInitializer<Decryptable<any>>(jsonItem.initializerKey);
|
||||
return initializer(jsonItem);
|
||||
});
|
||||
const result = await encryptService.decryptItems(items, key);
|
||||
|
||||
workerApi.postMessage({
|
||||
id: request.id,
|
||||
items: JSON.stringify(result),
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSetConfig(newConfig: Jsonify<ServerConfig>) {
|
||||
encryptService.onServerConfigChange(ServerConfig.fromJSON(newConfig));
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
|
||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
|
||||
/**
|
||||
* @deprecated Will be deleted in an immediate subsequent PR
|
||||
*/
|
||||
export class FallbackBulkEncryptService implements BulkEncryptService {
|
||||
private featureFlagEncryptService: BulkEncryptService;
|
||||
private currentServerConfig: ServerConfig | undefined = undefined;
|
||||
|
||||
constructor(protected encryptService: EncryptService) {}
|
||||
|
||||
/**
|
||||
* Decrypts items using a web worker if the environment supports it.
|
||||
* Will fall back to the main thread if the window object is not available.
|
||||
*/
|
||||
async decryptItems<T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<T[]> {
|
||||
return await this.encryptService.decryptItems(items, key);
|
||||
}
|
||||
|
||||
async setFeatureFlagEncryptService(featureFlagEncryptService: BulkEncryptService) {}
|
||||
|
||||
onServerConfigChange(newConfig: ServerConfig): void {}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
|
||||
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
|
||||
|
||||
/**
|
||||
* @deprecated Will be deleted in an immediate subsequent PR
|
||||
*/
|
||||
export class MultithreadEncryptServiceImplementation extends EncryptServiceImplementation {
|
||||
protected useSDKForDecryption: boolean = true;
|
||||
|
||||
/**
|
||||
* Sends items to a web worker to decrypt them.
|
||||
* This utilises multithreading to decrypt items faster without interrupting other operations (e.g. updating UI).
|
||||
*/
|
||||
async decryptItems<T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<T[]> {
|
||||
return await super.decryptItems(items, key);
|
||||
}
|
||||
|
||||
override onServerConfigChange(newConfig: ServerConfig): void {}
|
||||
}
|
||||
@@ -154,46 +154,6 @@ describe("WebCrypto Function Service", () => {
|
||||
testHmac("sha512", Sha512Mac);
|
||||
});
|
||||
|
||||
describe("compare", () => {
|
||||
it("should successfully compare two of the same values", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const equal = await cryptoFunctionService.compare(a, a);
|
||||
expect(equal).toBe(true);
|
||||
});
|
||||
|
||||
it("should successfully compare two different values of the same length", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
b[1] = 4;
|
||||
const equal = await cryptoFunctionService.compare(a, b);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
|
||||
it("should successfully compare two different values of different lengths", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
const equal = await cryptoFunctionService.compare(a, b);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hmacFast", () => {
|
||||
testHmacFast("sha1", Sha1Mac);
|
||||
testHmacFast("sha256", Sha256Mac);
|
||||
testHmacFast("sha512", Sha512Mac);
|
||||
});
|
||||
|
||||
describe("compareFast", () => {
|
||||
it("should successfully compare two of the same values", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
@@ -523,20 +483,6 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function testHmacFast(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
it("should create valid " + algorithm + " hmac", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey"));
|
||||
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!"));
|
||||
const computedMac = await cryptoFunctionService.hmacFast(
|
||||
dataByteString,
|
||||
keyByteString,
|
||||
algorithm,
|
||||
);
|
||||
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac))).toBe(mac);
|
||||
});
|
||||
}
|
||||
|
||||
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
|
||||
it(
|
||||
"should successfully generate a " + length + " bit key pair",
|
||||
|
||||
@@ -146,34 +146,6 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
async compare(a: Uint8Array, b: Uint8Array): Promise<boolean> {
|
||||
const macKey = await this.randomBytes(32);
|
||||
const signingAlgorithm = {
|
||||
name: "HMAC",
|
||||
hash: { name: "SHA-256" },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("raw", macKey, signingAlgorithm, false, ["sign"]);
|
||||
const mac1 = await this.subtle.sign(signingAlgorithm, impKey, a);
|
||||
const mac2 = await this.subtle.sign(signingAlgorithm, impKey, b);
|
||||
|
||||
if (mac1.byteLength !== mac2.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const arr1 = new Uint8Array(mac1);
|
||||
const arr2 = new Uint8Array(mac2);
|
||||
for (let i = 0; i < arr2.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hmacFast(value: string, key: string, algorithm: "sha1" | "sha256" | "sha512"): Promise<string> {
|
||||
const hmac = forge.hmac.create();
|
||||
hmac.start(algorithm, key);
|
||||
@@ -182,6 +154,9 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return Promise.resolve(bytes);
|
||||
}
|
||||
|
||||
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
async compareFast(a: string, b: string): Promise<boolean> {
|
||||
const rand = await this.randomBytes(32);
|
||||
const bytes = new Uint32Array(rand);
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { makeStaticByteArray } from "../../../../spec";
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import {
|
||||
DECRYPT_COMMAND,
|
||||
DecryptCommandData,
|
||||
SET_CONFIG_COMMAND,
|
||||
buildDecryptMessage,
|
||||
buildSetConfigMessage,
|
||||
} from "./worker-command.type";
|
||||
|
||||
describe("Worker command types", () => {
|
||||
describe("buildDecryptMessage", () => {
|
||||
it("builds a message with the correct command", () => {
|
||||
const commandData = createDecryptCommandData();
|
||||
|
||||
const result = buildDecryptMessage(commandData);
|
||||
|
||||
const parsedResult = JSON.parse(result);
|
||||
expect(parsedResult.command).toBe(DECRYPT_COMMAND);
|
||||
});
|
||||
|
||||
it("includes the provided data in the message", () => {
|
||||
const mockItems = [{ encrypted: "test-encrypted" } as unknown as Decryptable<any>];
|
||||
const commandData = createDecryptCommandData(mockItems);
|
||||
|
||||
const result = buildDecryptMessage(commandData);
|
||||
|
||||
const parsedResult = JSON.parse(result);
|
||||
expect(parsedResult.command).toBe(DECRYPT_COMMAND);
|
||||
expect(parsedResult.id).toBe("test-id");
|
||||
expect(parsedResult.items).toEqual(mockItems);
|
||||
expect(SymmetricCryptoKey.fromJSON(parsedResult.key)).toEqual(commandData.key);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildSetConfigMessage", () => {
|
||||
it("builds a message with the correct command", () => {
|
||||
const result = buildSetConfigMessage({ newConfig: mock<ServerConfig>() });
|
||||
|
||||
const parsedResult = JSON.parse(result);
|
||||
expect(parsedResult.command).toBe(SET_CONFIG_COMMAND);
|
||||
});
|
||||
|
||||
it("includes the provided data in the message", () => {
|
||||
const serverConfig = { version: "test-version" } as unknown as ServerConfig;
|
||||
|
||||
const result = buildSetConfigMessage({ newConfig: serverConfig });
|
||||
|
||||
const parsedResult = JSON.parse(result);
|
||||
expect(parsedResult.command).toBe(SET_CONFIG_COMMAND);
|
||||
expect(ServerConfig.fromJSON(parsedResult.newConfig).version).toEqual(serverConfig.version);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createDecryptCommandData(items?: Decryptable<any>[]): DecryptCommandData {
|
||||
return {
|
||||
id: "test-id",
|
||||
items: items ?? [],
|
||||
key: new SymmetricCryptoKey(makeStaticByteArray(64)),
|
||||
};
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
export const DECRYPT_COMMAND = "decrypt";
|
||||
export const SET_CONFIG_COMMAND = "updateConfig";
|
||||
|
||||
export type DecryptCommandData = {
|
||||
id: string;
|
||||
items: Decryptable<any>[];
|
||||
key: SymmetricCryptoKey;
|
||||
};
|
||||
|
||||
export type ParsedDecryptCommandData = {
|
||||
id: string;
|
||||
items: Jsonify<Decryptable<any>>[];
|
||||
key: Jsonify<SymmetricCryptoKey>;
|
||||
};
|
||||
|
||||
type SetConfigCommandData = { newConfig: ServerConfig };
|
||||
|
||||
export function buildDecryptMessage(data: DecryptCommandData): string {
|
||||
return JSON.stringify({
|
||||
command: DECRYPT_COMMAND,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSetConfigMessage(data: SetConfigCommandData): string {
|
||||
return JSON.stringify({
|
||||
command: SET_CONFIG_COMMAND,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user