From d01b19f0c189402872c474311ffbf8ae166a0fa0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 20 Jan 2026 16:00:12 +0100 Subject: [PATCH] Set userkey to state from SDK --- .../src/key-management/user-key-mapper.ts | 21 +++++++++++++++++ .../services/key-state/user-key.state.ts | 4 +++- .../services/sdk/client-managed-state.ts | 9 ++++++-- libs/key-management/src/key.service.ts | 23 +++++++++++++++---- 4 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 libs/common/src/key-management/user-key-mapper.ts diff --git a/libs/common/src/key-management/user-key-mapper.ts b/libs/common/src/key-management/user-key-mapper.ts new file mode 100644 index 00000000000..1a622f878bc --- /dev/null +++ b/libs/common/src/key-management/user-key-mapper.ts @@ -0,0 +1,21 @@ +import { SdkRecordMapper } from "@bitwarden/common/platform/services/sdk/client-managed-state"; +import { UserKeyDefinition } from "@bitwarden/common/platform/state"; +import { UserKeyState } from "@bitwarden/sdk-internal"; + +import { USER_KEY } from "../platform/services/key-state/user-key.state"; +import { UserKey } from "../types/key"; +import { SymmetricCryptoKey } from "../platform/models/domain/symmetric-crypto-key"; + +export class UserKeyRecordMapper implements SdkRecordMapper { + userKeyDefinition(): UserKeyDefinition> { + return USER_KEY; + } + + toSdk(value: UserKey): UserKeyState { + return { decrypted_user_key: value.toBase64() } as UserKeyState; + } + + fromSdk(value: UserKeyState): UserKey { + return SymmetricCryptoKey.fromString(value.decrypted_user_key) as UserKey; + } +} diff --git a/libs/common/src/platform/services/key-state/user-key.state.ts b/libs/common/src/platform/services/key-state/user-key.state.ts index 64577768c8d..a87f24f37f5 100644 --- a/libs/common/src/platform/services/key-state/user-key.state.ts +++ b/libs/common/src/platform/services/key-state/user-key.state.ts @@ -3,6 +3,7 @@ import { SignedPublicKey, WrappedSigningKey } from "../../../key-management/type import { UserKey } from "../../../types/key"; import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; import { CRYPTO_DISK, CRYPTO_MEMORY, UserKeyDefinition } from "../../state"; +import { record } from "@bitwarden/serialization"; export const USER_EVER_HAD_USER_KEY = new UserKeyDefinition( CRYPTO_DISK, @@ -22,7 +23,8 @@ export const USER_ENCRYPTED_PRIVATE_KEY = new UserKeyDefinition }, ); -export const USER_KEY = new UserKeyDefinition(CRYPTO_MEMORY, "userKey", { +// This is a map with a single entry to conform with the repository pattern in the SDK. +export const USER_KEY = UserKeyDefinition.record(CRYPTO_MEMORY, "userKey", { deserializer: (obj) => SymmetricCryptoKey.fromJSON(obj) as UserKey, clearOn: ["logout", "lock"], }); diff --git a/libs/common/src/platform/services/sdk/client-managed-state.ts b/libs/common/src/platform/services/sdk/client-managed-state.ts index 1e3273d0801..7e3a0d1e9df 100644 --- a/libs/common/src/platform/services/sdk/client-managed-state.ts +++ b/libs/common/src/platform/services/sdk/client-managed-state.ts @@ -5,14 +5,19 @@ import { CipherRecordMapper } from "@bitwarden/common/vault/models/domain/cipher import { StateClient, Repository } from "@bitwarden/sdk-internal"; import { StateProvider, UserKeyDefinition } from "../../state"; +import { UserKeyRecordMapper } from "@bitwarden/common/key-management/user-key-mapper"; export async function initializeState( userId: UserId, stateClient: StateClient, stateProvider: StateProvider, ): Promise { - await stateClient.register_cipher_repository( - new RepositoryRecord(userId, stateProvider, new CipherRecordMapper()), + stateClient.register_client_managed_repositories( + { + cipher: new RepositoryRecord(userId, stateProvider, new CipherRecordMapper()), + folder: null, + user_key_state: new RepositoryRecord(userId, stateProvider, new UserKeyRecordMapper()), + } ); } diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 4c749e9f6c4..d849002b141 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -108,7 +108,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { } // Set userId to ensure we have one for the account status update - await this.stateProvider.setUserState(USER_KEY, key, userId); + await this.stateProvider.setUserState(USER_KEY, this.userKeyToStateObject(key), userId); await this.stateProvider.setUserState(USER_EVER_HAD_USER_KEY, true, userId); await this.storeAdditionalKeys(key, userId); @@ -165,7 +165,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { } getInMemoryUserKeyFor$(userId: UserId): Observable { - return this.stateProvider.getUserState$(USER_KEY, userId); + return this.stateProvider.getUserState$(USER_KEY, userId).pipe(map(userKey => this.stateObjectToUserKey(userKey))); } /** @@ -173,7 +173,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { */ async getUserKey(userId?: UserId): Promise { const userKey = await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId)); - return userKey; + return this.stateObjectToUserKey(userKey); } async getUserKeyFromStorage( @@ -751,7 +751,8 @@ export class DefaultKeyService implements KeyServiceAbstraction { } userKey$(userId: UserId): Observable { - return this.stateProvider.getUser(userId, USER_KEY).state$; + return this.stateProvider.getUser(userId, USER_KEY).state$ + .pipe(map((key) => (key != null ? (key[""] as UserKey) : null))); } userPublicKey$(userId: UserId) { @@ -1016,4 +1017,18 @@ export class DefaultKeyService implements KeyServiceAbstraction { userSignedPublicKey$(userId: UserId): Observable { return this.stateProvider.getUserState$(USER_SIGNED_PUBLIC_KEY, userId); } + + private userKeyToStateObject(userKey: UserKey | null): Record | null { + if (userKey == null) { + return null; + } + return { "": userKey }; + } + + private stateObjectToUserKey(stateObject: Record | null): UserKey | null { + if (stateObject == null) { + return null; + } + return stateObject[""] ?? null; + } }