1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[PM-3475] Remove deprecated keys (#13266)

* Remove deprecated keys

* Fix cli build

* Fix build
This commit is contained in:
Bernd Schoolmann
2025-03-31 16:58:02 +02:00
committed by GitHub
parent 0311681803
commit 22039d038d
14 changed files with 32 additions and 427 deletions

View File

@@ -4,11 +4,9 @@ import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
@@ -18,7 +16,7 @@ import {
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, PinKey, UserKey } from "@bitwarden/common/types/key";
import { PinKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfig, KdfConfigService } from "@bitwarden/key-management";
import { PinServiceAbstraction } from "../../abstractions/pin.service.abstraction";
@@ -73,19 +71,6 @@ export const USER_KEY_ENCRYPTED_PIN = new UserKeyDefinition<EncryptedString>(
},
);
/**
* The old MasterKey, encrypted by the PinKey (formerly called `pinProtected`).
* Deprecated and used for migration purposes only.
*/
export const OLD_PIN_KEY_ENCRYPTED_MASTER_KEY = new UserKeyDefinition<EncryptedString>(
PIN_DISK,
"oldPinKeyEncryptedMasterKey",
{
deserializer: (jsonValue) => jsonValue,
clearOn: ["logout"],
},
);
export class PinService implements PinServiceAbstraction {
constructor(
private accountService: AccountService,
@@ -94,9 +79,7 @@ export class PinService implements PinServiceAbstraction {
private kdfConfigService: KdfConfigService,
private keyGenerationService: KeyGenerationService,
private logService: LogService,
private masterPasswordService: MasterPasswordServiceAbstraction,
private stateProvider: StateProvider,
private stateService: StateService,
) {}
async getPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise<EncString | null> {
@@ -190,9 +173,7 @@ export class PinService implements PinServiceAbstraction {
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const pinKey = await this.makePinKey(pin, email, kdfConfig);
return await this.encryptService.encrypt(userKey.key, pinKey);
}
@@ -242,20 +223,6 @@ export class PinService implements PinServiceAbstraction {
return await this.encryptService.encrypt(pin, userKey);
}
async getOldPinKeyEncryptedMasterKey(userId: UserId): Promise<EncryptedString | null> {
this.validateUserId(userId, "Cannot get oldPinKeyEncryptedMasterKey.");
return await firstValueFrom(
this.stateProvider.getUserState$(OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, userId),
);
}
async clearOldPinKeyEncryptedMasterKey(userId: UserId): Promise<void> {
this.validateUserId(userId, "Cannot clear oldPinKeyEncryptedMasterKey.");
await this.stateProvider.setUserState(OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, null, userId);
}
async makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise<PinKey> {
const pinKey = await this.keyGenerationService.deriveKeyFromPassword(pin, salt, kdfConfig);
return (await this.keyGenerationService.stretchKey(pinKey)) as PinKey;
@@ -264,23 +231,13 @@ export class PinService implements PinServiceAbstraction {
async getPinLockType(userId: UserId): Promise<PinLockType> {
this.validateUserId(userId, "Cannot get PinLockType.");
/**
* We can't check the `userKeyEncryptedPin` (formerly called `protectedPin`) for both because old
* accounts only used it for MP on Restart
*/
const aUserKeyEncryptedPinIsSet = !!(await this.getUserKeyEncryptedPin(userId));
const aPinKeyEncryptedUserKeyPersistentIsSet =
!!(await this.getPinKeyEncryptedUserKeyPersistent(userId));
const anOldPinKeyEncryptedMasterKeyIsSet =
!!(await this.getOldPinKeyEncryptedMasterKey(userId));
if (aPinKeyEncryptedUserKeyPersistentIsSet || anOldPinKeyEncryptedMasterKeyIsSet) {
if (aPinKeyEncryptedUserKeyPersistentIsSet) {
return "PERSISTENT";
} else if (
aUserKeyEncryptedPinIsSet &&
!aPinKeyEncryptedUserKeyPersistentIsSet &&
!anOldPinKeyEncryptedMasterKeyIsSet
) {
} else if (aUserKeyEncryptedPinIsSet && !aPinKeyEncryptedUserKeyPersistentIsSet) {
return "EPHEMERAL";
} else {
return "DISABLED";
@@ -302,7 +259,7 @@ export class PinService implements PinServiceAbstraction {
case "DISABLED":
return false;
case "PERSISTENT":
// The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey or OldPinKeyEncryptedMasterKey set.
// The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey set.
return true;
case "EPHEMERAL": {
// The above getPinLockType call ensures that we have a UserKeyEncryptedPin set.
@@ -326,31 +283,21 @@ export class PinService implements PinServiceAbstraction {
try {
const pinLockType = await this.getPinLockType(userId);
const requireMasterPasswordOnClientRestart = pinLockType === "EPHEMERAL";
const { pinKeyEncryptedUserKey, oldPinKeyEncryptedMasterKey } =
await this.getPinKeyEncryptedKeys(pinLockType, userId);
const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedKeys(pinLockType, userId);
const email = await firstValueFrom(
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
let userKey: UserKey;
if (oldPinKeyEncryptedMasterKey) {
userKey = await this.decryptAndMigrateOldPinKeyEncryptedMasterKey(
userId,
pin,
email,
kdfConfig,
requireMasterPasswordOnClientRestart,
oldPinKeyEncryptedMasterKey,
);
} else {
userKey = await this.decryptUserKey(userId, pin, email, kdfConfig, pinKeyEncryptedUserKey);
}
const userKey: UserKey = await this.decryptUserKey(
userId,
pin,
email,
kdfConfig,
pinKeyEncryptedUserKey,
);
if (!userKey) {
this.logService.warning(`User key null after pin key decryption.`);
return null;
@@ -394,109 +341,23 @@ export class PinService implements PinServiceAbstraction {
}
/**
* Creates a new `pinKeyEncryptedUserKey` and clears the `oldPinKeyEncryptedMasterKey`.
* @returns UserKey
*/
private async decryptAndMigrateOldPinKeyEncryptedMasterKey(
userId: UserId,
pin: string,
email: string,
kdfConfig: KdfConfig,
requireMasterPasswordOnClientRestart: boolean,
oldPinKeyEncryptedMasterKey: EncString,
): Promise<UserKey> {
this.validateUserId(userId, "Cannot decrypt and migrate oldPinKeyEncryptedMasterKey.");
const masterKey = await this.decryptMasterKeyWithPin(
userId,
pin,
email,
kdfConfig,
oldPinKeyEncryptedMasterKey,
);
const encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({ userId: userId });
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
masterKey,
userId,
encUserKey ? new EncString(encUserKey) : undefined,
);
const pinKeyEncryptedUserKey = await this.createPinKeyEncryptedUserKey(pin, userKey, userId);
await this.storePinKeyEncryptedUserKey(
pinKeyEncryptedUserKey,
requireMasterPasswordOnClientRestart,
userId,
);
const userKeyEncryptedPin = await this.createUserKeyEncryptedPin(pin, userKey);
await this.setUserKeyEncryptedPin(userKeyEncryptedPin, userId);
await this.clearOldPinKeyEncryptedMasterKey(userId);
return userKey;
}
// Only for migration purposes
private async decryptMasterKeyWithPin(
userId: UserId,
pin: string,
salt: string,
kdfConfig: KdfConfig,
oldPinKeyEncryptedMasterKey?: EncString,
): Promise<MasterKey> {
this.validateUserId(userId, "Cannot decrypt master key with PIN.");
if (!oldPinKeyEncryptedMasterKey) {
const oldPinKeyEncryptedMasterKeyString = await this.getOldPinKeyEncryptedMasterKey(userId);
if (oldPinKeyEncryptedMasterKeyString == null) {
throw new Error("No oldPinKeyEncrytedMasterKey found.");
}
oldPinKeyEncryptedMasterKey = new EncString(oldPinKeyEncryptedMasterKeyString);
}
const pinKey = await this.makePinKey(pin, salt, kdfConfig);
const masterKey = await this.encryptService.decryptToBytes(oldPinKeyEncryptedMasterKey, pinKey);
return new SymmetricCryptoKey(masterKey) as MasterKey;
}
/**
* Gets the user's `pinKeyEncryptedUserKey` (persistent or ephemeral) and `oldPinKeyEncryptedMasterKey`
* Gets the user's `pinKeyEncryptedUserKey` (persistent or ephemeral)
* (if one exists) based on the user's PinLockType.
*
* @remarks The `oldPinKeyEncryptedMasterKey` (formerly `pinProtected`) is only used for migration and
* will be null for all migrated accounts.
* @throws If PinLockType is 'DISABLED' or if userId is not provided
*/
private async getPinKeyEncryptedKeys(
pinLockType: PinLockType,
userId: UserId,
): Promise<{ pinKeyEncryptedUserKey: EncString; oldPinKeyEncryptedMasterKey?: EncString }> {
): Promise<EncString> {
this.validateUserId(userId, "Cannot get PinKey encrypted keys.");
switch (pinLockType) {
case "PERSISTENT": {
const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedUserKeyPersistent(userId);
const oldPinKeyEncryptedMasterKey = await this.getOldPinKeyEncryptedMasterKey(userId);
return {
pinKeyEncryptedUserKey,
oldPinKeyEncryptedMasterKey: oldPinKeyEncryptedMasterKey
? new EncString(oldPinKeyEncryptedMasterKey)
: undefined,
};
return await this.getPinKeyEncryptedUserKeyPersistent(userId);
}
case "EPHEMERAL": {
const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedUserKeyEphemeral(userId);
return {
pinKeyEncryptedUserKey,
oldPinKeyEncryptedMasterKey: undefined, // Going forward, we only migrate non-ephemeral version
};
return await this.getPinKeyEncryptedUserKeyEphemeral(userId);
}
case "DISABLED":
throw new Error("Pin is disabled");