mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-08 19:18:02 +01:00
add better tests to crypto service
This commit is contained in:
parent
b41248cece
commit
7b7fa276be
@ -0,0 +1,91 @@
|
||||
import { mock, mockReset } from "jest-mock-extended";
|
||||
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
SymmetricCryptoKey,
|
||||
UserKey,
|
||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
|
||||
import { ElectronCryptoService } from "./electron-crypto.service";
|
||||
import { ElectronStateService } from "./electron-state.service.abstraction";
|
||||
|
||||
describe("electronCryptoService", () => {
|
||||
let electronCryptoService: ElectronCryptoService;
|
||||
|
||||
const cryptoFunctionService = mock<CryptoFunctionService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const platformUtilService = mock<PlatformUtilsService>();
|
||||
const logService = mock<LogService>();
|
||||
const stateService = mock<ElectronStateService>();
|
||||
|
||||
const mockUserId = "mock user id";
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(cryptoFunctionService);
|
||||
mockReset(encryptService);
|
||||
mockReset(platformUtilService);
|
||||
mockReset(logService);
|
||||
mockReset(stateService);
|
||||
|
||||
electronCryptoService = new ElectronCryptoService(
|
||||
cryptoFunctionService,
|
||||
encryptService,
|
||||
platformUtilService,
|
||||
logService,
|
||||
stateService
|
||||
);
|
||||
});
|
||||
|
||||
it("instantiates", () => {
|
||||
expect(electronCryptoService).not.toBeFalsy();
|
||||
});
|
||||
|
||||
describe("setUserKey", () => {
|
||||
let mockUserKey: UserKey;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockRandomBytes = new Uint8Array(64).buffer as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
});
|
||||
|
||||
describe("Biometric Key refresh", () => {
|
||||
it("sets an Biometric key if getBiometricUnlock is true and the platform supports secure storage", async () => {
|
||||
stateService.getBiometricUnlock.mockResolvedValue(true);
|
||||
platformUtilService.supportsSecureStorage.mockReturnValue(true);
|
||||
stateService.getBiometricRequirePasswordOnStart.mockResolvedValue(false);
|
||||
|
||||
await electronCryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setUserKeyBiometric).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ key: expect.any(String), clientEncKeyHalf: null }),
|
||||
{
|
||||
userId: mockUserId,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("clears the Biometric key if getBiometricUnlock is false or the platform does not support secure storage", async () => {
|
||||
stateService.getBiometricUnlock.mockResolvedValue(true);
|
||||
platformUtilService.supportsSecureStorage.mockReturnValue(false);
|
||||
|
||||
await electronCryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setUserKeyBiometric).toHaveBeenCalledWith(null, {
|
||||
userId: mockUserId,
|
||||
});
|
||||
});
|
||||
|
||||
it("clears the old deprecated Biometric key whenever a User Key is set", async () => {
|
||||
await electronCryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setCryptoMasterKeyBiometric).toHaveBeenCalledWith(null, {
|
||||
userId: mockUserId,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -6,6 +6,7 @@ import { EncryptService } from "../abstractions/encrypt.service";
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import {
|
||||
MasterKey,
|
||||
PinKey,
|
||||
@ -23,6 +24,8 @@ describe("cryptoService", () => {
|
||||
const logService = mock<LogService>();
|
||||
const stateService = mock<StateService>();
|
||||
|
||||
const mockUserId = "mock user id";
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(cryptoFunctionService);
|
||||
mockReset(encryptService);
|
||||
@ -43,7 +46,41 @@ describe("cryptoService", () => {
|
||||
expect(cryptoService).not.toBeFalsy();
|
||||
});
|
||||
|
||||
describe("getKeyForUserDecryption", () => {
|
||||
describe("getUserKey", () => {
|
||||
let mockUserKey: UserKey;
|
||||
let stateSvcGetUserKey: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockRandomBytes = new Uint8Array(64).buffer as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
|
||||
stateSvcGetUserKey = jest.spyOn(stateService, "getUserKey");
|
||||
});
|
||||
|
||||
it("returns the User Key if available", async () => {
|
||||
stateSvcGetUserKey.mockResolvedValue(mockUserKey);
|
||||
|
||||
const userKey = await cryptoService.getUserKey(mockUserId);
|
||||
|
||||
expect(stateSvcGetUserKey).toHaveBeenCalledWith({ userId: mockUserId });
|
||||
expect(userKey).toEqual(mockUserKey);
|
||||
});
|
||||
|
||||
it("sets the Auto key if the User Key if not set", async () => {
|
||||
const autoKeyB64 =
|
||||
"IT5cA1i5Hncd953pb00E58D2FqJX+fWTj4AvoI67qkGHSQPgulAqKv+LaKRAo9Bg0xzP9Nw00wk4TqjMmGSM+g==";
|
||||
stateService.getUserKeyAuto.mockResolvedValue(autoKeyB64);
|
||||
|
||||
const userKey = await cryptoService.getUserKey(mockUserId);
|
||||
|
||||
expect(stateService.setUserKey).toHaveBeenCalledWith(expect.any(SymmetricCryptoKey), {
|
||||
userId: mockUserId,
|
||||
});
|
||||
expect(userKey.keyB64).toEqual(autoKeyB64);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserKeyWithLegacySupport", () => {
|
||||
let mockUserKey: UserKey;
|
||||
let mockMasterKey: MasterKey;
|
||||
let stateSvcGetUserKey: jest.SpyInstance;
|
||||
@ -58,30 +95,29 @@ describe("cryptoService", () => {
|
||||
stateSvcGetMasterKey = jest.spyOn(stateService, "getMasterKey");
|
||||
});
|
||||
|
||||
it("returns the user key if available", async () => {
|
||||
it("returns the User Key if available", async () => {
|
||||
stateSvcGetUserKey.mockResolvedValue(mockUserKey);
|
||||
|
||||
const encryptionKey = await cryptoService.getUserKeyWithLegacySupport();
|
||||
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
|
||||
|
||||
expect(stateSvcGetUserKey).toHaveBeenCalled();
|
||||
expect(stateSvcGetUserKey).toHaveBeenCalledWith({ userId: mockUserId });
|
||||
expect(stateSvcGetMasterKey).not.toHaveBeenCalled();
|
||||
|
||||
expect(encryptionKey).toEqual(mockUserKey);
|
||||
expect(userKey).toEqual(mockUserKey);
|
||||
});
|
||||
|
||||
it("returns the user's master key when symmetric key is not available", async () => {
|
||||
it("returns the user's master key when User Key is not available", async () => {
|
||||
stateSvcGetUserKey.mockResolvedValue(null);
|
||||
stateSvcGetMasterKey.mockResolvedValue(mockMasterKey);
|
||||
|
||||
const encryptionKey = await cryptoService.getUserKeyWithLegacySupport();
|
||||
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
|
||||
|
||||
expect(stateSvcGetMasterKey).toHaveBeenCalled();
|
||||
expect(encryptionKey).toEqual(mockMasterKey);
|
||||
expect(stateSvcGetMasterKey).toHaveBeenCalledWith({ userId: mockUserId });
|
||||
expect(userKey).toEqual(mockMasterKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setUserKey", () => {
|
||||
const mockUserId = "example user id";
|
||||
let mockUserKey: UserKey;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -89,25 +125,87 @@ describe("cryptoService", () => {
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
});
|
||||
|
||||
it("saves an Auto key if needed", async () => {
|
||||
stateService.getVaultTimeout.mockResolvedValue(null);
|
||||
describe("Auto Key refresh", () => {
|
||||
it("sets an Auto key if vault timeout is set to null", async () => {
|
||||
stateService.getVaultTimeout.mockResolvedValue(null);
|
||||
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setUserKeyAuto).toHaveBeenCalled();
|
||||
expect(stateService.setUserKeyAuto).not.toHaveBeenCalledWith(null, { userId: mockUserId });
|
||||
expect(stateService.setUserKeyAuto).toHaveBeenCalledWith(mockUserKey.keyB64, {
|
||||
userId: mockUserId,
|
||||
});
|
||||
});
|
||||
|
||||
it("clears the Auto key if vault timeout is set to anything other than null", async () => {
|
||||
stateService.getVaultTimeout.mockResolvedValue(10);
|
||||
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setUserKeyAuto).toHaveBeenCalledWith(null, { userId: mockUserId });
|
||||
});
|
||||
|
||||
it("clears the old deprecated Auto key whenever a User Key is set", async () => {
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setCryptoMasterKeyAuto).toHaveBeenCalledWith(null, {
|
||||
userId: mockUserId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("saves a Pin key if needed", async () => {
|
||||
stateService.getUserKeyPinEphemeral.mockResolvedValue(null);
|
||||
const cryptoSvcMakePinKey = jest.spyOn(cryptoService, "makePinKey");
|
||||
cryptoSvcMakePinKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(new Uint8Array(64).buffer) as PinKey
|
||||
);
|
||||
describe("Pin Key refresh", () => {
|
||||
let cryptoSvcMakePinKey: jest.SpyInstance;
|
||||
const protectedPin =
|
||||
"2.jcow2vTUePO+CCyokcIfVw==|DTBNlJ5yVsV2Bsk3UU3H6Q==|YvFBff5gxWqM+UsFB6BKimKxhC32AtjF3IStpU1Ijwg=";
|
||||
let encPin: EncString;
|
||||
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
beforeEach(() => {
|
||||
cryptoSvcMakePinKey = jest.spyOn(cryptoService, "makePinKey");
|
||||
cryptoSvcMakePinKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(new Uint8Array(64).buffer) as PinKey
|
||||
);
|
||||
encPin = new EncString(
|
||||
"2.jcow2vTUePO+CCyokcIfVw==|DTBNlJ5yVsV2Bsk3UU3H6Q==|YvFBff5gxWqM+UsFB6BKimKxhC32AtjF3IStpU1Ijwg="
|
||||
);
|
||||
encryptService.encrypt.mockResolvedValue(encPin);
|
||||
});
|
||||
|
||||
expect(stateService.setUserKeyPin).toHaveBeenCalled();
|
||||
it("sets a UserKeyPin if a ProtectedPin and UserKeyPin is set", async () => {
|
||||
stateService.getProtectedPin.mockResolvedValue(protectedPin);
|
||||
stateService.getUserKeyPin.mockResolvedValue(
|
||||
new EncString(
|
||||
"2.OdGNE3L23GaDZGvu9h2Brw==|/OAcNnrYwu0rjiv8+RUr3Tc+Ef8fV035Tm1rbTxfEuC+2LZtiCAoIvHIZCrM/V1PWnb/pHO2gh9+Koks04YhX8K29ED4FzjeYP8+YQD/dWo=|+12xTcIK/UVRsOyawYudPMHb6+lCHeR2Peq1pQhPm0A="
|
||||
)
|
||||
);
|
||||
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setUserKeyPin).toHaveBeenCalledWith(expect.any(EncString), {
|
||||
userId: mockUserId,
|
||||
});
|
||||
});
|
||||
|
||||
it("sets a PinKeyEphemeral if a ProtectedPin is set, but a UserKeyPin is not set", async () => {
|
||||
stateService.getProtectedPin.mockResolvedValue(protectedPin);
|
||||
stateService.getUserKeyPin.mockResolvedValue(null);
|
||||
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setUserKeyPinEphemeral).toHaveBeenCalledWith(expect.any(EncString), {
|
||||
userId: mockUserId,
|
||||
});
|
||||
});
|
||||
|
||||
it("clears the UserKeyPin and UserKeyPinEphemeral if the ProtectedPin is not set", async () => {
|
||||
stateService.getProtectedPin.mockResolvedValue(null);
|
||||
|
||||
await cryptoService.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
expect(stateService.setUserKeyPin).toHaveBeenCalledWith(null, { userId: mockUserId });
|
||||
expect(stateService.setUserKeyPinEphemeral).toHaveBeenCalledWith(null, {
|
||||
userId: mockUserId,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -712,7 +712,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
|
||||
const storePin = await this.shouldStoreKey(KeySuffixOptions.Pin, userId);
|
||||
if (storePin) {
|
||||
await this.storePinKey(key);
|
||||
await this.storePinKey(key, userId);
|
||||
// We can't always clear deprecated keys because the pin is only
|
||||
// migrated once used to unlock
|
||||
await this.clearDeprecatedKeys(KeySuffixOptions.Pin, userId);
|
||||
@ -727,23 +727,23 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
* ephemeral version.
|
||||
* @param key The user key
|
||||
*/
|
||||
protected async storePinKey(key: UserKey) {
|
||||
protected async storePinKey(key: UserKey, userId?: string) {
|
||||
const pin = await this.encryptService.decryptToUtf8(
|
||||
new EncString(await this.stateService.getProtectedPin()),
|
||||
new EncString(await this.stateService.getProtectedPin({ userId: userId })),
|
||||
key
|
||||
);
|
||||
const pinKey = await this.makePinKey(
|
||||
pin,
|
||||
await this.stateService.getEmail(),
|
||||
await this.stateService.getKdfType(),
|
||||
await this.stateService.getKdfConfig()
|
||||
await this.stateService.getEmail({ userId: userId }),
|
||||
await this.stateService.getKdfType({ userId: userId }),
|
||||
await this.stateService.getKdfConfig({ userId: userId })
|
||||
);
|
||||
const encPin = await this.encryptService.encrypt(key.key, pinKey);
|
||||
|
||||
if ((await this.stateService.getUserKeyPin()) != null) {
|
||||
await this.stateService.setUserKeyPin(encPin);
|
||||
await this.stateService.setUserKeyPin(encPin, { userId: userId });
|
||||
} else {
|
||||
await this.stateService.setUserKeyPinEphemeral(encPin);
|
||||
await this.stateService.setUserKeyPinEphemeral(encPin, { userId: userId });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,7 +268,7 @@ describe("deviceCryptoService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
const methodsToTestForErrorsOrInvalidReturns = [
|
||||
const methodsToTestForErrorsOrInvalidReturns: any = [
|
||||
{
|
||||
method: "makeDeviceKey",
|
||||
spy: () => makeDeviceKeySpy,
|
||||
|
Loading…
Reference in New Issue
Block a user