mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +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:
@@ -841,10 +841,7 @@ export default class MainBackground {
|
||||
);
|
||||
|
||||
this.pinService = new PinService(
|
||||
this.accountService,
|
||||
this.encryptService,
|
||||
this.kdfConfigService,
|
||||
this.keyGenerationService,
|
||||
this.logService,
|
||||
this.keyService,
|
||||
this.sdkService,
|
||||
@@ -1112,7 +1109,7 @@ export default class MainBackground {
|
||||
this.collectionService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.accountService,
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
@@ -1120,7 +1117,7 @@ export default class MainBackground {
|
||||
this.individualVaultExportService = new IndividualVaultExportService(
|
||||
this.folderService,
|
||||
this.cipherService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
@@ -1134,7 +1131,7 @@ export default class MainBackground {
|
||||
this.organizationVaultExportService = new OrganizationVaultExportService(
|
||||
this.cipherService,
|
||||
this.exportApiService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
|
||||
@@ -492,10 +492,7 @@ export class ServiceContainer {
|
||||
|
||||
const pinStateService = new PinStateService(this.stateProvider);
|
||||
this.pinService = new PinService(
|
||||
this.accountService,
|
||||
this.encryptService,
|
||||
this.kdfConfigService,
|
||||
this.keyGenerationService,
|
||||
this.logService,
|
||||
this.keyService,
|
||||
this.sdkService,
|
||||
@@ -908,7 +905,7 @@ export class ServiceContainer {
|
||||
this.collectionService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.accountService,
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
@@ -916,7 +913,7 @@ export class ServiceContainer {
|
||||
this.individualExportService = new IndividualVaultExportService(
|
||||
this.folderService,
|
||||
this.cipherService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
@@ -930,7 +927,7 @@ export class ServiceContainer {
|
||||
this.organizationExportService = new OrganizationVaultExportService(
|
||||
this.cipherService,
|
||||
this.vaultExportApiService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
|
||||
@@ -952,7 +952,7 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [
|
||||
FolderServiceAbstraction,
|
||||
CipherServiceAbstraction,
|
||||
PinServiceAbstraction,
|
||||
KeyGenerationService,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
CryptoFunctionServiceAbstraction,
|
||||
@@ -972,7 +972,7 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [
|
||||
CipherServiceAbstraction,
|
||||
VaultExportApiService,
|
||||
PinServiceAbstraction,
|
||||
KeyGenerationService,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
CryptoFunctionServiceAbstraction,
|
||||
@@ -1357,16 +1357,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: PinServiceAbstraction,
|
||||
useClass: PinService,
|
||||
deps: [
|
||||
AccountServiceAbstraction,
|
||||
EncryptService,
|
||||
KdfConfigService,
|
||||
KeyGenerationService,
|
||||
LogService,
|
||||
KeyService,
|
||||
SdkService,
|
||||
PinStateServiceAbstraction,
|
||||
],
|
||||
deps: [EncryptService, LogService, KeyService, SdkService, PinStateServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: WebAuthnLoginPrfKeyServiceAbstraction,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
*
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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">;
|
||||
|
||||
@@ -7,8 +7,8 @@ import { safeProvider, SafeProvider } from "@bitwarden/angular/platform/utils/sa
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -84,7 +84,7 @@ export const ImporterProviders: SafeProvider[] = [
|
||||
CollectionService,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
PinServiceAbstraction,
|
||||
KeyGenerationService,
|
||||
AccountService,
|
||||
RestrictedItemTypesService,
|
||||
],
|
||||
|
||||
@@ -2,8 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { emptyGuid, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
@@ -24,7 +24,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
const password = Utils.newGuid();
|
||||
const promptForPassword_callback = async () => {
|
||||
@@ -36,7 +36,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
encryptService = mock<EncryptService>();
|
||||
i18nService = mock<I18nService>();
|
||||
cipherService = mock<CipherService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
keyGenerationService = mock<KeyGenerationService>();
|
||||
accountService = mock<AccountService>();
|
||||
|
||||
accountService.activeAccount$ = of({
|
||||
@@ -71,7 +71,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
encryptService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
accountService,
|
||||
promptForPassword_callback,
|
||||
);
|
||||
@@ -105,7 +105,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
encryptService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
accountService,
|
||||
promptForPassword_callback,
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -29,7 +29,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
||||
encryptService: EncryptService,
|
||||
i18nService: I18nService,
|
||||
cipherService: CipherService,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private keyGenerationService: KeyGenerationService,
|
||||
accountService: AccountService,
|
||||
private promptForPassword_callback: () => Promise<string>,
|
||||
) {
|
||||
@@ -86,7 +86,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
||||
? new PBKDF2KdfConfig(jdoc.kdfIterations)
|
||||
: new Argon2KdfConfig(jdoc.kdfIterations, jdoc.kdfMemory, jdoc.kdfParallelism);
|
||||
|
||||
this.key = await this.pinService.makePinKey(password, jdoc.salt, kdfConfig);
|
||||
this.key = await this.keyGenerationService.deriveVaultExportKey(password, jdoc.salt, kdfConfig);
|
||||
|
||||
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
@@ -36,7 +36,7 @@ describe("ImportService", () => {
|
||||
let collectionService: MockProxy<CollectionService>;
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
let restrictedItemTypesService: MockProxy<RestrictedItemTypesService>;
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("ImportService", () => {
|
||||
collectionService = mock<CollectionService>();
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
keyGenerationService = mock<KeyGenerationService>();
|
||||
restrictedItemTypesService = mock<RestrictedItemTypesService>();
|
||||
|
||||
importService = new ImportService(
|
||||
@@ -59,7 +59,7 @@ describe("ImportService", () => {
|
||||
collectionService,
|
||||
keyService,
|
||||
encryptService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
accountService,
|
||||
restrictedItemTypesService,
|
||||
);
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { ImportCiphersRequest } from "@bitwarden/common/models/request/import-ciphers.request";
|
||||
import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/request/import-organization-ciphers.request";
|
||||
import { KvpRequest } from "@bitwarden/common/models/request/kvp.request";
|
||||
@@ -119,7 +119,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
private collectionService: CollectionService,
|
||||
private keyService: KeyService,
|
||||
private encryptService: EncryptService,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private keyGenerationService: KeyGenerationService,
|
||||
private accountService: AccountService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {}
|
||||
@@ -238,7 +238,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
this.encryptService,
|
||||
this.i18nService,
|
||||
this.cipherService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.accountService,
|
||||
promptForPassword_callback,
|
||||
);
|
||||
|
||||
@@ -70,12 +70,13 @@ import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismi
|
||||
import { RemoveNewCustomizationOptionsCalloutDismissed } from "./migrations/71-remove-new-customization-options-callout-dismissed";
|
||||
import { RemoveAccountDeprovisioningBannerDismissed } from "./migrations/72-remove-account-deprovisioning-banner-dismissed";
|
||||
import { AddMasterPasswordUnlockData } from "./migrations/73-add-master-password-unlock-data";
|
||||
import { RemoveLegacyPin } from "./migrations/74-remove-legacy-pin";
|
||||
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
|
||||
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 3;
|
||||
export const CURRENT_VERSION = 73;
|
||||
export const CURRENT_VERSION = 74;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
@@ -150,7 +151,8 @@ export function createMigrationBuilder() {
|
||||
.with(RemoveAcBannersDismissed, 69, 70)
|
||||
.with(RemoveNewCustomizationOptionsCalloutDismissed, 70, 71)
|
||||
.with(RemoveAccountDeprovisioningBannerDismissed, 71, 72)
|
||||
.with(AddMasterPasswordUnlockData, 72, CURRENT_VERSION);
|
||||
.with(AddMasterPasswordUnlockData, 72, 73)
|
||||
.with(RemoveLegacyPin, 73, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { runMigrator } from "../migration-helper.spec";
|
||||
import { IRREVERSIBLE } from "../migrator";
|
||||
|
||||
import { RemoveLegacyPin } from "./74-remove-legacy-pin";
|
||||
|
||||
describe("RemoveLegacyPin", () => {
|
||||
const sut = new RemoveLegacyPin(73, 74);
|
||||
|
||||
describe("migrate", () => {
|
||||
it("deletes legacy pin from all users", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
user2: {
|
||||
email: "user2@email.com",
|
||||
name: "User 2",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
user_user1_pinUnlock_pinKeyEncryptedUserKeyPersistent: "abc",
|
||||
user_user2_pinUnlock_pinKeyEncryptedUserKeyPersistent: "def",
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_account_accounts: {
|
||||
user1: {
|
||||
email: "user1@email.com",
|
||||
name: "User 1",
|
||||
emailVerified: true,
|
||||
},
|
||||
user2: {
|
||||
email: "user2@email.com",
|
||||
name: "User 2",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("rollback", () => {
|
||||
it("is irreversible", async () => {
|
||||
await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { IRREVERSIBLE, Migrator } from "../migrator";
|
||||
|
||||
type ExpectedAccountType = NonNullable<unknown>;
|
||||
|
||||
export const PinProtectedUserKey: KeyDefinitionLike = {
|
||||
key: "pinKeyEncryptedUserKeyPersistent",
|
||||
stateDefinition: {
|
||||
name: "pinUnlock",
|
||||
},
|
||||
};
|
||||
|
||||
export class RemoveLegacyPin extends Migrator<73, 74> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
|
||||
const pinProtectedUserKey = await helper.getFromUser(userId, PinProtectedUserKey);
|
||||
|
||||
if (pinProtectedUserKey != null) {
|
||||
await helper.removeFromUser(userId, PinProtectedUserKey);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
|
||||
}
|
||||
|
||||
async rollback(helper: MigrationHelper): Promise<void> {
|
||||
throw IRREVERSIBLE;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -12,7 +12,7 @@ import { KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"
|
||||
import { BitwardenCsvExportType, BitwardenPasswordProtectedFileFormat } from "../types";
|
||||
export class BaseVaultExportService {
|
||||
constructor(
|
||||
protected pinService: PinServiceAbstraction,
|
||||
protected keyGenerationService: KeyGenerationService,
|
||||
protected encryptService: EncryptService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private kdfConfigService: KdfConfigService,
|
||||
@@ -26,7 +26,8 @@ export class BaseVaultExportService {
|
||||
const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig(userId);
|
||||
|
||||
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
||||
const key = await this.pinService.makePinKey(password, salt, kdfConfig);
|
||||
|
||||
const key = await this.keyGenerationService.deriveVaultExportKey(password, salt, kdfConfig);
|
||||
|
||||
const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), key);
|
||||
const encText = await this.encryptService.encryptString(clearText, key);
|
||||
|
||||
@@ -3,13 +3,13 @@ import * as JSZip from "jszip";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import {
|
||||
EncryptedString,
|
||||
EncString,
|
||||
} from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherId, emptyGuid, UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -169,7 +169,7 @@ describe("VaultExportService", () => {
|
||||
let exportService: IndividualVaultExportService;
|
||||
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
||||
let folderService: MockProxy<FolderService>;
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
@@ -184,7 +184,7 @@ describe("VaultExportService", () => {
|
||||
beforeEach(() => {
|
||||
cryptoFunctionService = mock<CryptoFunctionService>();
|
||||
cipherService = mock<CipherService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
keyGenerationService = mock<KeyGenerationService>();
|
||||
folderService = mock<FolderService>();
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
@@ -220,7 +220,7 @@ describe("VaultExportService", () => {
|
||||
exportService = new IndividualVaultExportService(
|
||||
folderService,
|
||||
cipherService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
keyService,
|
||||
encryptService,
|
||||
cryptoFunctionService,
|
||||
|
||||
@@ -5,9 +5,9 @@ import * as papa from "papaparse";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -42,7 +42,7 @@ export class IndividualVaultExportService
|
||||
constructor(
|
||||
private folderService: FolderService,
|
||||
private cipherService: CipherService,
|
||||
pinService: PinServiceAbstraction,
|
||||
keyGenerationService: KeyGenerationService,
|
||||
private keyService: KeyService,
|
||||
encryptService: EncryptService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
@@ -50,7 +50,7 @@ export class IndividualVaultExportService
|
||||
private apiService: ApiService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
super(keyGenerationService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
}
|
||||
|
||||
/** Creates an export of an individual vault (My Vault). Based on the provided format it will either be unencrypted, encrypted or password protected and in case zip is selected will include attachments
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
CollectionDetailsResponse,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
@@ -46,7 +46,7 @@ export class OrganizationVaultExportService
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private vaultExportApiService: VaultExportApiService,
|
||||
pinService: PinServiceAbstraction,
|
||||
keyGenerationService: KeyGenerationService,
|
||||
private keyService: KeyService,
|
||||
encryptService: EncryptService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
@@ -54,7 +54,7 @@ export class OrganizationVaultExportService
|
||||
kdfConfigService: KdfConfigService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
super(keyGenerationService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
}
|
||||
|
||||
/** Creates a password protected export of an organizational vault.
|
||||
|
||||
Reference in New Issue
Block a user