mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +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>;
|
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
|
* Retrieves the user key from storage
|
||||||
* @param keySuffix The desired version of the user's key to retrieve
|
* @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>;
|
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.
|
* 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
|
* 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$", () => {
|
describe("everHadUserKey$", () => {
|
||||||
let everHadUserKeyState: FakeSingleUserState<boolean>;
|
let everHadUserKeyState: FakeSingleUserState<boolean>;
|
||||||
|
|
||||||
|
|||||||
@@ -157,34 +157,6 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
return userKey;
|
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(
|
async getUserKeyFromStorage(
|
||||||
keySuffix: KeySuffixOptions,
|
keySuffix: KeySuffixOptions,
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
@@ -819,29 +791,6 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
return this.stateProvider.getUser(userId, USER_KEY).state$;
|
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) {
|
userPublicKey$(userId: UserId) {
|
||||||
return this.userPrivateKey$(userId).pipe(
|
return this.userPrivateKey$(userId).pipe(
|
||||||
switchMap(async (pk) => await this.derivePublicKey(pk)),
|
switchMap(async (pk) => await this.derivePublicKey(pk)),
|
||||||
@@ -857,9 +806,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userPrivateKey$(userId: UserId): Observable<UserPrivateKey | null> {
|
userPrivateKey$(userId: UserId): Observable<UserPrivateKey | null> {
|
||||||
return this.userPrivateKeyHelper$(userId, false).pipe(
|
return this.userPrivateKeyHelper$(userId).pipe(map((keys) => keys?.userPrivateKey ?? null));
|
||||||
map((keys) => keys?.userPrivateKey ?? null),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userEncryptionKeyPair$(
|
userEncryptionKeyPair$(
|
||||||
@@ -881,14 +828,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$;
|
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$;
|
||||||
}
|
}
|
||||||
|
|
||||||
userPrivateKeyWithLegacySupport$(userId: UserId): Observable<UserPrivateKey | null> {
|
private userPrivateKeyHelper$(userId: UserId) {
|
||||||
return this.userPrivateKeyHelper$(userId, true).pipe(
|
const userKey$ = this.userKey$(userId);
|
||||||
map((keys) => keys?.userPrivateKey ?? null),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private userPrivateKeyHelper$(userId: UserId, legacySupport: boolean) {
|
|
||||||
const userKey$ = legacySupport ? this.userKeyWithLegacySupport$(userId) : this.userKey$(userId);
|
|
||||||
return userKey$.pipe(
|
return userKey$.pipe(
|
||||||
switchMap((userKey) => {
|
switchMap((userKey) => {
|
||||||
if (userKey == null) {
|
if (userKey == null) {
|
||||||
@@ -971,7 +912,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
orgKeys$(userId: UserId): Observable<Record<OrganizationId, OrgKey> | null> {
|
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$(
|
encryptedOrgKeys$(
|
||||||
@@ -980,11 +921,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$;
|
return this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$;
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherDecryptionKeys$(
|
cipherDecryptionKeys$(userId: UserId): Observable<CipherDecryptionKeys | null> {
|
||||||
userId: UserId,
|
return this.userPrivateKeyHelper$(userId)?.pipe(
|
||||||
legacySupport: boolean = false,
|
|
||||||
): Observable<CipherDecryptionKeys | null> {
|
|
||||||
return this.userPrivateKeyHelper$(userId, legacySupport)?.pipe(
|
|
||||||
switchMap((userKeys) => {
|
switchMap((userKeys) => {
|
||||||
if (userKeys == null) {
|
if (userKeys == null) {
|
||||||
return of(null);
|
return of(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user