1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-12-05 09:14:28 +01:00
This commit is contained in:
Bernd Schoolmann 2025-12-05 01:32:00 +01:00 committed by GitHub
commit 7810e4fe60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 227 additions and 3 deletions

View File

@ -85,6 +85,8 @@ import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/bill
import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service";
import { ClientType } from "@bitwarden/common/enums";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service";
import {
DefaultKeyGenerationService,
KeyGenerationService,
@ -452,6 +454,7 @@ export default class MainBackground {
syncServiceListener: SyncServiceListener;
browserInitialInstallService: BrowserInitialInstallService;
backgroundSyncService: BackgroundSyncService;
accountCryptographicStateService: AccountCryptographicStateService;
webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService;
themeStateService: DefaultThemeStateService;
@ -1004,6 +1007,9 @@ export default class MainBackground {
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.providerService = new ProviderService(this.stateProvider);
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
this.stateProvider,
);
this.syncService = new DefaultSyncService(
this.masterPasswordService,
this.accountService,
@ -1031,6 +1037,7 @@ export default class MainBackground {
this.stateProvider,
this.securityStateService,
this.kdfConfigService,
this.accountCryptographicStateService,
);
this.syncServiceListener = new SyncServiceListener(

View File

@ -69,6 +69,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service";
import { ClientType } from "@bitwarden/common/enums";
import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service";
import {
DefaultKeyGenerationService,
KeyGenerationService,
@ -333,6 +334,7 @@ export class ServiceContainer {
masterPasswordUnlockService: MasterPasswordUnlockService;
cipherArchiveService: CipherArchiveService;
lockService: LockService;
private accountCryptographicStateService: DefaultAccountCryptographicStateService;
constructor() {
let p = null;
@ -716,6 +718,10 @@ export class ServiceContainer {
this.accountService,
);
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
this.stateProvider,
);
this.loginStrategyService = new LoginStrategyService(
this.accountService,
this.masterPasswordService,
@ -743,6 +749,7 @@ export class ServiceContainer {
this.kdfConfigService,
this.taskSchedulerService,
this.configService,
this.accountCryptographicStateService,
);
this.restrictedItemTypesService = new RestrictedItemTypesService(
@ -878,6 +885,7 @@ export class ServiceContainer {
this.stateProvider,
this.securityStateService,
this.kdfConfigService,
this.accountCryptographicStateService,
);
this.totpService = new TotpService(this.sdkService);

View File

@ -55,6 +55,7 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
import { ClientType } from "@bitwarden/common/enums";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
@ -404,6 +405,7 @@ const safeProviders: SafeProvider[] = [
OrganizationUserApiService,
InternalUserDecryptionOptionsServiceAbstraction,
MessagingServiceAbstraction,
AccountCryptographicStateService,
],
}),
safeProvider({

View File

@ -15,6 +15,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
@ -43,6 +44,7 @@ describe("DesktopSetInitialPasswordService", () => {
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let messagingService: MockProxy<MessagingService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
beforeEach(() => {
apiService = mock<ApiService>();
@ -56,6 +58,7 @@ describe("DesktopSetInitialPasswordService", () => {
organizationUserApiService = mock<OrganizationUserApiService>();
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
messagingService = mock<MessagingService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
sut = new DesktopSetInitialPasswordService(
apiService,
@ -69,6 +72,7 @@ describe("DesktopSetInitialPasswordService", () => {
organizationUserApiService,
userDecryptionOptionsService,
messagingService,
accountCryptographicStateService,
);
});

View File

@ -9,6 +9,7 @@ import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -32,6 +33,7 @@ export class DesktopSetInitialPasswordService
protected organizationUserApiService: OrganizationUserApiService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
private messagingService: MessagingService,
protected accountCryptographicStateService: AccountCryptographicStateService,
) {
super(
apiService,
@ -44,6 +46,7 @@ export class DesktopSetInitialPasswordService
organizationApiService,
organizationUserApiService,
userDecryptionOptionsService,
accountCryptographicStateService,
);
}

View File

@ -16,6 +16,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
@ -45,6 +46,7 @@ describe("WebSetInitialPasswordService", () => {
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let organizationInviteService: MockProxy<OrganizationInviteService>;
let routerService: MockProxy<RouterService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
beforeEach(() => {
apiService = mock<ApiService>();
@ -59,6 +61,7 @@ describe("WebSetInitialPasswordService", () => {
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
organizationInviteService = mock<OrganizationInviteService>();
routerService = mock<RouterService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
sut = new WebSetInitialPasswordService(
apiService,
@ -73,6 +76,7 @@ describe("WebSetInitialPasswordService", () => {
userDecryptionOptionsService,
organizationInviteService,
routerService,
accountCryptographicStateService,
);
});

View File

@ -10,6 +10,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -34,6 +35,7 @@ export class WebSetInitialPasswordService
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
private organizationInviteService: OrganizationInviteService,
private routerService: RouterService,
protected accountCryptographicStateService: AccountCryptographicStateService,
) {
super(
apiService,
@ -46,6 +48,7 @@ export class WebSetInitialPasswordService
organizationApiService,
organizationUserApiService,
userDecryptionOptionsService,
accountCryptographicStateService,
);
}

View File

@ -15,6 +15,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
@ -44,6 +45,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
protected organizationApiService: OrganizationApiServiceAbstraction,
protected organizationUserApiService: OrganizationUserApiService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
protected accountCryptographicStateService: AccountCryptographicStateService,
) {}
async setInitialPassword(
@ -162,6 +164,14 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
throw new Error("encrypted private key not found. Could not set private key in state.");
}
await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId);
await this.accountCryptographicStateService.setAccountCryptographicState(
{
V1: {
private_key: keyPair[1].encryptedString,
},
},
userId,
);
}
await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId);

View File

@ -20,6 +20,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import {
EncryptedString,
@ -56,6 +57,7 @@ describe("DefaultSetInitialPasswordService", () => {
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let userId: UserId;
let userKey: UserKey;
@ -73,6 +75,7 @@ describe("DefaultSetInitialPasswordService", () => {
organizationApiService = mock<OrganizationApiServiceAbstraction>();
organizationUserApiService = mock<OrganizationUserApiService>();
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
userId = "userId" as UserId;
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
@ -90,6 +93,7 @@ describe("DefaultSetInitialPasswordService", () => {
organizationApiService,
organizationUserApiService,
userDecryptionOptionsService,
accountCryptographicStateService,
);
});

View File

@ -168,6 +168,8 @@ import { OrganizationBillingService } from "@bitwarden/common/billing/services/o
import { DefaultSubscriptionPricingService } from "@bitwarden/common/billing/services/subscription-pricing.service";
import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service";
import {
DefaultKeyGenerationService,
KeyGenerationService,
@ -569,6 +571,7 @@ const safeProviders: SafeProvider[] = [
KdfConfigService,
TaskSchedulerService,
ConfigService,
AccountCryptographicStateService,
],
}),
safeProvider({
@ -891,8 +894,14 @@ const safeProviders: SafeProvider[] = [
StateProvider,
SecurityStateService,
KdfConfigService,
AccountCryptographicStateService,
],
}),
safeProvider({
provide: AccountCryptographicStateService,
useClass: DefaultAccountCryptographicStateService,
deps: [StateProvider],
}),
safeProvider({
provide: BroadcasterService,
useClass: DefaultBroadcasterService,
@ -1564,6 +1573,7 @@ const safeProviders: SafeProvider[] = [
OrganizationApiServiceAbstraction,
OrganizationUserApiService,
InternalUserDecryptionOptionsServiceAbstraction,
AccountCryptographicStateService,
],
}),
safeProvider({

View File

@ -6,6 +6,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
@ -57,6 +58,7 @@ describe("AuthRequestLoginStrategy", () => {
let kdfConfigService: MockProxy<KdfConfigService>;
let environmentService: MockProxy<EnvironmentService>;
let configService: MockProxy<ConfigService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
@ -94,6 +96,7 @@ describe("AuthRequestLoginStrategy", () => {
kdfConfigService = mock<KdfConfigService>();
environmentService = mock<EnvironmentService>();
configService = mock<ConfigService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
accountService = mockAccountServiceWith(mockUserId);
masterPasswordService = new FakeMasterPasswordService();
@ -125,6 +128,7 @@ describe("AuthRequestLoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
tokenResponse = identityTokenResponseFactory();

View File

@ -128,6 +128,12 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
userId,
);
if (response.accountKeysResponseModel) {
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
userId,
);
}
}
exportCache(): CacheData {

View File

@ -17,6 +17,7 @@ import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/resp
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
@ -137,6 +138,7 @@ describe("LoginStrategy", () => {
let kdfConfigService: MockProxy<KdfConfigService>;
let environmentService: MockProxy<EnvironmentService>;
let configService: MockProxy<ConfigService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let passwordLoginStrategy: PasswordLoginStrategy;
let credentials: PasswordLoginCredentials;
@ -163,6 +165,7 @@ describe("LoginStrategy", () => {
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
environmentService = mock<EnvironmentService>();
configService = mock<ConfigService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
@ -193,6 +196,7 @@ describe("LoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
credentials = new PasswordLoginCredentials(email, masterPassword);
});
@ -522,6 +526,7 @@ describe("LoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
@ -583,6 +588,7 @@ describe("LoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
const result = await passwordLoginStrategy.logIn(credentials);

View File

@ -17,6 +17,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import {
@ -87,6 +88,7 @@ export abstract class LoginStrategy {
protected KdfConfigService: KdfConfigService,
protected environmentService: EnvironmentService,
protected configService: ConfigService,
protected accountCryptographicStateService: AccountCryptographicStateService,
) {}
abstract exportCache(): CacheData;

View File

@ -12,6 +12,7 @@ import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/respons
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
import {
@ -85,6 +86,7 @@ describe("PasswordLoginStrategy", () => {
let kdfConfigService: MockProxy<KdfConfigService>;
let environmentService: MockProxy<EnvironmentService>;
let configService: MockProxy<ConfigService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let passwordLoginStrategy: PasswordLoginStrategy;
let credentials: PasswordLoginCredentials;
@ -113,6 +115,7 @@ describe("PasswordLoginStrategy", () => {
kdfConfigService = mock<KdfConfigService>();
environmentService = mock<EnvironmentService>();
configService = mock<ConfigService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.decodeAccessToken.mockResolvedValue({
@ -153,6 +156,7 @@ describe("PasswordLoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
credentials = new PasswordLoginCredentials(email, masterPassword);
tokenResponse = identityTokenResponseFactory(masterPasswordPolicyResponse);

View File

@ -155,6 +155,12 @@ export class PasswordLoginStrategy extends LoginStrategy {
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
userId,
);
if (response.accountKeysResponseModel) {
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
userId,
);
}
}
protected override encryptionKeyMigrationRequired(response: IdentityTokenResponse): boolean {

View File

@ -10,6 +10,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
@ -70,6 +71,7 @@ describe("SsoLoginStrategy", () => {
let kdfConfigService: MockProxy<KdfConfigService>;
let environmentService: MockProxy<EnvironmentService>;
let configService: MockProxy<ConfigService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let ssoLoginStrategy: SsoLoginStrategy;
let credentials: SsoLoginCredentials;
@ -108,6 +110,7 @@ describe("SsoLoginStrategy", () => {
kdfConfigService = mock<KdfConfigService>();
environmentService = mock<EnvironmentService>();
configService = mock<ConfigService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
tokenService.getTwoFactorToken.mockResolvedValue(null);
appIdService.getAppId.mockResolvedValue(deviceId);
@ -162,6 +165,7 @@ describe("SsoLoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId);
});

View File

@ -339,6 +339,13 @@ export class SsoLoginStrategy extends LoginStrategy {
tokenResponse: IdentityTokenResponse,
userId: UserId,
): Promise<void> {
if (tokenResponse.accountKeysResponseModel) {
await this.accountCryptographicStateService.setAccountCryptographicState(
tokenResponse.accountKeysResponseModel.toWrappedAccountCryptographicState(),
userId,
);
}
if (tokenResponse.hasMasterKeyEncryptedUserKey()) {
// User has masterKeyEncryptedUserKey, so set the userKeyEncryptedPrivateKey
// Note: new JIT provisioned SSO users will not yet have a user asymmetric key pair

View File

@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
@ -58,6 +59,7 @@ describe("UserApiLoginStrategy", () => {
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let configService: MockProxy<ConfigService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let apiLogInStrategy: UserApiLoginStrategy;
let credentials: UserApiLoginCredentials;
@ -91,6 +93,7 @@ describe("UserApiLoginStrategy", () => {
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
kdfConfigService = mock<KdfConfigService>();
configService = mock<ConfigService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.getTwoFactorToken.mockResolvedValue(null);
@ -119,6 +122,7 @@ describe("UserApiLoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
credentials = new UserApiLoginCredentials(apiClientId, apiClientSecret);

View File

@ -87,6 +87,12 @@ export class UserApiLoginStrategy extends LoginStrategy {
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
userId,
);
if (response.accountKeysResponseModel) {
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
userId,
);
}
}
// Overridden to save client ID and secret to token service

View File

@ -9,6 +9,7 @@ import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/mod
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
import {
@ -56,6 +57,7 @@ describe("WebAuthnLoginStrategy", () => {
let kdfConfigService: MockProxy<KdfConfigService>;
let environmentService: MockProxy<EnvironmentService>;
let configService: MockProxy<ConfigService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let webAuthnLoginStrategy!: WebAuthnLoginStrategy;
@ -101,6 +103,7 @@ describe("WebAuthnLoginStrategy", () => {
kdfConfigService = mock<KdfConfigService>();
environmentService = mock<EnvironmentService>();
configService = mock<ConfigService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
tokenService.getTwoFactorToken.mockResolvedValue(null);
appIdService.getAppId.mockResolvedValue(deviceId);
@ -128,6 +131,7 @@ describe("WebAuthnLoginStrategy", () => {
kdfConfigService,
environmentService,
configService,
accountCryptographicStateService,
);
// Create credentials

View File

@ -107,6 +107,12 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
userId,
);
if (response.accountKeysResponseModel) {
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
userId,
);
}
}
exportCache(): CacheData {

View File

@ -13,6 +13,7 @@ import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogi
import { UserDecryptionOptionsResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
@ -84,6 +85,7 @@ describe("LoginStrategyService", () => {
let kdfConfigService: MockProxy<KdfConfigService>;
let taskSchedulerService: MockProxy<TaskSchedulerService>;
let configService: MockProxy<ConfigService>;
let accountCryptographicStateService: MockProxy<DefaultAccountCryptographicStateService>;
let stateProvider: FakeGlobalStateProvider;
let loginStrategyCacheExpirationState: FakeGlobalState<Date | null>;
@ -117,6 +119,7 @@ describe("LoginStrategyService", () => {
kdfConfigService = mock<KdfConfigService>();
taskSchedulerService = mock<TaskSchedulerService>();
configService = mock<ConfigService>();
accountCryptographicStateService = mock<DefaultAccountCryptographicStateService>();
sut = new LoginStrategyService(
accountService,
@ -145,6 +148,7 @@ describe("LoginStrategyService", () => {
kdfConfigService,
taskSchedulerService,
configService,
accountCryptographicStateService,
);
loginStrategyCacheExpirationState = stateProvider.getFake(CACHE_EXPIRATION_KEY);

View File

@ -19,6 +19,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
@ -160,6 +161,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
protected kdfConfigService: KdfConfigService,
protected taskSchedulerService: TaskSchedulerService,
protected configService: ConfigService,
protected accountCryptographicStateService: AccountCryptographicStateService,
) {
this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY);
this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY);
@ -509,6 +511,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
this.kdfConfigService,
this.environmentService,
this.configService,
this.accountCryptographicStateService,
];
return source.pipe(

View File

@ -1,3 +1,4 @@
import { PrivateKeysResponseModel } from "@bitwarden/common/key-management/keys/response/private-keys.response";
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
@ -20,6 +21,7 @@ export class IdentityTokenResponse extends BaseResponse {
// Decryption Information
resetMasterPassword: boolean;
privateKey: string; // userKeyEncryptedPrivateKey
accountKeysResponseModel: PrivateKeysResponseModel | null = null;
key?: EncString; // masterKeyEncryptedUserKey
twoFactorToken: string;
kdfConfig: KdfConfig;
@ -54,6 +56,11 @@ export class IdentityTokenResponse extends BaseResponse {
this.resetMasterPassword = this.getResponseProperty("ResetMasterPassword");
this.privateKey = this.getResponseProperty("PrivateKey");
if (this.getResponseProperty("AccountKeys") != null) {
this.accountKeysResponseModel = new PrivateKeysResponseModel(
this.getResponseProperty("AccountKeys"),
);
}
const key = this.getResponseProperty("Key");
if (key) {
this.key = new EncString(key);

View File

@ -0,0 +1,20 @@
import { Observable } from "rxjs";
import { UserId } from "@bitwarden/common/types/guid";
import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal";
export abstract class AccountCryptographicStateService {
constructor() {}
// Emits the provided user's security state, or null if there is no security state present for the user.
abstract accountCryptographicState$(
userId: UserId,
): Observable<WrappedAccountCryptographicState | null>;
// Sets the security state for the provided user.
// This is not yet validated, and is only validated upon SDK initialization.
abstract setAccountCryptographicState(
accountCryptographicState: WrappedAccountCryptographicState,
userId: UserId,
): Promise<void>;
}

View File

@ -0,0 +1,35 @@
import { Observable } from "rxjs";
import { UserId } from "@bitwarden/common/types/guid";
import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal";
import { CRYPTO_DISK, StateProvider, UserKeyDefinition } from "@bitwarden/state";
import { AccountCryptographicStateService } from "./account-cryptographic-state.service";
export const ACCOUNT_CRYPTOGRAPHIC_STATE = new UserKeyDefinition<WrappedAccountCryptographicState>(
CRYPTO_DISK,
"accountCryptographicState",
{
deserializer: (obj) => obj,
clearOn: ["logout"],
},
);
export class DefaultAccountCryptographicStateService implements AccountCryptographicStateService {
constructor(protected stateProvider: StateProvider) {}
accountCryptographicState$(userId: UserId): Observable<WrappedAccountCryptographicState | null> {
return this.stateProvider.getUserState$(ACCOUNT_CRYPTOGRAPHIC_STATE, userId);
}
async setAccountCryptographicState(
accountCryptographicState: WrappedAccountCryptographicState,
userId: UserId,
): Promise<void> {
await this.stateProvider.setUserState(
ACCOUNT_CRYPTOGRAPHIC_STATE,
accountCryptographicState,
userId,
);
}
}

View File

@ -1,3 +1,5 @@
import { SignedPublicKey, WrappedAccountCryptographicState } from "@bitwarden/sdk-internal";
import { SecurityStateResponse } from "../../security-state/response/security-state.response";
import { PublicKeyEncryptionKeyPairResponse } from "./public-key-encryption-key-pair.response";
@ -52,4 +54,27 @@ export class PrivateKeysResponseModel {
);
}
}
toWrappedAccountCryptographicState(): WrappedAccountCryptographicState {
if (this.signatureKeyPair === null && this.securityState === null) {
// V1 user
return {
V1: {
private_key: this.publicKeyEncryptionKeyPair.wrappedPrivateKey,
},
};
} else if (this.signatureKeyPair !== null && this.securityState !== null) {
// V2 user
return {
V2: {
private_key: this.publicKeyEncryptionKeyPair.wrappedPrivateKey,
signing_key: this.signatureKeyPair.wrappedSigningKey,
signed_public_key: this.publicKeyEncryptionKeyPair.signedPublicKey as SignedPublicKey,
security_state: this.securityState.securityState as string,
},
};
} else {
throw new Error("Both signatureKeyPair and securityState must be present or absent together");
}
}
}

View File

@ -11,6 +11,7 @@ import {
UserDecryptionOptions,
UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
@ -76,6 +77,7 @@ describe("DefaultSyncService", () => {
let stateProvider: MockProxy<StateProvider>;
let securityStateService: MockProxy<SecurityStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let sut: DefaultSyncService;
@ -107,6 +109,7 @@ describe("DefaultSyncService", () => {
stateProvider = mock();
securityStateService = mock();
kdfConfigService = mock();
accountCryptographicStateService = mock();
sut = new DefaultSyncService(
masterPasswordAbstraction,
@ -135,6 +138,7 @@ describe("DefaultSyncService", () => {
stateProvider,
securityStateService,
kdfConfigService,
accountCryptographicStateService,
);
});

View File

@ -9,8 +9,9 @@ import {
CollectionDetailsResponse,
CollectionService,
} from "@bitwarden/admin-console/common";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service";
// 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 { KdfConfigService, KeyService } from "@bitwarden/key-management";
@ -101,6 +102,7 @@ export class DefaultSyncService extends CoreSyncService {
stateProvider: StateProvider,
private securityStateService: SecurityStateService,
private kdfConfigService: KdfConfigService,
private accountCryptographicStateService: AccountCryptographicStateService,
) {
super(
tokenService,
@ -239,12 +241,17 @@ export class DefaultSyncService extends CoreSyncService {
// Cleanup: Only the first branch should be kept after the server always returns accountKeys https://bitwarden.atlassian.net/browse/PM-21768
if (response.accountKeys != null) {
await this.keyService.setPrivateKey(
response.accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey,
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeys.toWrappedAccountCryptographicState(),
response.id,
);
if (response.accountKeys.signatureKeyPair !== null) {
// User is V2 user
await this.keyService.setPrivateKey(
response.accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey,
response.id,
);
await this.keyService.setUserSigningKey(
response.accountKeys.signatureKeyPair.wrappedSigningKey,
response.id,
@ -257,6 +264,11 @@ export class DefaultSyncService extends CoreSyncService {
response.accountKeys.publicKeyEncryptionKeyPair.signedPublicKey,
response.id,
);
} else {
await this.keyService.setPrivateKey(
response.accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey,
response.id,
);
}
} else {
await this.keyService.setPrivateKey(response.privateKey, response.id);