1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

[PM-24128] New Pin service, using PasswordProtectedKeyEnvelope (#15863)

* fix: broken SDK interface

* Fix all compile errors related to uuids

* Update usages of sdk to type-safe SDK type

* Update sdk version

* Update to "toSdk"

* Move pin service to km ownership

* Run format

* Eslint

* Fix tsconfig

* Fix imports and test

* Clean up imports

* Pin tmp

* Initial version of updated pin service

* Add tests

* Rename function

* Clean up logging

* Fix imports

* Fix cli build

* Fix browser desktop

* Fix tests

* Attempt to fix

* Fix build

* Fix tests

* Fix browser build

* Add missing empty line

* Fix linting

* Remove non-required change

* Missing newline

* Re-add comment

* Undo change to file

* Fix missing empty line

* Cleanup

* Cleanup

* Cleanup

* Cleanup

* Switch to replaysubject

* Add comments

* Fix tests

* Run prettier

* Undo change

* Fix browser

* Fix circular dependency on browser

* Add missing clear ephemeral pin

* Address feedback

* Update docs

* Simplify sdk usage in pin service

* Replace with mock sdk

* Update sdk

* Initialize pin service via unlock instead of listening to keyservice

* Cleanup

* Fix test

* Prevent race condition with userkey not being set

* Filter null userkeys

* [PM-24124] Pin State Service (#16641)

* add pin-state.service

* add remaining tests

* improve description for clearEphemeralPinState

* rename getUserKeyWrappedPin$ to userKeyWrappedPin$

* drop temp variable in setPinState

* add new test and remove copied one

* Fix dep cycle

* Fix tests and remaining build issues

* Fix cli build

* Add comments about functions not being public API

---------

Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com>
Co-authored-by: Hinton <hinton@users.noreply.github.com>
Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
Bernd Schoolmann
2025-10-17 16:30:29 +02:00
committed by GitHub
parent 7015663c38
commit a860f218bd
33 changed files with 1610 additions and 1029 deletions

View File

@@ -10,7 +10,6 @@ import {
EncryptedString,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
import { UnsignedPublicKey, WrappedSigningKey } from "@bitwarden/common/key-management/types";
import { VaultTimeoutStringType } from "@bitwarden/common/key-management/vault-timeout";
import { VAULT_TIMEOUT } from "@bitwarden/common/key-management/vault-timeout/services/vault-timeout-settings.state";
@@ -57,7 +56,6 @@ import { KdfConfig } from "./models/kdf-config";
describe("keyService", () => {
let keyService: DefaultKeyService;
const pinService = mock<PinServiceAbstraction>();
const keyGenerationService = mock<KeyGenerationService>();
const cryptoFunctionService = mock<CryptoFunctionService>();
const encryptService = mock<EncryptService>();
@@ -77,7 +75,6 @@ describe("keyService", () => {
stateProvider = new FakeStateProvider(accountService);
keyService = new DefaultKeyService(
pinService,
masterPasswordService,
keyGenerationService,
cryptoFunctionService,
@@ -256,54 +253,6 @@ describe("keyService", () => {
"No userId provided.",
);
});
describe("Pin Key refresh", () => {
const mockPinKeyEncryptedUserKey = new EncString(
"2.AAAw2vTUePO+CCyokcIfVw==|DTBNlJ5yVsV2Bsk3UU3H6Q==|YvFBff5gxWqM+UsFB6BKimKxhC32AtjF3IStpU1Ijwg=",
);
const mockUserKeyEncryptedPin = new EncString(
"2.BBBw2vTUePO+CCyokcIfVw==|DTBNlJ5yVsV2Bsk3UU3H6Q==|YvFBff5gxWqM+UsFB6BKimKxhC32AtjF3IStpU1Ijwg=",
);
it("sets a pinKeyEncryptedUserKeyPersistent if a userKeyEncryptedPin and pinKeyEncryptedUserKey is set", async () => {
pinService.createPinKeyEncryptedUserKey.mockResolvedValue(mockPinKeyEncryptedUserKey);
pinService.getUserKeyEncryptedPin.mockResolvedValue(mockUserKeyEncryptedPin);
pinService.getPinKeyEncryptedUserKeyPersistent.mockResolvedValue(
mockPinKeyEncryptedUserKey,
);
await keyService.setUserKey(mockUserKey, mockUserId);
expect(pinService.storePinKeyEncryptedUserKey).toHaveBeenCalledWith(
mockPinKeyEncryptedUserKey,
false,
mockUserId,
);
});
it("sets a pinKeyEncryptedUserKeyEphemeral if a userKeyEncryptedPin is set, but a pinKeyEncryptedUserKey is not set", async () => {
pinService.createPinKeyEncryptedUserKey.mockResolvedValue(mockPinKeyEncryptedUserKey);
pinService.getUserKeyEncryptedPin.mockResolvedValue(mockUserKeyEncryptedPin);
pinService.getPinKeyEncryptedUserKeyPersistent.mockResolvedValue(null);
await keyService.setUserKey(mockUserKey, mockUserId);
expect(pinService.storePinKeyEncryptedUserKey).toHaveBeenCalledWith(
mockPinKeyEncryptedUserKey,
true,
mockUserId,
);
});
it("clears the pinKeyEncryptedUserKeyPersistent and pinKeyEncryptedUserKeyEphemeral if the UserKeyEncryptedPin is not set", async () => {
pinService.getUserKeyEncryptedPin.mockResolvedValue(null);
await keyService.setUserKey(mockUserKey, mockUserId);
expect(pinService.clearPinKeyEncryptedUserKeyPersistent).toHaveBeenCalledWith(mockUserId);
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(mockUserId);
});
});
});
describe("setUserKeys", () => {
@@ -388,36 +337,22 @@ describe("keyService", () => {
const invalidUserIdTestCases = [
{ keySuffix: KeySuffixOptions.Auto, userId: null as unknown as UserId },
{ keySuffix: KeySuffixOptions.Auto, userId: undefined as unknown as UserId },
{ keySuffix: KeySuffixOptions.Pin, userId: null as unknown as UserId },
{ keySuffix: KeySuffixOptions.Pin, userId: undefined as unknown as UserId },
];
test.each(invalidUserIdTestCases)(
"throws when keySuffix is $keySuffix and userId is $userId",
async ({ keySuffix, userId }) => {
await expect(keyService.clearStoredUserKey(keySuffix, userId)).rejects.toThrow(
"UserId is required",
);
await expect(keyService.clearStoredUserKey(userId)).rejects.toThrow("UserId is required");
},
);
});
describe("with Auto key suffix", () => {
it("UserKeyAutoUnlock is cleared and pin keys are not cleared", async () => {
await keyService.clearStoredUserKey(KeySuffixOptions.Auto, mockUserId);
await keyService.clearStoredUserKey(mockUserId);
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, {
userId: mockUserId,
});
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).not.toHaveBeenCalled();
});
});
describe("with PIN key suffix", () => {
it("pin keys are cleared and user key auto unlock not", async () => {
await keyService.clearStoredUserKey(KeySuffixOptions.Pin, mockUserId);
expect(stateService.setUserKeyAutoUnlock).not.toHaveBeenCalled();
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(mockUserId);
});
});
});
@@ -448,24 +383,6 @@ describe("keyService", () => {
});
});
describe("clearPinKeys", () => {
test.each([null as unknown as UserId, undefined as unknown as UserId])(
"throws when the provided userId is %s",
async (userId) => {
await expect(keyService.clearPinKeys(userId)).rejects.toThrow("UserId is required");
},
);
it("calls pin service to clear", async () => {
const userId = "someOtherUser" as UserId;
await keyService.clearPinKeys(userId);
expect(pinService.clearPinKeyEncryptedUserKeyPersistent).toHaveBeenCalledWith(userId);
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(userId);
expect(pinService.clearUserKeyEncryptedPin).toHaveBeenCalledWith(userId);
});
});
describe("userPrivateKey$", () => {
let mockUserKey: UserKey;
let mockUserPrivateKey: Uint8Array;
@@ -1262,7 +1179,6 @@ describe("keyService", () => {
expect(result).toEqual(mockUserKey);
expect(validateUserKeySpy).toHaveBeenCalledWith(mockUserKey, mockUserId);
expect(logService.warning).toHaveBeenCalledWith("Invalid key, throwing away stored keys");
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(mockUserId);
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, {
userId: mockUserId,
});