1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-10 13:23:15 +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 { ThemeType } from "../enums/themeType";
import { UriMatchType } from "../enums/uriMatchType";
import { EncryptedOrganizationKeyStore } from "../interfaces/encryptedOrganizationKeyStore";
import { CipherData } from "../models/data/cipherData";
import { CollectionData } from "../models/data/collectionData";
import { EventData } from "../models/data/eventData";
@@ -23,7 +24,6 @@ import { CipherView } from "../models/view/cipherView";
import { CollectionView } from "../models/view/collectionView";
import { FolderView } from "../models/view/folderView";
import { SendView } from "../models/view/sendView";
export abstract class StateService<T extends Account = Account> {
accounts: BehaviorSubject<{ [userId: string]: T }>;
activeAccount: BehaviorSubject<string>;
@@ -184,9 +184,11 @@ export abstract class StateService<T extends Account = Account> {
value: { [id: string]: FolderData },
options?: StorageOptions
) => Promise<void>;
getEncryptedOrganizationKeys: (options?: StorageOptions) => Promise<any>;
getEncryptedOrganizationKeys: (
options?: StorageOptions
) => Promise<{ [orgId: string]: EncryptedOrganizationKeyStore }>;
setEncryptedOrganizationKeys: (
value: Map<string, SymmetricCryptoKey>,
value: { [orgId: string]: EncryptedOrganizationKeyStore },
options?: StorageOptions
) => Promise<void>;
getEncryptedPasswordGenerationHistory: (

View File

@@ -3,5 +3,6 @@ export enum StateVersion {
Two = 2, // Move to a typed State object
Three = 3, // Fix migration of users' premium status
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 { KdfType } from "../../enums/kdfType";
import { UriMatchType } from "../../enums/uriMatchType";
import { EncryptedOrganizationKeyStore } from "../../interfaces/encryptedOrganizationKeyStore";
import { CipherData } from "../data/cipherData";
import { CollectionData } from "../data/collectionData";
import { EventData } from "../data/eventData";
@@ -20,6 +21,7 @@ import { GeneratedPasswordHistory } from "./generatedPasswordHistory";
import { Policy } from "./policy";
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
export class EncryptionPair<TEncrypted, TDecrypted> {
encrypted?: TEncrypted;
decrypted?: TDecrypted;
@@ -65,8 +67,11 @@ export class AccountKeys {
string,
SymmetricCryptoKey
>();
organizationKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<
any,
organizationKeys?: EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyStore },
Map<string, SymmetricCryptoKey>
> = new EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyStore },
Map<string, SymmetricCryptoKey>
>();
providerKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<

View File

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

View File

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

View File

@@ -155,6 +155,9 @@ export class StateMigrationService<
case StateVersion.Three:
await this.migrateStateFrom3To4();
break;
case StateVersion.Four:
await this.migrateStateFrom4To5();
break;
}
currentStateVersion += 1;
@@ -488,6 +491,19 @@ export class StateMigrationService<
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 {
return { htmlStorageLocation: HtmlStorageLocation.Local };
}
@@ -510,4 +526,10 @@ export class StateMigrationService<
protected async getCurrentStateVersion(): Promise<StateVersion> {
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);
}
}