1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-12 22:33:27 +00:00

Delay decryption of provider keys

This commit is contained in:
Thomas Rittson
2022-03-29 18:38:18 +10:00
parent a4fba0e1c5
commit 3bd8630f33
7 changed files with 77 additions and 20 deletions

View File

@@ -3,6 +3,7 @@ import { BehaviorSubject } from "rxjs";
import { KdfType } from "../enums/kdfType"; import { KdfType } from "../enums/kdfType";
import { ThemeType } from "../enums/themeType"; import { ThemeType } from "../enums/themeType";
import { UriMatchType } from "../enums/uriMatchType"; import { UriMatchType } from "../enums/uriMatchType";
import { EncryptedOrganizationKeyStore } from "../interfaces/encryptedOrganizationKeyStore";
import { CipherData } from "../models/data/cipherData"; import { CipherData } from "../models/data/cipherData";
import { CollectionData } from "../models/data/collectionData"; import { CollectionData } from "../models/data/collectionData";
import { EventData } from "../models/data/eventData"; import { EventData } from "../models/data/eventData";
@@ -23,7 +24,6 @@ import { CipherView } from "../models/view/cipherView";
import { CollectionView } from "../models/view/collectionView"; import { CollectionView } from "../models/view/collectionView";
import { FolderView } from "../models/view/folderView"; import { FolderView } from "../models/view/folderView";
import { SendView } from "../models/view/sendView"; import { SendView } from "../models/view/sendView";
export abstract class StateService<T extends Account = Account> { export abstract class StateService<T extends Account = Account> {
accounts: BehaviorSubject<{ [userId: string]: T }>; accounts: BehaviorSubject<{ [userId: string]: T }>;
activeAccount: BehaviorSubject<string>; activeAccount: BehaviorSubject<string>;
@@ -184,9 +184,11 @@ export abstract class StateService<T extends Account = Account> {
value: { [id: string]: FolderData }, value: { [id: string]: FolderData },
options?: StorageOptions options?: StorageOptions
) => Promise<void>; ) => Promise<void>;
getEncryptedOrganizationKeys: (options?: StorageOptions) => Promise<any>; getEncryptedOrganizationKeys: (
options?: StorageOptions
) => Promise<{ [orgId: string]: EncryptedOrganizationKeyStore }>;
setEncryptedOrganizationKeys: ( setEncryptedOrganizationKeys: (
value: Map<string, SymmetricCryptoKey>, value: { [orgId: string]: EncryptedOrganizationKeyStore },
options?: StorageOptions options?: StorageOptions
) => Promise<void>; ) => Promise<void>;
getEncryptedPasswordGenerationHistory: ( getEncryptedPasswordGenerationHistory: (

View File

@@ -3,5 +3,6 @@ export enum StateVersion {
Two = 2, // Move to a typed State object Two = 2, // Move to a typed State object
Three = 3, // Fix migration of users' premium status Three = 3, // Fix migration of users' premium status
Four = 4, // Fix 'Never Lock' option by removing stale data Four = 4, // Fix 'Never Lock' option by removing stale data
Latest = Four, Five = 5,
Latest = Five,
} }

View File

@@ -0,0 +1,4 @@
export interface EncryptedOrganizationKeyStore {
key: string;
providerId?: string;
}

View File

@@ -1,6 +1,7 @@
import { AuthenticationStatus } from "../../enums/authenticationStatus"; import { AuthenticationStatus } from "../../enums/authenticationStatus";
import { KdfType } from "../../enums/kdfType"; import { KdfType } from "../../enums/kdfType";
import { UriMatchType } from "../../enums/uriMatchType"; import { UriMatchType } from "../../enums/uriMatchType";
import { EncryptedOrganizationKeyStore } from "../../interfaces/encryptedOrganizationKeyStore";
import { CipherData } from "../data/cipherData"; import { CipherData } from "../data/cipherData";
import { CollectionData } from "../data/collectionData"; import { CollectionData } from "../data/collectionData";
import { EventData } from "../data/eventData"; import { EventData } from "../data/eventData";
@@ -20,6 +21,7 @@ import { GeneratedPasswordHistory } from "./generatedPasswordHistory";
import { Policy } from "./policy"; import { Policy } from "./policy";
import { SymmetricCryptoKey } from "./symmetricCryptoKey"; import { SymmetricCryptoKey } from "./symmetricCryptoKey";
export class EncryptionPair<TEncrypted, TDecrypted> { export class EncryptionPair<TEncrypted, TDecrypted> {
encrypted?: TEncrypted; encrypted?: TEncrypted;
decrypted?: TDecrypted; decrypted?: TDecrypted;
@@ -65,8 +67,11 @@ export class AccountKeys {
string, string,
SymmetricCryptoKey SymmetricCryptoKey
>(); >();
organizationKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair< organizationKeys?: EncryptionPair<
any, { [orgId: string]: EncryptedOrganizationKeyStore },
Map<string, SymmetricCryptoKey>
> = new EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyStore },
Map<string, SymmetricCryptoKey> Map<string, SymmetricCryptoKey>
>(); >();
providerKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair< providerKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<

View File

@@ -1,5 +1,7 @@
import * as bigInt from "big-integer"; import * as bigInt from "big-integer";
import { EncryptedOrganizationKeyStore } from 'jslib-common/interfaces/encryptedOrganizationKeyStore';
import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service"; import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service";
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
import { LogService } from "../abstractions/log.service"; import { LogService } from "../abstractions/log.service";
@@ -59,20 +61,22 @@ export class CryptoService implements CryptoServiceAbstraction {
orgs: ProfileOrganizationResponse[], orgs: ProfileOrganizationResponse[],
providerOrgs: ProfileProviderOrganizationResponse[] providerOrgs: ProfileProviderOrganizationResponse[]
): Promise<void> { ): Promise<void> {
const orgKeys: any = {}; const orgKeyStore: { [orgId: string]: EncryptedOrganizationKeyStore } = {};
orgs.forEach((org) => { orgs.forEach((org) => {
orgKeys[org.id] = org.key; orgKeyStore[org.id] = { key: org.key };
}); });
for (const providerOrg of providerOrgs) { for (const providerOrg of providerOrgs) {
// Convert provider encrypted keys to user encrypted. // Store providerId with the key later so it can be decrypted using the provider's key
const providerKey = await this.getProviderKey(providerOrg.providerId); // We can't decrypt it yet because we may not have all the required encryption keys available
const decValue = await this.decryptToBytes(new EncString(providerOrg.key), providerKey); orgKeyStore[providerOrg.id] = {
orgKeys[providerOrg.id] = (await this.rsaEncrypt(decValue)).encryptedString; key: providerOrg.key,
providerId: providerOrg.providerId,
}
} }
await this.stateService.setDecryptedOrganizationKeys(null); await this.stateService.setDecryptedOrganizationKeys(null);
return await this.stateService.setEncryptedOrganizationKeys(orgKeys); return await this.stateService.setEncryptedOrganizationKeys(orgKeyStore);
} }
async setProviderKeys(providers: ProfileProviderResponse[]): Promise<void> { async setProviderKeys(providers: ProfileProviderResponse[]): Promise<void> {
@@ -215,20 +219,35 @@ export class CryptoService implements CryptoServiceAbstraction {
return decryptedOrganizationKeys; return decryptedOrganizationKeys;
} }
const encOrgKeys = await this.stateService.getEncryptedOrganizationKeys(); const encOrgKeyStore = await this.stateService.getEncryptedOrganizationKeys();
if (encOrgKeys == null) { if (encOrgKeyStore == null) {
return null; return null;
} }
let setKey = false; let setKey = false;
for (const orgId in encOrgKeys) { for (const orgId in encOrgKeyStore) {
// eslint-disable-next-line // eslint-disable-next-line
if (!encOrgKeys.hasOwnProperty(orgId)) { if (!encOrgKeyStore.hasOwnProperty(orgId)) {
continue; continue;
} }
const decValue = await this.rsaDecrypt(encOrgKeys[orgId]); if (orgKeys.has(orgId)) {
continue;
}
const encOrgKey = encOrgKeyStore[orgId].key;
const providerId = encOrgKeyStore[orgId].providerId;
let decValue = null;
if (providerId == null) {
// User encrypted
decValue = await this.rsaDecrypt(encOrgKey);
} else {
// Provider encrypted
const providerKey = await this.getProviderKey(providerId);
decValue = await this.decryptToBytes(new EncString(encOrgKey), providerKey);
}
orgKeys.set(orgId, new SymmetricCryptoKey(decValue)); orgKeys.set(orgId, new SymmetricCryptoKey(decValue));
setKey = true; setKey = true;
} }

View File

@@ -1,5 +1,7 @@
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { EncryptedOrganizationKeyStore } from "jslib-common/interfaces/encryptedOrganizationKeyStore";
import { LogService } from "../abstractions/log.service"; import { LogService } from "../abstractions/log.service";
import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
import { StateMigrationService } from "../abstractions/stateMigration.service"; import { StateMigrationService } from "../abstractions/stateMigration.service";
@@ -1213,14 +1215,16 @@ export class StateService<
); );
} }
async getEncryptedOrganizationKeys(options?: StorageOptions): Promise<any> { async getEncryptedOrganizationKeys(
options?: StorageOptions
): Promise<{ [orgId: string]: EncryptedOrganizationKeyStore }> {
return ( return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.keys?.organizationKeys.encrypted; )?.keys?.organizationKeys.encrypted;
} }
async setEncryptedOrganizationKeys( async setEncryptedOrganizationKeys(
value: Map<string, SymmetricCryptoKey>, value: { [orgId: string]: EncryptedOrganizationKeyStore },
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(

View File

@@ -155,6 +155,9 @@ export class StateMigrationService<
case StateVersion.Three: case StateVersion.Three:
await this.migrateStateFrom3To4(); await this.migrateStateFrom3To4();
break; break;
case StateVersion.Four:
await this.migrateStateFrom4To5();
break;
} }
currentStateVersion += 1; currentStateVersion += 1;
@@ -488,6 +491,19 @@ export class StateMigrationService<
await this.set(keys.global, globals); await this.set(keys.global, globals);
} }
protected async migrateStateFrom4To5(): Promise<void> {
const authenticatedUserIds = await this.get<string[]>(keys.authenticatedAccounts);
for (const userId in authenticatedUserIds) {
const account = await this.get<TAccount>(userId);
const encryptedOrgKeys = account.keys.organizationKeys?.encrypted;
if (encryptedOrgKeys != null) {
// TODO, iterate through KVPs and update value
}
}
await this.setCurrentStateVersion(StateVersion.Five);
}
protected get options(): StorageOptions { protected get options(): StorageOptions {
return { htmlStorageLocation: HtmlStorageLocation.Local }; return { htmlStorageLocation: HtmlStorageLocation.Local };
} }
@@ -510,4 +526,10 @@ export class StateMigrationService<
protected async getCurrentStateVersion(): Promise<StateVersion> { protected async getCurrentStateVersion(): Promise<StateVersion> {
return (await this.getGlobals())?.stateVersion ?? StateVersion.One; return (await this.getGlobals())?.stateVersion ?? StateVersion.One;
} }
protected async setCurrentStateVersion(newVersion: StateVersion): Promise<void> {
const globals = await this.getGlobals();
globals.stateVersion = newVersion;
await this.set(keys.global, globals);
}
} }