From bff18a8cd24d54f1859953a6d433f42a1fe66179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 4 Sep 2025 19:37:40 +0200 Subject: [PATCH] [PM-25131] Initialize provider keys on the SDK (#16183) * [PM-25131] Initialize provider keys on the SDK * Remove null default * Typechecking --- .../services/sdk/default-sdk.service.ts | 9 +-- .../src/abstractions/key.service.ts | 5 +- libs/key-management/src/key.service.ts | 55 +++++++++++++++++-- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 210e1bdc59..2713aaf8f4 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -27,10 +27,9 @@ import { UnsignedSharedKey, } from "@bitwarden/sdk-internal"; -import { EncryptedOrganizationKeyData } from "../../../admin-console/models/data/encrypted-organization-key.data"; import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; import { DeviceType } from "../../../enums/device-type.enum"; -import { EncryptedString } from "../../../key-management/crypto/models/enc-string"; +import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { OrganizationId, UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; @@ -220,7 +219,7 @@ export class DefaultSdkService implements SdkService { kdfParams: KdfConfig, privateKey: EncryptedString, userKey: UserKey, - orgKeys: Record | null, + orgKeys: Record, ) { await client.crypto().initialize_user_crypto({ userId: asUuid(userId), @@ -245,9 +244,7 @@ export class DefaultSdkService implements SdkService { // null to make sure any existing org keys are cleared. await client.crypto().initialize_org_crypto({ organizationKeys: new Map( - Object.entries(orgKeys ?? {}) - .filter(([_, v]) => v.type === "organization") - .map(([k, v]) => [asUuid(k), v.key as UnsignedSharedKey]), + Object.entries(orgKeys).map(([k, v]) => [asUuid(k), v.toJSON() as UnsignedSharedKey]), ), }); diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index 1685938de3..0f9618cbab 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -1,6 +1,5 @@ import { Observable } from "rxjs"; -import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; import { ProfileOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-organization.response"; import { ProfileProviderOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-provider-organization.response"; import { ProfileProviderResponse } from "@bitwarden/common/admin-console/models/response/profile-provider.response"; @@ -406,9 +405,7 @@ export abstract class KeyService { * @deprecated Temporary function to allow the SDK to be initialized after the login process, it * will be removed when auth has been migrated to the SDK. */ - abstract encryptedOrgKeys$( - userId: UserId, - ): Observable | null>; + abstract encryptedOrgKeys$(userId: UserId): Observable>; /** * Gets an observable stream of the users public key. If the user is does not have diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index ed0b844a2a..53f7c6ed15 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -907,10 +907,57 @@ export class DefaultKeyService implements KeyServiceAbstraction { return this.cipherDecryptionKeys$(userId).pipe(map((keys) => keys?.orgKeys ?? null)); } - encryptedOrgKeys$( - userId: UserId, - ): Observable | null> { - return this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$; + encryptedOrgKeys$(userId: UserId): Observable> { + return this.userPrivateKey$(userId)?.pipe( + switchMap((userPrivateKey) => { + if (userPrivateKey == null) { + // We can't do any org based decryption + return of({}); + } + + return combineLatest([ + this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$, + this.providerKeysHelper$(userId, userPrivateKey), + ]).pipe( + switchMap(async ([encryptedOrgKeys, providerKeys]) => { + const userPubKey = await this.derivePublicKey(userPrivateKey); + + const result: Record = {}; + encryptedOrgKeys = encryptedOrgKeys ?? {}; + for (const orgId of Object.keys(encryptedOrgKeys) as OrganizationId[]) { + if (result[orgId] != null) { + continue; + } + const encrypted = BaseEncryptedOrganizationKey.fromData(encryptedOrgKeys[orgId]); + if (encrypted == null) { + continue; + } + + let orgKey: EncString; + + // Because the SDK only supports user encrypted org keys, we need to re-encrypt + // any provider encrypted org keys with the user's public key. This should be removed + // once the SDK has support for provider keys. + if (BaseEncryptedOrganizationKey.isProviderEncrypted(encrypted)) { + if (providerKeys == null) { + continue; + } + orgKey = await this.encryptService.encapsulateKeyUnsigned( + await encrypted.decrypt(this.encryptService, providerKeys!), + userPubKey!, + ); + } else { + orgKey = encrypted.encryptedOrganizationKey; + } + + result[orgId] = orgKey; + } + + return result; + }), + ); + }), + ); } cipherDecryptionKeys$(userId: UserId): Observable {