mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-24 21:41:33 +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 { LogService } from "../abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
|
||||||
import { StateService } from "../abstractions/state.service";
|
import { StateService } from "../abstractions/state.service";
|
||||||
|
import { EncString } from "../models/domain/enc-string";
|
||||||
import {
|
import {
|
||||||
MasterKey,
|
MasterKey,
|
||||||
PinKey,
|
PinKey,
|
||||||
@ -23,6 +24,8 @@ describe("cryptoService", () => {
|
|||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
const stateService = mock<StateService>();
|
const stateService = mock<StateService>();
|
||||||
|
|
||||||
|
const mockUserId = "mock user id";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockReset(cryptoFunctionService);
|
mockReset(cryptoFunctionService);
|
||||||
mockReset(encryptService);
|
mockReset(encryptService);
|
||||||
@ -43,7 +46,41 @@ describe("cryptoService", () => {
|
|||||||
expect(cryptoService).not.toBeFalsy();
|
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 mockUserKey: UserKey;
|
||||||
let mockMasterKey: MasterKey;
|
let mockMasterKey: MasterKey;
|
||||||
let stateSvcGetUserKey: jest.SpyInstance;
|
let stateSvcGetUserKey: jest.SpyInstance;
|
||||||
@ -58,30 +95,29 @@ describe("cryptoService", () => {
|
|||||||
stateSvcGetMasterKey = jest.spyOn(stateService, "getMasterKey");
|
stateSvcGetMasterKey = jest.spyOn(stateService, "getMasterKey");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns the user key if available", async () => {
|
it("returns the User Key if available", async () => {
|
||||||
stateSvcGetUserKey.mockResolvedValue(mockUserKey);
|
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(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);
|
stateSvcGetUserKey.mockResolvedValue(null);
|
||||||
stateSvcGetMasterKey.mockResolvedValue(mockMasterKey);
|
stateSvcGetMasterKey.mockResolvedValue(mockMasterKey);
|
||||||
|
|
||||||
const encryptionKey = await cryptoService.getUserKeyWithLegacySupport();
|
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
|
||||||
|
|
||||||
expect(stateSvcGetMasterKey).toHaveBeenCalled();
|
expect(stateSvcGetMasterKey).toHaveBeenCalledWith({ userId: mockUserId });
|
||||||
expect(encryptionKey).toEqual(mockMasterKey);
|
expect(userKey).toEqual(mockMasterKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setUserKey", () => {
|
describe("setUserKey", () => {
|
||||||
const mockUserId = "example user id";
|
|
||||||
let mockUserKey: UserKey;
|
let mockUserKey: UserKey;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -89,25 +125,87 @@ describe("cryptoService", () => {
|
|||||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("saves an Auto key if needed", async () => {
|
describe("Auto Key refresh", () => {
|
||||||
stateService.getVaultTimeout.mockResolvedValue(null);
|
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).toHaveBeenCalledWith(mockUserKey.keyB64, {
|
||||||
expect(stateService.setUserKeyAuto).not.toHaveBeenCalledWith(null, { userId: mockUserId });
|
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 () => {
|
describe("Pin Key refresh", () => {
|
||||||
stateService.getUserKeyPinEphemeral.mockResolvedValue(null);
|
let cryptoSvcMakePinKey: jest.SpyInstance;
|
||||||
const cryptoSvcMakePinKey = jest.spyOn(cryptoService, "makePinKey");
|
const protectedPin =
|
||||||
cryptoSvcMakePinKey.mockResolvedValue(
|
"2.jcow2vTUePO+CCyokcIfVw==|DTBNlJ5yVsV2Bsk3UU3H6Q==|YvFBff5gxWqM+UsFB6BKimKxhC32AtjF3IStpU1Ijwg=";
|
||||||
new SymmetricCryptoKey(new Uint8Array(64).buffer) as PinKey
|
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);
|
const storePin = await this.shouldStoreKey(KeySuffixOptions.Pin, userId);
|
||||||
if (storePin) {
|
if (storePin) {
|
||||||
await this.storePinKey(key);
|
await this.storePinKey(key, userId);
|
||||||
// We can't always clear deprecated keys because the pin is only
|
// We can't always clear deprecated keys because the pin is only
|
||||||
// migrated once used to unlock
|
// migrated once used to unlock
|
||||||
await this.clearDeprecatedKeys(KeySuffixOptions.Pin, userId);
|
await this.clearDeprecatedKeys(KeySuffixOptions.Pin, userId);
|
||||||
@ -727,23 +727,23 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
* ephemeral version.
|
* ephemeral version.
|
||||||
* @param key The user key
|
* @param key The user key
|
||||||
*/
|
*/
|
||||||
protected async storePinKey(key: UserKey) {
|
protected async storePinKey(key: UserKey, userId?: string) {
|
||||||
const pin = await this.encryptService.decryptToUtf8(
|
const pin = await this.encryptService.decryptToUtf8(
|
||||||
new EncString(await this.stateService.getProtectedPin()),
|
new EncString(await this.stateService.getProtectedPin({ userId: userId })),
|
||||||
key
|
key
|
||||||
);
|
);
|
||||||
const pinKey = await this.makePinKey(
|
const pinKey = await this.makePinKey(
|
||||||
pin,
|
pin,
|
||||||
await this.stateService.getEmail(),
|
await this.stateService.getEmail({ userId: userId }),
|
||||||
await this.stateService.getKdfType(),
|
await this.stateService.getKdfType({ userId: userId }),
|
||||||
await this.stateService.getKdfConfig()
|
await this.stateService.getKdfConfig({ userId: userId })
|
||||||
);
|
);
|
||||||
const encPin = await this.encryptService.encrypt(key.key, pinKey);
|
const encPin = await this.encryptService.encrypt(key.key, pinKey);
|
||||||
|
|
||||||
if ((await this.stateService.getUserKeyPin()) != null) {
|
if ((await this.stateService.getUserKeyPin()) != null) {
|
||||||
await this.stateService.setUserKeyPin(encPin);
|
await this.stateService.setUserKeyPin(encPin, { userId: userId });
|
||||||
} else {
|
} 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",
|
method: "makeDeviceKey",
|
||||||
spy: () => makeDeviceKeySpy,
|
spy: () => makeDeviceKeySpy,
|
||||||
|
Loading…
Reference in New Issue
Block a user