1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-24 00:23:17 +00:00

Merge branch 'master' into auth/pm-3797/emergency-access-refactor

This commit is contained in:
Jacob Fink
2023-10-16 12:48:11 -04:00
422 changed files with 10697 additions and 1589 deletions

View File

@@ -4,6 +4,7 @@ export enum FeatureFlag {
TrustedDeviceEncryption = "trusted-device-encryption",
PasswordlessLogin = "passwordless-login",
AutofillV2 = "autofill-v2",
BrowserFilelessImport = "browser-fileless-import",
}
// Replace this with a type safe lookup of the feature flag values in PM-2282

View File

@@ -52,8 +52,16 @@ export abstract class CryptoFunctionService {
mac: string,
key: SymmetricCryptoKey
) => DecryptParameters<Uint8Array | string>;
aesDecryptFast: (parameters: DecryptParameters<Uint8Array | string>) => Promise<string>;
aesDecrypt: (data: Uint8Array, iv: Uint8Array, key: Uint8Array) => Promise<Uint8Array>;
aesDecryptFast: (
parameters: DecryptParameters<Uint8Array | string>,
mode: "cbc" | "ecb"
) => Promise<string>;
aesDecrypt: (
data: Uint8Array,
iv: Uint8Array,
key: Uint8Array,
mode: "cbc" | "ecb"
) => Promise<Uint8Array>;
rsaEncrypt: (
data: Uint8Array,
publicKey: Uint8Array,

View File

@@ -99,7 +99,7 @@ export class EncryptServiceImplementation implements EncryptService {
}
}
return await this.cryptoFunctionService.aesDecryptFast(fastParams);
return await this.cryptoFunctionService.aesDecryptFast(fastParams, "cbc");
}
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<Uint8Array> {
@@ -140,7 +140,8 @@ export class EncryptServiceImplementation implements EncryptService {
const result = await this.cryptoFunctionService.aesDecrypt(
encThing.dataBytes,
encThing.ivBytes,
key.encKey
key.encKey,
"cbc"
);
return result ?? null;

View File

@@ -115,7 +115,8 @@ describe("EncryptService", () => {
expect(cryptoFunctionService.aesDecrypt).toBeCalledWith(
expect.toEqualBuffer(encBuffer.dataBytes),
expect.toEqualBuffer(encBuffer.ivBytes),
expect.toEqualBuffer(key.encKey)
expect.toEqualBuffer(key.encKey),
"cbc"
);
expect(actual).toEqualBuffer(decryptedBytes);

View File

@@ -3,6 +3,7 @@ import { Substitute } from "@fluffy-spoon/substitute";
import { Utils } from "../../platform/misc/utils";
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
import { DecryptParameters } from "../models/domain/decrypt-parameters";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import { WebCryptoFunctionService } from "./web-crypto-function.service";
@@ -233,7 +234,7 @@ describe("WebCrypto Function Service", () => {
});
});
describe("aesEncrypt", () => {
describe("aesEncrypt CBC mode", () => {
it("should successfully encrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
@@ -254,7 +255,7 @@ describe("WebCrypto Function Service", () => {
const b64Iv = Utils.fromBufferToB64(iv);
const symKey = new SymmetricCryptoKey(key);
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc");
expect(decValue).toBe(value);
});
@@ -265,30 +266,53 @@ describe("WebCrypto Function Service", () => {
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = new Uint8Array(await cryptoFunctionService.aesEncrypt(data, iv, key));
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv, key);
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv, key, "cbc");
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
describe("aesDecryptFast", () => {
describe("aesDecryptFast CBC mode", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = Utils.fromBufferToB64(makeStaticByteArray(16));
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
const data = "ByUF8vhyX4ddU9gcooznwA==";
const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc");
expect(decValue).toBe("EncryptMe!");
});
});
describe("aesDecrypt", () => {
describe("aesDecryptFast ECB mode", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw==");
const params = new DecryptParameters<string>();
params.encKey = Utils.fromBufferToByteString(key);
params.data = Utils.fromBufferToByteString(data);
const decValue = await cryptoFunctionService.aesDecryptFast(params, "ecb");
expect(decValue).toBe("EncryptMe!");
});
});
describe("aesDecrypt CBC mode", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key);
const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key, "cbc");
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
describe("aesDecrypt ECB mode", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw==");
const decValue = await cryptoFunctionService.aesDecrypt(data, null, key, "ecb");
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});

View File

@@ -273,17 +273,37 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return p;
}
aesDecryptFast(parameters: DecryptParameters<string>): Promise<string> {
const dataBuffer = forge.util.createBuffer(parameters.data);
const decipher = forge.cipher.createDecipher("AES-CBC", parameters.encKey);
decipher.start({ iv: parameters.iv });
aesDecryptFast(parameters: DecryptParameters<string>, mode: "cbc" | "ecb"): Promise<string> {
const decipher = (forge as any).cipher.createDecipher(
this.toWebCryptoAesMode(mode),
parameters.encKey
);
const options = {} as any;
if (mode === "cbc") {
options.iv = parameters.iv;
}
const dataBuffer = (forge as any).util.createBuffer(parameters.data);
decipher.start(options);
decipher.update(dataBuffer);
decipher.finish();
const val = decipher.output.toString();
return Promise.resolve(val);
}
async aesDecrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
async aesDecrypt(
data: Uint8Array,
iv: Uint8Array,
key: Uint8Array,
mode: "cbc" | "ecb"
): Promise<Uint8Array> {
if (mode === "ecb") {
// Web crypto does not support AES-ECB mode, so we need to do this in forge.
const params = new DecryptParameters<string>();
params.data = this.toByteString(data);
params.encKey = this.toByteString(key);
const result = await this.aesDecryptFast(params, "ecb");
return Utils.fromByteStringToArray(result);
}
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
"decrypt",
]);
@@ -411,6 +431,10 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return algorithm === "sha1" ? "SHA-1" : algorithm === "sha256" ? "SHA-256" : "SHA-512";
}
private toWebCryptoAesMode(mode: "cbc" | "ecb"): string {
return mode === "cbc" ? "AES-CBC" : "AES-ECB";
}
// ref: https://stackoverflow.com/a/47880734/1090359
private checkIfWasmSupported(): boolean {
try {

View File

@@ -1,5 +0,0 @@
export abstract class PasswordRepromptService {
protectedFields: () => string[];
showPasswordPrompt: () => Promise<boolean>;
enabled: () => Promise<boolean>;
}

View File

@@ -1,3 +1,4 @@
import { Organization } from "../../../admin-console/models/domain/organization";
import { ITreeNodeObject } from "../../../models/domain/tree-node";
import { View } from "../../../models/view/view";
import { Collection } from "../domain/collection";
@@ -10,6 +11,7 @@ export class CollectionView implements View, ITreeNodeObject {
organizationId: string = null;
name: string = null;
externalId: string = null;
// readOnly applies to the items within a collection
readOnly: boolean = null;
hidePasswords: boolean = null;
@@ -26,4 +28,24 @@ export class CollectionView implements View, ITreeNodeObject {
this.hidePasswords = c.hidePasswords;
}
}
// For editing collection details, not the items within it.
canEdit(org: Organization): boolean {
if (org.id !== this.organizationId) {
throw new Error(
"Id of the organization provided does not match the org id of the collection."
);
}
return org?.canEditAnyCollection || org?.canEditAssignedCollections;
}
// For deleting a collection, not the items within it.
canDelete(org: Organization): boolean {
if (org.id !== this.organizationId) {
throw new Error(
"Id of the organization provided does not match the org id of the collection."
);
}
return org?.canDeleteAnyCollection || org?.canDeleteAssignedCollections;
}
}