mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
Remove legacy encryption support in key service (#15898)
This commit is contained in:
@@ -107,28 +107,6 @@ export abstract class KeyService {
|
||||
*/
|
||||
abstract getUserKey(userId?: string): Promise<UserKey>;
|
||||
|
||||
/**
|
||||
* Checks if the user is using an old encryption scheme that used the master key
|
||||
* for encryption of data instead of the user key.
|
||||
*/
|
||||
abstract isLegacyUser(masterKey?: MasterKey, userId?: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Use for encryption/decryption of data in order to support legacy
|
||||
* encryption models. It will return the user key if available,
|
||||
* if not it will return the master key.
|
||||
*
|
||||
* @deprecated Please provide the userId of the user you want the user key for.
|
||||
*/
|
||||
abstract getUserKeyWithLegacySupport(): Promise<UserKey>;
|
||||
|
||||
/**
|
||||
* Use for encryption/decryption of data in order to support legacy
|
||||
* encryption models. It will return the user key if available,
|
||||
* if not it will return the master key.
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract getUserKeyWithLegacySupport(userId: UserId): Promise<UserKey>;
|
||||
/**
|
||||
* Retrieves the user key from storage
|
||||
* @param keySuffix The desired version of the user's key to retrieve
|
||||
@@ -317,15 +295,6 @@ export abstract class KeyService {
|
||||
*/
|
||||
abstract userEncryptedPrivateKey$(userId: UserId): Observable<EncryptedString | null>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the given users decrypted private key with legacy support,
|
||||
* will emit null if the user doesn't have a UserKey to decrypt the encrypted private key
|
||||
* or null if the user doesn't have an encrypted private key at all.
|
||||
*
|
||||
* @param userId The user id of the user to get the data for.
|
||||
*/
|
||||
abstract userPrivateKeyWithLegacySupport$(userId: UserId): Observable<UserPrivateKey | null>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the given users decrypted private key and public key, guaranteed to be consistent.
|
||||
* Will emit null if the user doesn't have a userkey to decrypt the encrypted private key, or null if the user doesn't have a private key
|
||||
|
||||
@@ -171,41 +171,6 @@ describe("keyService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserKeyWithLegacySupport", () => {
|
||||
let mockUserKey: UserKey;
|
||||
let mockMasterKey: MasterKey;
|
||||
let getMasterKey: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey;
|
||||
|
||||
getMasterKey = jest.spyOn(masterPasswordService, "masterKey$");
|
||||
});
|
||||
|
||||
it("returns the User Key if available", async () => {
|
||||
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(mockUserKey);
|
||||
const getKeySpy = jest.spyOn(keyService, "getUserKey");
|
||||
|
||||
const userKey = await keyService.getUserKeyWithLegacySupport(mockUserId);
|
||||
|
||||
expect(getKeySpy).toHaveBeenCalledWith(mockUserId);
|
||||
expect(getMasterKey).not.toHaveBeenCalled();
|
||||
|
||||
expect(userKey).toEqual(mockUserKey);
|
||||
});
|
||||
|
||||
it("returns the user's master key when User Key is not available", async () => {
|
||||
masterPasswordService.masterKeySubject.next(mockMasterKey);
|
||||
|
||||
const userKey = await keyService.getUserKeyWithLegacySupport(mockUserId);
|
||||
|
||||
expect(getMasterKey).toHaveBeenCalledWith(mockUserId);
|
||||
expect(userKey).toEqual(mockMasterKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe("everHadUserKey$", () => {
|
||||
let everHadUserKeyState: FakeSingleUserState<boolean>;
|
||||
|
||||
|
||||
@@ -157,34 +157,6 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
return userKey;
|
||||
}
|
||||
|
||||
async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise<boolean> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (userId == null) {
|
||||
throw new Error("No active user id found.");
|
||||
}
|
||||
masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
|
||||
return await this.validateUserKey(masterKey, userId);
|
||||
}
|
||||
|
||||
// TODO: legacy support for user key is no longer needed since we require users to migrate on login
|
||||
async getUserKeyWithLegacySupport(userId?: UserId): Promise<UserKey> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (userId == null) {
|
||||
throw new Error("No active user id found.");
|
||||
}
|
||||
|
||||
const userKey = await this.getUserKey(userId);
|
||||
if (userKey) {
|
||||
return userKey;
|
||||
}
|
||||
|
||||
// Legacy support: encryption used to be done with the master key (derived from master password).
|
||||
// Users who have not migrated will have a null user key and must use the master key instead.
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
return masterKey as unknown as UserKey;
|
||||
}
|
||||
|
||||
async getUserKeyFromStorage(
|
||||
keySuffix: KeySuffixOptions,
|
||||
userId: UserId,
|
||||
@@ -819,29 +791,6 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
return this.stateProvider.getUser(userId, USER_KEY).state$;
|
||||
}
|
||||
|
||||
private userKeyWithLegacySupport$(userId: UserId) {
|
||||
return this.userKey$(userId).pipe(
|
||||
switchMap((userKey) => {
|
||||
if (userKey != null) {
|
||||
return of(userKey);
|
||||
}
|
||||
|
||||
// Legacy path
|
||||
return this.masterPasswordService.masterKey$(userId).pipe(
|
||||
switchMap(async (masterKey) => {
|
||||
if (!(await this.validateUserKey(masterKey, userId))) {
|
||||
// We don't have a UserKey or a valid MasterKey
|
||||
return null;
|
||||
}
|
||||
|
||||
// The master key is valid meaning, the org keys and such are encrypted with this key
|
||||
return masterKey as unknown as UserKey;
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
userPublicKey$(userId: UserId) {
|
||||
return this.userPrivateKey$(userId).pipe(
|
||||
switchMap(async (pk) => await this.derivePublicKey(pk)),
|
||||
@@ -857,9 +806,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
|
||||
userPrivateKey$(userId: UserId): Observable<UserPrivateKey | null> {
|
||||
return this.userPrivateKeyHelper$(userId, false).pipe(
|
||||
map((keys) => keys?.userPrivateKey ?? null),
|
||||
);
|
||||
return this.userPrivateKeyHelper$(userId).pipe(map((keys) => keys?.userPrivateKey ?? null));
|
||||
}
|
||||
|
||||
userEncryptionKeyPair$(
|
||||
@@ -881,14 +828,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$;
|
||||
}
|
||||
|
||||
userPrivateKeyWithLegacySupport$(userId: UserId): Observable<UserPrivateKey | null> {
|
||||
return this.userPrivateKeyHelper$(userId, true).pipe(
|
||||
map((keys) => keys?.userPrivateKey ?? null),
|
||||
);
|
||||
}
|
||||
|
||||
private userPrivateKeyHelper$(userId: UserId, legacySupport: boolean) {
|
||||
const userKey$ = legacySupport ? this.userKeyWithLegacySupport$(userId) : this.userKey$(userId);
|
||||
private userPrivateKeyHelper$(userId: UserId) {
|
||||
const userKey$ = this.userKey$(userId);
|
||||
return userKey$.pipe(
|
||||
switchMap((userKey) => {
|
||||
if (userKey == null) {
|
||||
@@ -971,7 +912,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
|
||||
orgKeys$(userId: UserId): Observable<Record<OrganizationId, OrgKey> | null> {
|
||||
return this.cipherDecryptionKeys$(userId, true).pipe(map((keys) => keys?.orgKeys ?? null));
|
||||
return this.cipherDecryptionKeys$(userId).pipe(map((keys) => keys?.orgKeys ?? null));
|
||||
}
|
||||
|
||||
encryptedOrgKeys$(
|
||||
@@ -980,11 +921,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$;
|
||||
}
|
||||
|
||||
cipherDecryptionKeys$(
|
||||
userId: UserId,
|
||||
legacySupport: boolean = false,
|
||||
): Observable<CipherDecryptionKeys | null> {
|
||||
return this.userPrivateKeyHelper$(userId, legacySupport)?.pipe(
|
||||
cipherDecryptionKeys$(userId: UserId): Observable<CipherDecryptionKeys | null> {
|
||||
return this.userPrivateKeyHelper$(userId)?.pipe(
|
||||
switchMap((userKeys) => {
|
||||
if (userKeys == null) {
|
||||
return of(null);
|
||||
|
||||
Reference in New Issue
Block a user