1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-29 14:43:31 +00:00

[PM-24353] Drop legacy pin support (#17328)

* Drop legacy pin support

* Fix cli build

* Fix browser build

* Remove pin key

* Fix comment

* Fix CI / tests

* Add migration to remove key

* Inline export key

* Extract vault export key generation

* Cleanup

* Add migrator

* Fix mv2 build
This commit is contained in:
Bernd Schoolmann
2025-12-11 13:01:09 +01:00
committed by GitHub
parent 404e07b6bd
commit 51d29f777e
26 changed files with 175 additions and 404 deletions

View File

@@ -91,4 +91,12 @@ export class DefaultKeyGenerationService implements KeyGenerationService {
return new SymmetricCryptoKey(newKey);
}
async deriveVaultExportKey(
password: string,
salt: string,
kdfConfig: KdfConfig,
): Promise<SymmetricCryptoKey> {
return await this.stretchKey(await this.deriveKeyFromPassword(password, salt, kdfConfig));
}
}

View File

@@ -87,4 +87,19 @@ export abstract class KeyGenerationService {
* @returns 64 byte derived key.
*/
abstract stretchKey(key: SymmetricCryptoKey): Promise<SymmetricCryptoKey>;
/**
* Derives a 64 byte key for encrypting and decrypting vault exports.
*
* @deprecated Do not use this for new use-cases.
* @param password Password to derive the key from.
* @param salt Salt for the key derivation function.
* @param kdfConfig Configuration for the key derivation function.
* @returns 64 byte derived key.
*/
abstract deriveVaultExportKey(
password: string,
salt: string,
kdfConfig: KdfConfig,
): Promise<SymmetricCryptoKey>;
}

View File

