diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index 168dbe7442e..4c56ee6d31c 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -10,6 +10,7 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-st import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service"; import { + SignedPublicKey, SignedSecurityState, UnsignedPublicKey, WrappedPrivateKey, @@ -308,9 +309,11 @@ export class UserKeyRotationService { userId: asUuid(userId), kdfParams: kdfConfig.toSdkConfig(), email: email, - privateKey: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, - signingKey: undefined, - securityState: undefined, + accountCryptographicState: { + V1: { + private_key: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, + }, + }, method: { decryptedKey: { decrypted_user_key: cryptographicStateParameters.userKey.toBase64() }, }, @@ -334,9 +337,15 @@ export class UserKeyRotationService { userId: asUuid(userId), kdfParams: kdfConfig.toSdkConfig(), email: email, - privateKey: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, - signingKey: cryptographicStateParameters.signingKey, - securityState: cryptographicStateParameters.securityState, + accountCryptographicState: { + V2: { + private_key: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, + signing_key: cryptographicStateParameters.signingKey, + security_state: cryptographicStateParameters.securityState, + signed_public_key: + cryptographicStateParameters.publicKeyEncryptionKeyPair.signedPublicKey, + }, + }, method: { decryptedKey: { decrypted_user_key: cryptographicStateParameters.userKey.toBase64() }, }, @@ -632,6 +641,10 @@ export class UserKeyRotationService { this.securityStateService.accountSecurityState$(user.id), "User security state", ); + const signedPublicKey = await this.firstValueFromOrThrow( + this.keyService.userSignedPublicKey$(user.id), + "User signed public key", + ); return { masterKeyKdfConfig, @@ -642,6 +655,7 @@ export class UserKeyRotationService { publicKeyEncryptionKeyPair: { wrappedPrivateKey: currentUserKeyWrappedPrivateKey, publicKey: publicKey, + signedPublicKey: signedPublicKey, }, signingKey: signingKey!, securityState: securityState!, @@ -679,6 +693,7 @@ export type V2CryptographicStateParameters = { publicKeyEncryptionKeyPair: { wrappedPrivateKey: WrappedPrivateKey; publicKey: UnsignedPublicKey; + signedPublicKey: SignedPublicKey; }; signingKey: WrappedSigningKey; securityState: SignedSecurityState; diff --git a/libs/common/src/key-management/types.ts b/libs/common/src/key-management/types.ts index df64a3ed342..1c349e83a03 100644 --- a/libs/common/src/key-management/types.ts +++ b/libs/common/src/key-management/types.ts @@ -1,6 +1,10 @@ import { Opaque } from "type-fest"; -import { EncString, SignedSecurityState as SdkSignedSecurityState } from "@bitwarden/sdk-internal"; +import { + EncString, + SignedSecurityState as SdkSignedSecurityState, + SignedPublicKey as SdkSignedPublicKey, +} from "@bitwarden/sdk-internal"; /** * A private key, encrypted with a symmetric key. @@ -10,7 +14,7 @@ export type WrappedPrivateKey = Opaque; /** * A public key, signed with the accounts signature key. */ -export type SignedPublicKey = Opaque; +export type SignedPublicKey = Opaque; /** * A public key in base64 encoded SPKI-DER */ 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 2416c211d6b..64577768c8d 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 @@ -1,5 +1,5 @@ import { EncryptedString } from "../../../key-management/crypto/models/enc-string"; -import { WrappedSigningKey } from "../../../key-management/types"; +import { SignedPublicKey, WrappedSigningKey } from "../../../key-management/types"; import { UserKey } from "../../../types/key"; import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; import { CRYPTO_DISK, CRYPTO_MEMORY, UserKeyDefinition } from "../../state"; @@ -35,3 +35,12 @@ export const USER_KEY_ENCRYPTED_SIGNING_KEY = new UserKeyDefinition( + CRYPTO_DISK, + "userSignedPublicKey", + { + deserializer: (obj) => obj, + clearOn: ["logout"], + }, +); 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 6f9c9df761c..52dcec7b154 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -16,6 +16,7 @@ import { } from "rxjs"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { UserKey } from "@bitwarden/common/types/key"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; @@ -25,16 +26,15 @@ import { DeviceType as SdkDeviceType, TokenProvider, UnsignedSharedKey, + WrappedUserAccountCryptographicState, } from "@bitwarden/sdk-internal"; import { ApiService } from "../../../abstractions/api.service"; import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; import { DeviceType } from "../../../enums/device-type.enum"; -import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; +import { EncString } from "../../../key-management/crypto/models/enc-string"; import { SecurityStateService } from "../../../key-management/security-state/abstractions/security-state.service"; -import { SignedSecurityState, WrappedSigningKey } from "../../../key-management/types"; import { OrganizationId, UserId } from "../../../types/guid"; -import { UserKey } from "../../../types/key"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; @@ -171,6 +171,9 @@ export class DefaultSdkService implements SdkService { const securityState$ = this.securityStateService .accountSecurityState$(userId) .pipe(distinctUntilChanged(compareValues)); + const signedPublicKey$ = this.keyService + .userSignedPublicKey$(userId) + .pipe(distinctUntilChanged(compareValues)); const client$ = combineLatest([ this.environmentService.getEnvironment$(userId), @@ -181,11 +184,22 @@ export class DefaultSdkService implements SdkService { signingKey$, orgKeys$, securityState$, + signedPublicKey$, SdkLoadService.Ready, // Makes sure we wait (once) for the SDK to be loaded ]).pipe( // switchMap is required to allow the clean-up logic to be executed when `combineLatest` emits a new value. switchMap( - ([env, account, kdfParams, privateKey, userKey, signingKey, orgKeys, securityState]) => { + ([ + env, + account, + kdfParams, + privateKey, + userKey, + signingKey, + orgKeys, + securityState, + signedPublicKey, + ]) => { // Create our own observable to be able to implement clean-up logic return new Observable>((subscriber) => { const createAndInitializeClient = async () => { @@ -199,15 +213,31 @@ export class DefaultSdkService implements SdkService { settings, ); + let accountCryptographicState: WrappedUserAccountCryptographicState; + if (signingKey != null && securityState != null && signedPublicKey != null) { + accountCryptographicState = { + V2: { + private_key: privateKey, + signing_key: signingKey, + security_state: securityState, + signed_public_key: signedPublicKey, + }, + }; + } else { + accountCryptographicState = { + V1: { + private_key: privateKey, + }, + }; + } + await this.initializeClient( userId, client, account, kdfParams, - privateKey, userKey, - signingKey, - securityState, + accountCryptographicState, orgKeys, ); @@ -242,10 +272,8 @@ export class DefaultSdkService implements SdkService { client: BitwardenClient, account: AccountInfo, kdfParams: KdfConfig, - privateKey: EncryptedString, userKey: UserKey, - signingKey: WrappedSigningKey | null, - securityState: SignedSecurityState | null, + accountCryptographicState: WrappedUserAccountCryptographicState, orgKeys: Record, ) { await client.crypto().initialize_user_crypto({ @@ -262,9 +290,7 @@ export class DefaultSdkService implements SdkService { parallelism: kdfParams.parallelism, }, }, - privateKey, - signingKey: signingKey || undefined, - securityState: securityState || undefined, + accountCryptographicState: accountCryptographicState, }); // We initialize the org crypto even if the org_keys are diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index e599fbc1c48..910702bddd0 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -253,6 +253,10 @@ export class DefaultSyncService extends CoreSyncService { response.accountKeys.securityState.securityState, response.id, ); + await this.keyService.setSignedPublicKey( + response.accountKeys.publicKeyEncryptionKeyPair.signedPublicKey, + response.id, + ); } } else { await this.keyService.setPrivateKey(response.privateKey, response.id); diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index f86ca922c9e..feb4a38ac27 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -7,7 +7,7 @@ import { EncryptedString, EncString, } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { WrappedSigningKey } from "@bitwarden/common/key-management/types"; +import { SignedPublicKey, WrappedSigningKey } from "@bitwarden/common/key-management/types"; import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid"; @@ -428,4 +428,8 @@ export abstract class KeyService { * @param userId The user id for the key */ abstract validateUserKey(key: UserKey, userId: UserId): Promise; + + abstract setSignedPublicKey(signedPublicKey: SignedPublicKey, userId: UserId): Promise; + + abstract userSignedPublicKey$(userId: UserId): Observable; } diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 2701109fde2..713ab234c03 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -27,7 +27,7 @@ import { EncryptedString, } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { WrappedSigningKey } from "@bitwarden/common/key-management/types"; +import { SignedPublicKey, WrappedSigningKey } from "@bitwarden/common/key-management/types"; import { VaultTimeoutStringType } from "@bitwarden/common/key-management/vault-timeout"; import { VAULT_TIMEOUT } from "@bitwarden/common/key-management/vault-timeout/services/vault-timeout-settings.state"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -45,6 +45,7 @@ import { USER_EVER_HAD_USER_KEY, USER_KEY, USER_KEY_ENCRYPTED_SIGNING_KEY, + USER_SIGNED_PUBLIC_KEY, } from "@bitwarden/common/platform/services/key-state/user-key.state"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CsprngArray } from "@bitwarden/common/types/csprng"; @@ -1005,4 +1006,12 @@ export class DefaultKeyService implements KeyServiceAbstraction { }), ); } + + async setSignedPublicKey(signedPublicKey: SignedPublicKey, userId: UserId): Promise { + await this.stateProvider.setUserState(USER_SIGNED_PUBLIC_KEY, signedPublicKey, userId); + } + + userSignedPublicKey$(userId: UserId): Observable { + return this.stateProvider.getUserState$(USER_SIGNED_PUBLIC_KEY, userId); + } }