mirror of
https://github.com/bitwarden/browser
synced 2026-02-25 00:53:22 +00:00
Merge branch 'main' into km/new-encrypt-service-interface
This commit is contained in:
@@ -21,7 +21,7 @@ describe("WebAuthnLoginPrfKeyService", () => {
|
||||
|
||||
const result = await service.createSymmetricKeyFromPrf(randomBytes(32));
|
||||
|
||||
expect(result.key.length).toBe(64);
|
||||
expect(result.toEncoded().length).toBe(64);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ export enum FeatureFlag {
|
||||
|
||||
/* Auth */
|
||||
PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
|
||||
PM9115_TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence",
|
||||
|
||||
/* Autofill */
|
||||
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
|
||||
@@ -54,7 +55,6 @@ export enum FeatureFlag {
|
||||
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
|
||||
NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss",
|
||||
NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss",
|
||||
VaultBulkManagementAction = "vault-bulk-management-action",
|
||||
SecurityTasks = "security-tasks",
|
||||
CipherKeyEncryption = "cipher-key-encryption",
|
||||
PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms",
|
||||
@@ -108,7 +108,6 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
|
||||
[FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE,
|
||||
[FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE,
|
||||
[FeatureFlag.VaultBulkManagementAction]: FALSE,
|
||||
[FeatureFlag.SecurityTasks]: FALSE,
|
||||
[FeatureFlag.CipherKeyEncryption]: FALSE,
|
||||
[FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE,
|
||||
@@ -116,6 +115,7 @@ export const DefaultFeatureFlagValue = {
|
||||
|
||||
/* Auth */
|
||||
[FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
|
||||
[FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE,
|
||||
|
||||
/* Billing */
|
||||
[FeatureFlag.TrialPaymentOptional]: FALSE,
|
||||
|
||||
@@ -103,7 +103,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
throw new Error("No wrappingKey provided for wrapping.");
|
||||
}
|
||||
|
||||
return await this.encryptUint8Array(keyToBeWrapped.key, wrappingKey);
|
||||
return await this.encryptUint8Array(keyToBeWrapped.toEncoded(), wrappingKey);
|
||||
}
|
||||
|
||||
async unwrapDecapsulationKey(
|
||||
@@ -166,7 +166,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
}
|
||||
|
||||
if (this.blockType0) {
|
||||
if (key.inner().type === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) {
|
||||
if (key.inner().type === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Type 0 encryption is not supported.");
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
}
|
||||
|
||||
if (this.blockType0) {
|
||||
if (key.inner().type === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) {
|
||||
if (key.inner().type === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Type 0 encryption is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,10 +58,9 @@ describe("EncryptService", () => {
|
||||
it("fails if type 0 key is provided with flag turned on", async () => {
|
||||
(encryptService as any).blockType0 = true;
|
||||
const mock32Key = mock<SymmetricCryptoKey>();
|
||||
mock32Key.key = makeStaticByteArray(32);
|
||||
mock32Key.inner.mockReturnValue({
|
||||
type: 0,
|
||||
encryptionKey: mock32Key.key,
|
||||
encryptionKey: makeStaticByteArray(32),
|
||||
});
|
||||
|
||||
await expect(encryptService.wrapSymmetricKey(mock32Key, mock32Key)).rejects.toThrow(
|
||||
@@ -99,10 +98,9 @@ describe("EncryptService", () => {
|
||||
it("throws if type 0 key is provided with flag turned on", async () => {
|
||||
(encryptService as any).blockType0 = true;
|
||||
const mock32Key = mock<SymmetricCryptoKey>();
|
||||
mock32Key.key = makeStaticByteArray(32);
|
||||
mock32Key.inner.mockReturnValue({
|
||||
type: 0,
|
||||
encryptionKey: mock32Key.key,
|
||||
encryptionKey: makeStaticByteArray(32),
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -140,10 +138,9 @@ describe("EncryptService", () => {
|
||||
it("throws if type 0 key is provided with flag turned on", async () => {
|
||||
(encryptService as any).blockType0 = true;
|
||||
const mock32Key = mock<SymmetricCryptoKey>();
|
||||
mock32Key.key = makeStaticByteArray(32);
|
||||
mock32Key.inner.mockReturnValue({
|
||||
type: 0,
|
||||
encryptionKey: mock32Key.key,
|
||||
encryptionKey: makeStaticByteArray(32),
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -187,10 +184,9 @@ describe("EncryptService", () => {
|
||||
(encryptService as any).blockType0 = true;
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||
const mock32Key = mock<SymmetricCryptoKey>();
|
||||
mock32Key.key = makeStaticByteArray(32);
|
||||
mock32Key.inner.mockReturnValue({
|
||||
type: 0,
|
||||
encryptionKey: mock32Key.key,
|
||||
encryptionKey: makeStaticByteArray(32),
|
||||
});
|
||||
|
||||
await expect(encryptService.encrypt(null!, key)).rejects.toThrow(
|
||||
@@ -273,10 +269,9 @@ describe("EncryptService", () => {
|
||||
(encryptService as any).blockType0 = true;
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||
const mock32Key = mock<SymmetricCryptoKey>();
|
||||
mock32Key.key = makeStaticByteArray(32);
|
||||
mock32Key.inner.mockReturnValue({
|
||||
type: 0,
|
||||
encryptionKey: mock32Key.key,
|
||||
encryptionKey: makeStaticByteArray(32),
|
||||
});
|
||||
|
||||
await expect(encryptService.encryptToBytes(plainValue, key)).rejects.toThrow(
|
||||
@@ -666,7 +661,7 @@ describe("EncryptService", () => {
|
||||
const actual = await encryptService.encapsulateKeyUnsigned(testKey, publicKey);
|
||||
|
||||
expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith(
|
||||
expect.toEqualBuffer(testKey.key),
|
||||
expect.toEqualBuffer(testKey.toEncoded()),
|
||||
expect.toEqualBuffer(publicKey),
|
||||
"sha1",
|
||||
);
|
||||
@@ -717,7 +712,7 @@ describe("EncryptService", () => {
|
||||
"sha1",
|
||||
);
|
||||
|
||||
expect(actual.key).toEqualBuffer(data);
|
||||
expect(actual.toEncoded()).toEqualBuffer(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -221,8 +221,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
|
||||
}
|
||||
|
||||
const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey);
|
||||
const newEncryptedUserKey = await this.encryptService.rsaEncrypt(
|
||||
newUserKey.key,
|
||||
const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(
|
||||
newUserKey,
|
||||
publicKey,
|
||||
);
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ describe("deviceTrustService", () => {
|
||||
|
||||
// RsaEncrypt must be called w/ a user key array buffer of 64 bytes
|
||||
const userKey = cryptoSvcRsaEncryptSpy.mock.calls[0][0];
|
||||
expect(userKey.key.byteLength).toBe(64);
|
||||
expect(userKey.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64);
|
||||
|
||||
expect(encryptServiceWrapDecapsulationKeySpy).toHaveBeenCalledTimes(1);
|
||||
expect(encryptServiceWrapEncapsulationKeySpy).toHaveBeenCalledTimes(1);
|
||||
@@ -706,7 +706,9 @@ describe("deviceTrustService", () => {
|
||||
);
|
||||
encryptService.decryptToBytes.mockResolvedValue(null);
|
||||
encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data"));
|
||||
encryptService.rsaEncrypt.mockResolvedValue(new EncString("test_encrypted_data"));
|
||||
encryptService.encapsulateKeyUnsigned.mockResolvedValue(
|
||||
new EncString("test_encrypted_data"),
|
||||
);
|
||||
|
||||
const protectedDeviceResponse = new ProtectedDeviceResponse({
|
||||
id: "id",
|
||||
@@ -861,8 +863,8 @@ describe("deviceTrustService", () => {
|
||||
|
||||
// Mock the decryption of the public key with the old user key
|
||||
encryptService.decryptToBytes.mockImplementationOnce((_encValue, privateKeyValue) => {
|
||||
expect(privateKeyValue.key.byteLength).toBe(64);
|
||||
expect(new Uint8Array(privateKeyValue.key)[0]).toBe(FakeOldUserKeyMarker);
|
||||
expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64);
|
||||
expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker);
|
||||
const data = new Uint8Array(250);
|
||||
data.fill(FakeDecryptedPublicKeyMarker, 0, 1);
|
||||
return Promise.resolve(data);
|
||||
@@ -870,8 +872,8 @@ describe("deviceTrustService", () => {
|
||||
|
||||
// Mock the encryption of the new user key with the decrypted public key
|
||||
encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => {
|
||||
expect(data.key.byteLength).toBe(64); // New key should also be 64 bytes
|
||||
expect(new Uint8Array(data.key)[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1';
|
||||
expect(data.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); // New key should also be 64 bytes
|
||||
expect(new Uint8Array(data.toEncoded())[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1';
|
||||
|
||||
expect(new Uint8Array(publicKey)[0]).toBe(FakeDecryptedPublicKeyMarker);
|
||||
return Promise.resolve(new EncString("4.ZW5jcnlwdGVkdXNlcg=="));
|
||||
@@ -882,7 +884,7 @@ describe("deviceTrustService", () => {
|
||||
expect(plainValue).toBeInstanceOf(Uint8Array);
|
||||
expect(new Uint8Array(plainValue as Uint8Array)[0]).toBe(FakeDecryptedPublicKeyMarker);
|
||||
|
||||
expect(new Uint8Array(key.key)[0]).toBe(FakeNewUserKeyMarker);
|
||||
expect(new Uint8Array(key.toEncoded())[0]).toBe(FakeNewUserKeyMarker);
|
||||
return Promise.resolve(
|
||||
new EncString("2.ZW5jcnlwdGVkcHVibGlj|ZW5jcnlwdGVkcHVibGlj|ZW5jcnlwdGVkcHVibGlj"),
|
||||
);
|
||||
|
||||
@@ -19,7 +19,6 @@ describe("SymmetricCryptoKey", () => {
|
||||
const cryptoKey = new SymmetricCryptoKey(key);
|
||||
|
||||
expect(cryptoKey).toEqual({
|
||||
key: key,
|
||||
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
innerKey: {
|
||||
type: EncryptionType.AesCbc256_B64,
|
||||
@@ -33,7 +32,6 @@ describe("SymmetricCryptoKey", () => {
|
||||
const cryptoKey = new SymmetricCryptoKey(key);
|
||||
|
||||
expect(cryptoKey).toEqual({
|
||||
key: key,
|
||||
keyB64:
|
||||
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==",
|
||||
innerKey: {
|
||||
|
||||
@@ -24,7 +24,6 @@ export type Aes256CbcKey = {
|
||||
export class SymmetricCryptoKey {
|
||||
private innerKey: Aes256CbcHmacKey | Aes256CbcKey;
|
||||
|
||||
key: Uint8Array;
|
||||
keyB64: string;
|
||||
|
||||
/**
|
||||
@@ -40,7 +39,6 @@ export class SymmetricCryptoKey {
|
||||
type: EncryptionType.AesCbc256_B64,
|
||||
encryptionKey: key,
|
||||
};
|
||||
this.key = key;
|
||||
this.keyB64 = this.toBase64();
|
||||
} else if (key.byteLength === 64) {
|
||||
this.innerKey = {
|
||||
@@ -48,7 +46,6 @@ export class SymmetricCryptoKey {
|
||||
encryptionKey: key.slice(0, 32),
|
||||
authenticationKey: key.slice(32),
|
||||
};
|
||||
this.key = key;
|
||||
this.keyB64 = this.toBase64();
|
||||
} else {
|
||||
throw new Error(`Unsupported encType/key length ${key.byteLength}`);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management";
|
||||
|
||||
import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service";
|
||||
import { CsprngArray } from "../../types/csprng";
|
||||
import { EncryptionType } from "../enums";
|
||||
|
||||
import { KeyGenerationService } from "./key-generation.service";
|
||||
|
||||
@@ -52,7 +53,7 @@ describe("KeyGenerationService", () => {
|
||||
|
||||
expect(salt).toEqual(inputSalt);
|
||||
expect(material).toEqual(inputMaterial);
|
||||
expect(derivedKey.key.length).toEqual(64);
|
||||
expect(derivedKey.inner().type).toEqual(EncryptionType.AesCbc256_HmacSha256_B64);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -67,7 +68,7 @@ describe("KeyGenerationService", () => {
|
||||
|
||||
const key = await sut.deriveKeyFromMaterial(material, salt, purpose);
|
||||
|
||||
expect(key.key.length).toEqual(64);
|
||||
expect(key.inner().type).toEqual(EncryptionType.AesCbc256_HmacSha256_B64);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +82,7 @@ describe("KeyGenerationService", () => {
|
||||
|
||||
const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig);
|
||||
|
||||
expect(key.key.length).toEqual(32);
|
||||
expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64);
|
||||
});
|
||||
|
||||
it("should derive a 32 byte key from a password using argon2id", async () => {
|
||||
@@ -94,7 +95,7 @@ describe("KeyGenerationService", () => {
|
||||
|
||||
const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig);
|
||||
|
||||
expect(key.key.length).toEqual(32);
|
||||
expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { MasterKey, PinKey } from "@bitwarden/common/types/key";
|
||||
import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management";
|
||||
|
||||
import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service";
|
||||
@@ -78,10 +79,21 @@ export class KeyGenerationService implements KeyGenerationServiceAbstraction {
|
||||
return new SymmetricCryptoKey(key);
|
||||
}
|
||||
|
||||
async stretchKey(key: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {
|
||||
async stretchKey(key: MasterKey | PinKey): Promise<SymmetricCryptoKey> {
|
||||
const newKey = new Uint8Array(64);
|
||||
const encKey = await this.cryptoFunctionService.hkdfExpand(key.key, "enc", 32, "sha256");
|
||||
const macKey = await this.cryptoFunctionService.hkdfExpand(key.key, "mac", 32, "sha256");
|
||||
// Master key and pin key are always 32 bytes
|
||||
const encKey = await this.cryptoFunctionService.hkdfExpand(
|
||||
key.inner().encryptionKey,
|
||||
"enc",
|
||||
32,
|
||||
"sha256",
|
||||
);
|
||||
const macKey = await this.cryptoFunctionService.hkdfExpand(
|
||||
key.inner().encryptionKey,
|
||||
"mac",
|
||||
32,
|
||||
"sha256",
|
||||
);
|
||||
|
||||
newKey.set(new Uint8Array(encKey));
|
||||
newKey.set(new Uint8Array(macKey), 32);
|
||||
|
||||
@@ -124,12 +124,8 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
* decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete.
|
||||
*/
|
||||
cipherViews$ = perUserCache$((userId: UserId): Observable<CipherView[] | null> => {
|
||||
return combineLatest([
|
||||
this.encryptedCiphersState(userId).state$,
|
||||
this.localData$(userId),
|
||||
this.keyService.cipherDecryptionKeys$(userId, true),
|
||||
]).pipe(
|
||||
filter(([ciphers, keys]) => ciphers != null && keys != null), // Skip if ciphers haven't been loaded yor synced yet
|
||||
return combineLatest([this.encryptedCiphersState(userId).state$, this.localData$(userId)]).pipe(
|
||||
filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet
|
||||
switchMap(() => this.getAllDecrypted(userId)),
|
||||
);
|
||||
}, this.clearCipherViewsForUser$);
|
||||
|
||||
Reference in New Issue
Block a user