@@ -45,14 +45,6 @@ export abstract class PinStateServiceAbstraction {
pinLockType: PinLockType,
): Promise<PasswordProtectedKeyEnvelope | null>;
/**
* Gets the user's legacy PIN-protected UserKey
* @deprecated Use {@link getPinProtectedUserKeyEnvelope} instead. Only for migration support.
* @param userId The user's id
* @throws If the user id is not provided
*/
abstract getLegacyPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise<EncString | null>;
/**
* Sets the PIN state for the user
* @deprecated - This is not a public API. DO NOT USE IT

View File

@@ -13,7 +13,6 @@ import {
PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT,
PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL,
USER_KEY_ENCRYPTED_PIN,
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
} from "./pin.state";
export class PinStateService implements PinStateServiceAbstraction {
@@ -36,9 +35,7 @@ export class PinStateService implements PinStateServiceAbstraction {
assertNonNullish(userId, "userId");
const isPersistentPinSet =
(await this.getPinProtectedUserKeyEnvelope(userId, "PERSISTENT")) != null ||
// Deprecated
(await this.getLegacyPinKeyEncryptedUserKeyPersistent(userId)) != null;
(await this.getPinProtectedUserKeyEnvelope(userId, "PERSISTENT")) != null;
const isPinSet =
(await firstValueFrom(this.stateProvider.getUserState$(USER_KEY_ENCRYPTED_PIN, userId))) !=
null;
@@ -71,16 +68,6 @@ export class PinStateService implements PinStateServiceAbstraction {
}
}
async getLegacyPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise<EncString | null> {
assertNonNullish(userId, "userId");
return await firstValueFrom(
this.stateProvider
.getUserState$(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, userId)
.pipe(map((value) => (value ? new EncString(value) : null))),
);
}
async setPinState(
userId: UserId,
pinProtectedUserKeyEnvelope: PasswordProtectedKeyEnvelope,
@@ -116,9 +103,6 @@ export class PinStateService implements PinStateServiceAbstraction {
await this.stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, null, userId);
await this.stateProvider.setUserState(PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL, null, userId);
await this.stateProvider.setUserState(PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT, null, userId);
// Note: This can be deleted after sufficiently many PINs are migrated and the state is removed.
await this.stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, null, userId);
}
async clearEphemeralPinState(userId: UserId): Promise<void> {

View File

@@ -13,7 +13,6 @@ import {
USER_KEY_ENCRYPTED_PIN,
PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL,
PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT,
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
} from "./pin.state";
describe("PinStateService", () => {
@@ -121,21 +120,6 @@ describe("PinStateService", () => {
expect(result).toBe("PERSISTENT");
});
it("should return 'PERSISTENT' if a legacy pin key encrypted user key (persistent) is found", async () => {
// Arrange
await stateProvider.setUserState(
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
mockUserKeyEncryptedPin,
mockUserId,
);
// Act
const result = await sut.getPinLockType(mockUserId);
// Assert
expect(result).toBe("PERSISTENT");
});
it("should return 'EPHEMERAL' if only user key encrypted pin is found", async () => {
// Arrange
await stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, mockUserKeyEncryptedPin, mockUserId);
@@ -164,7 +148,6 @@ describe("PinStateService", () => {
null,
mockUserId,
);
await stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, null, mockUserId);
await stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, null, mockUserId);
// Act
@@ -290,45 +273,6 @@ describe("PinStateService", () => {
});
});
describe("getLegacyPinKeyEncryptedUserKeyPersistent()", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test.each([null, undefined])("throws if userId is %p", async (userId) => {
// Act & Assert
await expect(() =>
sut.getLegacyPinKeyEncryptedUserKeyPersistent(userId as any),
).rejects.toThrow("userId is null or undefined.");
});
it("should return EncString when legacy key is set", async () => {
// Arrange
await stateProvider.setUserState(
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
mockUserKeyEncryptedPin,
mockUserId,
);
// Act
const result = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId);
// Assert
expect(result?.encryptedString).toEqual(mockUserKeyEncryptedPin);
});
test.each([null, undefined])("should return null when legacy key is %p", async (value) => {
// Arrange
await stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, value, mockUserId);
// Act
const result = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId);
// Assert
expect(result).toBeNull();
});
});
describe("setPinState()", () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -464,22 +408,6 @@ describe("PinStateService", () => {
expect(result).toBeNull();
});
it("clears legacy PIN key encrypted user key persistent", async () => {
// Arrange
await stateProvider.setUserState(
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
mockUserKeyEncryptedPin,
mockUserId,
);
// Act
await sut.clearPinState(mockUserId);
// Assert
const result = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId);
expect(result).toBeNull();
});
it("clears all PIN state when all types are set", async () => {
// Arrange - set up all possible PIN state
await sut.setPinState(
@@ -494,17 +422,11 @@ describe("PinStateService", () => {
mockUserKeyEncryptedPin,
"EPHEMERAL",
);
await stateProvider.setUserState(
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
mockUserKeyEncryptedPin,
mockUserId,
);
// Verify all state is set before clearing
expect(await firstValueFrom(sut.userKeyEncryptedPin$(mockUserId))).not.toBeNull();
expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL")).not.toBeNull();
expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "PERSISTENT")).not.toBeNull();
expect(await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId)).not.toBeNull();
// Act
await sut.clearPinState(mockUserId);
@@ -513,7 +435,6 @@ describe("PinStateService", () => {
expect(await firstValueFrom(sut.userKeyEncryptedPin$(mockUserId))).toBeNull();
expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL")).toBeNull();
expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "PERSISTENT")).toBeNull();
expect(await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId)).toBeNull();
});
it("results in PIN lock type DISABLED after clearing", async () => {
@@ -545,7 +466,6 @@ describe("PinStateService", () => {
expect(await firstValueFrom(sut.userKeyEncryptedPin$(mockUserId))).toBeNull();
expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL")).toBeNull();
expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "PERSISTENT")).toBeNull();
expect(await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId)).toBeNull();
expect(await sut.getPinLockType(mockUserId)).toBe("DISABLED");
});
});
@@ -623,32 +543,6 @@ describe("PinStateService", () => {
expect(ephemeralResult).toBeNull();
});
it("does not clear legacy PIN key encrypted user key persistent", async () => {
// Arrange - set up ephemeral state and legacy state
await sut.setPinState(
mockUserId,
mockEphemeralEnvelope,
mockUserKeyEncryptedPin,
"EPHEMERAL",
);
await stateProvider.setUserState(
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
mockUserKeyEncryptedPin,
mockUserId,
);
// Act
await sut.clearEphemeralPinState(mockUserId);
// Assert - legacy PIN should still be present
const legacyResult = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId);
expect(legacyResult?.encryptedString).toEqual(mockUserKeyEncryptedPin);
// Assert - ephemeral envelope should be cleared
const ephemeralResult = await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL");
expect(ephemeralResult).toBeNull();
});
it("changes PIN lock type from EPHEMERAL to DISABLED when no other PIN state exists", async () => {
// Arrange - set up only ephemeral PIN state
await sut.setPinState(

View File

@@ -1,8 +1,5 @@
// eslint-disable-next-line no-restricted-imports
import { KdfConfig } from "@bitwarden/key-management";
import { UserId } from "../../types/guid";
import { PinKey, UserKey } from "../../types/key";
import { UserKey } from "../../types/key";
import { PinLockType } from "./pin-lock-type";
@@ -69,10 +66,4 @@ export abstract class PinServiceAbstraction {
* @deprecated This is not deprecated, but only meant to be called by KeyService. DO NOT USE IT.
*/
abstract userUnlocked(userId: UserId): Promise<void>;
/**
* Makes a PinKey from the provided PIN.
* @deprecated - Note: This is currently re-used by vault exports, which is still permitted but should be refactored out to use a different construct.
*/
abstract makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise<PinKey>;
}

View File

@@ -1,18 +1,15 @@
import { firstValueFrom, map } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management";
import { KeyService } from "@bitwarden/key-management";
import { AccountService } from "../../auth/abstractions/account.service";
import { assertNonNullish } from "../../auth/utils";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../key-management/crypto/models/enc-string";
import { LogService } from "../../platform/abstractions/log.service";
import { SdkService } from "../../platform/abstractions/sdk/sdk.service";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { UserId } from "../../types/guid";
import { PinKey, UserKey } from "../../types/key";
import { KeyGenerationService } from "../crypto";
import { UserKey } from "../../types/key";
import { firstValueFromOrThrow } from "../utils";
import { PinLockType } from "./pin-lock-type";
@@ -21,10 +18,7 @@ import { PinServiceAbstraction } from "./pin.service.abstraction";
export class PinService implements PinServiceAbstraction {
constructor(
private accountService: AccountService,
private encryptService: EncryptService,
private kdfConfigService: KdfConfigService,
private keyGenerationService: KeyGenerationService,
private logService: LogService,
private keyService: KeyService,
private sdkService: SdkService,
@@ -56,19 +50,6 @@ export class PinService implements PinServiceAbstraction {
// On first unlock, set the ephemeral pin envelope, if it is not set yet
const pin = await this.getPin(userId);
await this.setPin(pin, "EPHEMERAL", userId);
} else if ((await this.pinStateService.getPinLockType(userId)) === "PERSISTENT") {
// Encrypted migration for persistent pin unlock to pin envelopes.
// This will be removed at the earliest in 2026.1.0
//
// ----- ENCRYPTION MIGRATION -----
// Pin-key encrypted user-keys are eagerly migrated to the new pin-protected user key envelope format.
if ((await this.pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent(userId)) != null) {
this.logService.info(
"[Pin Service] Migrating legacy PIN key to PinProtectedUserKeyEnvelope",
);
const pin = await this.getPin(userId);
await this.setPin(pin, "PERSISTENT", userId);
}
}
}
@@ -144,86 +125,30 @@ export class PinService implements PinServiceAbstraction {
assertNonNullish(pin, "pin");
assertNonNullish(userId, "userId");
const hasPinProtectedKeyEnvelopeSet =
(await this.pinStateService.getPinProtectedUserKeyEnvelope(userId, "EPHEMERAL")) != null ||
(await this.pinStateService.getPinProtectedUserKeyEnvelope(userId, "PERSISTENT")) != null;
this.logService.info("[Pin Service] Pin-unlock via PinProtectedUserKeyEnvelope");
if (hasPinProtectedKeyEnvelopeSet) {
this.logService.info("[Pin Service] Pin-unlock via PinProtectedUserKeyEnvelope");
const pinLockType = await this.pinStateService.getPinLockType(userId);
const envelope = await this.pinStateService.getPinProtectedUserKeyEnvelope(userId, pinLockType);
const pinLockType = await this.pinStateService.getPinLockType(userId);
const envelope = await this.pinStateService.getPinProtectedUserKeyEnvelope(
userId,
pinLockType,
try {
// Use the sdk to create an enrollment, not yet persisting it to state
const startTime = performance.now();
const userKeyBytes = await firstValueFrom(
this.sdkService.client$.pipe(
map((sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
return sdk.crypto().unseal_password_protected_key_envelope(pin, envelope!);
}),
),
);
this.logService.measure(startTime, "Crypto", "PinService", "UnsealPinEnvelope");
try {
// Use the sdk to create an enrollment, not yet persisting it to state
const startTime = performance.now();
const userKeyBytes = await firstValueFrom(
this.sdkService.client$.pipe(
map((sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
return sdk.crypto().unseal_password_protected_key_envelope(pin, envelope!);
}),
),
);
this.logService.measure(startTime, "Crypto", "PinService", "UnsealPinEnvelope");
return new SymmetricCryptoKey(userKeyBytes) as UserKey;
} catch (error) {
this.logService.error(`Failed to unseal pin: ${error}`);
return null;
}
} else {
this.logService.info("[Pin Service] Pin-unlock via legacy PinKeyEncryptedUserKey");
// This branch is deprecated and will be removed in the future, but is kept for migration.
try {
const pinKeyEncryptedUserKey =
await this.pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent(userId);
const email = await firstValueFrom(
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
return await this.decryptUserKey(pin, email, kdfConfig, pinKeyEncryptedUserKey!);
} catch (error) {
this.logService.error(`Error decrypting user key with pin: ${error}`);
return null;
}
return new SymmetricCryptoKey(userKeyBytes) as UserKey;
} catch (error) {
this.logService.error(`Failed to unseal pin: ${error}`);
return null;
}
}
/// Anything below here is deprecated and will be removed subsequently
async makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise<PinKey> {
const startTime = performance.now();
const pinKey = await this.keyGenerationService.deriveKeyFromPassword(pin, salt, kdfConfig);
this.logService.measure(startTime, "Crypto", "PinService", "makePinKey");
return (await this.keyGenerationService.stretchKey(pinKey)) as PinKey;
}
/**
* Decrypts the UserKey with the provided PIN.
* @deprecated
* @throws If the PIN does not match the PIN that was used to encrypt the user key
* @throws If the salt, or KDF don't match the salt / KDF used to encrypt the user key
*/
private async decryptUserKey(
pin: string,
salt: string,
kdfConfig: KdfConfig,
pinKeyEncryptedUserKey: EncString,
): Promise<UserKey> {
assertNonNullish(pin, "pin");
assertNonNullish(salt, "salt");
assertNonNullish(kdfConfig, "kdfConfig");
assertNonNullish(pinKeyEncryptedUserKey, "pinKeyEncryptedUserKey");
const pinKey = await this.makePinKey(pin, salt, kdfConfig);
const userKey = await this.encryptService.unwrapSymmetricKey(pinKeyEncryptedUserKey, pinKey);
return userKey as UserKey;
}
}

View File

@@ -2,17 +2,15 @@ import { mock } from "jest-mock-extended";
import { BehaviorSubject, filter } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management";
import { KeyService } from "@bitwarden/key-management";
import { PasswordProtectedKeyEnvelope } from "@bitwarden/sdk-internal";
import { MockSdkService } from "../..//platform/spec/mock-sdk.service";
import { FakeAccountService, mockAccountServiceWith, mockEnc } from "../../../spec";
import { LogService } from "../../platform/abstractions/log.service";
import { Utils } from "../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { UserId } from "../../types/guid";
import { PinKey, UserKey } from "../../types/key";
import { KeyGenerationService } from "../crypto";
import { UserKey } from "../../types/key";
import { EncryptService } from "../crypto/abstractions/encrypt.service";
import { EncryptedString, EncString } from "../crypto/models/enc-string";
@@ -22,16 +20,10 @@ import { PinService } from "./pin.service.implementation";
describe("PinService", () => {
let sut: PinService;
let accountService: FakeAccountService;
const encryptService = mock<EncryptService>();
const kdfConfigService = mock<KdfConfigService>();
const keyGenerationService = mock<KeyGenerationService>();
const logService = mock<LogService>();
const mockUserId = Utils.newGuid() as UserId;
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
const mockPinKey = new SymmetricCryptoKey(randomBytes(32)) as PinKey;
const mockUserEmail = "user@example.com";
const mockPin = "1234";
const mockUserKeyEncryptedPin = new EncString("userKeyEncryptedPin");
const mockEphemeralEnvelope = "mock-ephemeral-envelope" as PasswordProtectedKeyEnvelope;
@@ -42,7 +34,6 @@ describe("PinService", () => {
const behaviorSubject = new BehaviorSubject<{ userId: UserId; userKey: UserKey }>(null);
beforeEach(() => {
accountService = mockAccountServiceWith(mockUserId, { email: mockUserEmail });
(keyService as any)["unlockedUserKeys$"] = behaviorSubject
.asObservable()
.pipe(filter((x) => x != null));
@@ -50,16 +41,7 @@ describe("PinService", () => {
.mockDeep()
.unseal_password_protected_key_envelope.mockReturnValue(new Uint8Array(64));
sut = new PinService(
accountService,
encryptService,
kdfConfigService,
keyGenerationService,
logService,
keyService,
sdkService,
pinStateService,
);
sut = new PinService(encryptService, logService, keyService, sdkService, pinStateService);
});
it("should instantiate the PinService", () => {
@@ -89,26 +71,6 @@ describe("PinService", () => {
);
});
it("should migrate legacy persistent PIN if needed", async () => {
// Arrange
pinStateService.getPinLockType.mockResolvedValue("PERSISTENT");
pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent.mockResolvedValue(
mockEnc("legacy-key"),
);
const getPinSpy = jest.spyOn(sut, "getPin").mockResolvedValue(mockPin);
const setPinSpy = jest.spyOn(sut, "setPin").mockResolvedValue();
// Act
await sut.userUnlocked(mockUserId);
// Assert
expect(getPinSpy).toHaveBeenCalledWith(mockUserId);
expect(setPinSpy).toHaveBeenCalledWith(mockPin, "PERSISTENT", mockUserId);
expect(logService.info).toHaveBeenCalledWith(
"[Pin Service] Migrating legacy PIN key to PinProtectedUserKeyEnvelope",
);
});
it("should do nothing if no migration or setup is needed", async () => {
// Arrange
pinStateService.getPinLockType.mockResolvedValue("DISABLED");
@@ -124,28 +86,6 @@ describe("PinService", () => {
});
});
describe("makePinKey()", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should make a PinKey", async () => {
// Arrange
keyGenerationService.deriveKeyFromPassword.mockResolvedValue(mockPinKey);
// Act
await sut.makePinKey(mockPin, mockUserEmail, DEFAULT_KDF_CONFIG);
// Assert
expect(keyGenerationService.deriveKeyFromPassword).toHaveBeenCalledWith(
mockPin,
mockUserEmail,
DEFAULT_KDF_CONFIG,
);
expect(keyGenerationService.stretchKey).toHaveBeenCalledWith(mockPinKey);
});
});
describe("getPin()", () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -383,7 +323,6 @@ describe("PinService", () => {
jest.clearAllMocks();
pinStateService.userKeyEncryptedPin$.mockReset();
pinStateService.getPinProtectedUserKeyEnvelope.mockReset();
pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent.mockReset();
});
it("should throw an error if userId is null", async () => {
@@ -423,32 +362,5 @@ describe("PinService", () => {
// Assert
expect(result).toEqual(mockUserKey);
});
it("should return userkey with legacy pin PERSISTENT", async () => {
keyGenerationService.deriveKeyFromPassword.mockResolvedValue(mockPinKey);
keyGenerationService.stretchKey.mockResolvedValue(mockPinKey);
kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG);
encryptService.unwrapSymmetricKey.mockResolvedValue(mockUserKey);
// Arrange
const mockPin = "1234";
pinStateService.userKeyEncryptedPin$.mockReturnValueOnce(
new BehaviorSubject(mockUserKeyEncryptedPin),
);
pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent.mockResolvedValueOnce(
mockUserKeyEncryptedPin,
);
// Act
const result = await sut.decryptUserKeyWithPin(mockPin, mockUserId);
// Assert
expect(result).toEqual(mockUserKey);
});
});
});
// Test helpers
function randomBytes(length: number): Uint8Array {
return new Uint8Array(Array.from({ length }, (_, k) => k % 255));
}

View File

@@ -3,22 +3,6 @@ import { PasswordProtectedKeyEnvelope } from "@bitwarden/sdk-internal";
import { EncryptedString } from "../crypto/models/enc-string";
/**
* The persistent (stored on disk) version of the UserKey, encrypted by the PinKey.
*
* @deprecated
* @remarks Persists through a client reset. Used when `requireMasterPasswordOnClientRestart` is disabled.
* @see SetPinComponent.setPinForm.requireMasterPasswordOnClientRestart
*/
export const PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT = new UserKeyDefinition<EncryptedString>(
PIN_DISK,
"pinKeyEncryptedUserKeyPersistent",
{
deserializer: (jsonValue) => jsonValue,
clearOn: ["logout"],
},
);
/**
* The persistent (stored on disk) version of the UserKey, stored in a `PasswordProtectedKeyEnvelope`.
*

View File

@@ -56,7 +56,7 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract
return;
}
// If there is an active user, check if they have a pinKeyEncryptedUserKeyEphemeral. If so, prevent process reload upon lock.
// If there is an active user, check if they have an ephemeral PIN. If so, prevent process reload upon lock.
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
if (userId != null) {
if ((await this.pinService.getPinLockType(userId)) === "EPHEMERAL") {

View File

@@ -9,8 +9,6 @@ export type PrfKey = Opaque<SymmetricCryptoKey, "PrfKey">;
export type UserKey = Opaque<SymmetricCryptoKey, "UserKey">;
/** @deprecated Interacting with the master key directly is prohibited. Use a high level function from MasterPasswordService instead. */
export type MasterKey = Opaque<SymmetricCryptoKey, "MasterKey">;
/** @deprecated */
export type PinKey = Opaque<SymmetricCryptoKey, "PinKey">;
export type OrgKey = Opaque<SymmetricCryptoKey, "OrgKey">;
export type ProviderKey = Opaque<SymmetricCryptoKey, "ProviderKey">;
export type CipherKey = Opaque<SymmetricCryptoKey, "CipherKey">;