mirror of
https://github.com/bitwarden/browser
synced 2025-12-29 14:43:31 +00:00
[PM-27315] Add account cryptographic state service (#17589)
* Update account init and save signed public key * Add account cryptographic state service * Fix build * Cleanup * Fix build * Fix import * Fix build on browser * Fix * Fix DI * Fix * Fix * Fix * Fix * Fix * Fix test * Fix desktop build * Fix * Address nits * Cleanup setting private key * Add tests * Add tests * Add test coverage * Relative imports * Fix web build * Cleanup setting of private key
This commit is contained in:
@@ -87,6 +87,8 @@ import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service
|
||||
import { PhishingDetectionSettingsService } from "@bitwarden/common/dirt/services/phishing-detection/phishing-detection-settings.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,
|
||||
@@ -455,6 +457,7 @@ export default class MainBackground {
|
||||
syncServiceListener: SyncServiceListener;
|
||||
browserInitialInstallService: BrowserInitialInstallService;
|
||||
backgroundSyncService: BackgroundSyncService;
|
||||
accountCryptographicStateService: AccountCryptographicStateService;
|
||||
|
||||
webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService;
|
||||
themeStateService: DefaultThemeStateService;
|
||||
@@ -1010,6 +1013,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,
|
||||
@@ -1037,6 +1043,7 @@ export default class MainBackground {
|
||||
this.stateProvider,
|
||||
this.securityStateService,
|
||||
this.kdfConfigService,
|
||||
this.accountCryptographicStateService,
|
||||
);
|
||||
|
||||
this.syncServiceListener = new SyncServiceListener(
|
||||
|
||||
@@ -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,
|
||||
@@ -334,6 +335,7 @@ export class ServiceContainer {
|
||||
masterPasswordUnlockService: MasterPasswordUnlockService;
|
||||
cipherArchiveService: CipherArchiveService;
|
||||
lockService: LockService;
|
||||
private accountCryptographicStateService: DefaultAccountCryptographicStateService;
|
||||
|
||||
constructor() {
|
||||
let p = null;
|
||||
@@ -717,6 +719,10 @@ export class ServiceContainer {
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.loginStrategyService = new LoginStrategyService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
@@ -744,6 +750,7 @@ export class ServiceContainer {
|
||||
this.kdfConfigService,
|
||||
this.taskSchedulerService,
|
||||
this.configService,
|
||||
this.accountCryptographicStateService,
|
||||
);
|
||||
|
||||
this.restrictedItemTypesService = new RestrictedItemTypesService(
|
||||
@@ -879,6 +886,7 @@ export class ServiceContainer {
|
||||
this.stateProvider,
|
||||
this.securityStateService,
|
||||
this.kdfConfigService,
|
||||
this.accountCryptographicStateService,
|
||||
);
|
||||
|
||||
this.totpService = new TotpService(this.sdkService);
|
||||
|
||||
@@ -56,6 +56,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";
|
||||
@@ -416,6 +417,7 @@ const safeProviders: SafeProvider[] = [
|
||||
OrganizationUserApiService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
MessagingServiceAbstraction,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ import { OrganizationInviteService } from "@bitwarden/common/auth/services/organ
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.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 { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.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";
|
||||
@@ -314,6 +315,7 @@ const safeProviders: SafeProvider[] = [
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
OrganizationInviteService,
|
||||
RouterService,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -386,6 +390,16 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
// Assert
|
||||
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
|
||||
expect(keyService.setPrivateKey).toHaveBeenCalledWith(keyPair[1].encryptedString, userId);
|
||||
expect(
|
||||
accountCryptographicStateService.setAccountCryptographicState,
|
||||
).toHaveBeenCalledWith(
|
||||
{
|
||||
V1: {
|
||||
private_key: keyPair[1].encryptedString as EncryptedString,
|
||||
},
|
||||
},
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
it("should set the local master key hash to state", async () => {
|
||||
|
||||
@@ -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,
|
||||
@@ -572,6 +574,7 @@ const safeProviders: SafeProvider[] = [
|
||||
KdfConfigService,
|
||||
TaskSchedulerService,
|
||||
ConfigService,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -894,8 +897,14 @@ const safeProviders: SafeProvider[] = [
|
||||
StateProvider,
|
||||
SecurityStateService,
|
||||
KdfConfigService,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AccountCryptographicStateService,
|
||||
useClass: DefaultAccountCryptographicStateService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: BroadcasterService,
|
||||
useClass: DefaultBroadcasterService,
|
||||
@@ -1565,6 +1574,7 @@ const safeProviders: SafeProvider[] = [
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -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";
|
||||
@@ -22,7 +23,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { makeEncString, FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
@@ -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();
|
||||
@@ -208,4 +212,41 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
// trustDeviceIfRequired should be called
|
||||
expect(deviceTrustService.trustDeviceIfRequired).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets account cryptographic state when accountKeysResponseModel is present", async () => {
|
||||
const accountKeysData = {
|
||||
publicKeyEncryptionKeyPair: {
|
||||
publicKey: "testPublicKey",
|
||||
wrappedPrivateKey: "testPrivateKey",
|
||||
},
|
||||
};
|
||||
|
||||
tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.key = makeEncString("mockEncryptedUserKey");
|
||||
// Add accountKeysResponseModel to the response
|
||||
(tokenResponse as any).accountKeysResponseModel = {
|
||||
publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair,
|
||||
toWrappedAccountCryptographicState: jest.fn().mockReturnValue({
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
masterPasswordService.masterKeySubject.next(decMasterKey);
|
||||
masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(decUserKey);
|
||||
|
||||
await authRequestLoginStrategy.logIn(credentials);
|
||||
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1);
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
|
||||
{
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
},
|
||||
mockUserId,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
@@ -136,6 +137,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;
|
||||
@@ -162,6 +164,7 @@ describe("LoginStrategy", () => {
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
environmentService = mock<EnvironmentService>();
|
||||
configService = mock<ConfigService>();
|
||||
accountCryptographicStateService = mock<AccountCryptographicStateService>();
|
||||
|
||||
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||
|
||||
@@ -192,6 +195,7 @@ describe("LoginStrategy", () => {
|
||||
kdfConfigService,
|
||||
environmentService,
|
||||
configService,
|
||||
accountCryptographicStateService,
|
||||
);
|
||||
credentials = new PasswordLoginCredentials(email, masterPassword);
|
||||
});
|
||||
@@ -518,6 +522,7 @@ describe("LoginStrategy", () => {
|
||||
kdfConfigService,
|
||||
environmentService,
|
||||
configService,
|
||||
accountCryptographicStateService,
|
||||
);
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
@@ -579,6 +584,7 @@ describe("LoginStrategy", () => {
|
||||
kdfConfigService,
|
||||
environmentService,
|
||||
configService,
|
||||
accountCryptographicStateService,
|
||||
);
|
||||
|
||||
const result = await passwordLoginStrategy.logIn(credentials);
|
||||
|
||||
@@ -18,6 +18,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 {
|
||||
@@ -89,6 +90,7 @@ export abstract class LoginStrategy {
|
||||
protected KdfConfigService: KdfConfigService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected configService: ConfigService,
|
||||
protected accountCryptographicStateService: AccountCryptographicStateService,
|
||||
) {}
|
||||
|
||||
abstract exportCache(): CacheData;
|
||||
|
||||
@@ -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 {
|
||||
@@ -28,7 +29,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
||||
import { HashPurpose } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { FakeAccountService, makeEncString, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import {
|
||||
PasswordStrengthServiceAbstraction,
|
||||
PasswordStrengthService,
|
||||
@@ -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);
|
||||
@@ -392,4 +396,43 @@ describe("PasswordLoginStrategy", () => {
|
||||
);
|
||||
expect(result.userId).toBe(userId);
|
||||
});
|
||||
|
||||
it("sets account cryptographic state when accountKeysResponseModel is present", async () => {
|
||||
const accountKeysData = {
|
||||
publicKeyEncryptionKeyPair: {
|
||||
publicKey: "testPublicKey",
|
||||
wrappedPrivateKey: "testPrivateKey",
|
||||
},
|
||||
};
|
||||
|
||||
tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.key = makeEncString("mockEncryptedUserKey");
|
||||
// Add accountKeysResponseModel to the response
|
||||
(tokenResponse as any).accountKeysResponseModel = {
|
||||
publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair,
|
||||
toWrappedAccountCryptographicState: jest.fn().mockReturnValue({
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey,
|
||||
);
|
||||
|
||||
await passwordLoginStrategy.logIn(credentials);
|
||||
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1);
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
|
||||
{
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
},
|
||||
userId,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -156,6 +156,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 {
|
||||
|
||||
@@ -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";
|
||||
@@ -30,7 +31,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { FakeAccountService, makeEncString, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DeviceKey, MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
@@ -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);
|
||||
});
|
||||
@@ -556,4 +560,39 @@ describe("SsoLoginStrategy", () => {
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(userKey, userId);
|
||||
});
|
||||
});
|
||||
|
||||
it("sets account cryptographic state when accountKeysResponseModel is present", async () => {
|
||||
const accountKeysData = {
|
||||
publicKeyEncryptionKeyPair: {
|
||||
publicKey: "testPublicKey",
|
||||
wrappedPrivateKey: "testPrivateKey",
|
||||
},
|
||||
};
|
||||
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.key = makeEncString("mockEncryptedUserKey");
|
||||
// Add accountKeysResponseModel to the response
|
||||
(tokenResponse as any).accountKeysResponseModel = {
|
||||
publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair,
|
||||
toWrappedAccountCryptographicState: jest.fn().mockReturnValue({
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await ssoLoginStrategy.logIn(credentials);
|
||||
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1);
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
|
||||
{
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
},
|
||||
userId,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -226,4 +230,38 @@ describe("UserApiLoginStrategy", () => {
|
||||
);
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(userKey, userId);
|
||||
});
|
||||
|
||||
it("sets account cryptographic state when accountKeysResponseModel is present", async () => {
|
||||
const accountKeysData = {
|
||||
publicKeyEncryptionKeyPair: {
|
||||
publicKey: "testPublicKey",
|
||||
wrappedPrivateKey: "testPrivateKey",
|
||||
},
|
||||
};
|
||||
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
// Add accountKeysResponseModel to the response
|
||||
(tokenResponse as any).accountKeysResponseModel = {
|
||||
publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair,
|
||||
toWrappedAccountCryptographicState: jest.fn().mockReturnValue({
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1);
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
|
||||
{
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
},
|
||||
userId,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -340,6 +344,53 @@ describe("WebAuthnLoginStrategy", () => {
|
||||
// Assert
|
||||
expect(keyService.setUserKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets account cryptographic state when accountKeysResponseModel is present", async () => {
|
||||
// Arrange
|
||||
const accountKeysData = {
|
||||
publicKeyEncryptionKeyPair: {
|
||||
publicKey: "testPublicKey",
|
||||
wrappedPrivateKey: "testPrivateKey",
|
||||
},
|
||||
};
|
||||
|
||||
const idTokenResponse: IdentityTokenResponse = identityTokenResponseFactory(
|
||||
null,
|
||||
userDecryptionOptsServerResponseWithWebAuthnPrfOption,
|
||||
);
|
||||
// Add accountKeysResponseModel to the response
|
||||
(idTokenResponse as any).accountKeysResponseModel = {
|
||||
publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair,
|
||||
toWrappedAccountCryptographicState: jest.fn().mockReturnValue({
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(idTokenResponse);
|
||||
|
||||
const mockPrfPrivateKey: Uint8Array = randomBytes(32);
|
||||
const mockUserKeyArray: Uint8Array = randomBytes(32);
|
||||
encryptService.unwrapDecapsulationKey.mockResolvedValue(mockPrfPrivateKey);
|
||||
encryptService.decapsulateKeyUnsigned.mockResolvedValue(
|
||||
new SymmetricCryptoKey(mockUserKeyArray),
|
||||
);
|
||||
|
||||
// Act
|
||||
await webAuthnLoginStrategy.logIn(webAuthnCredentials);
|
||||
|
||||
// Assert
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1);
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
|
||||
{
|
||||
V1: {
|
||||
private_key: "testPrivateKey",
|
||||
},
|
||||
},
|
||||
userId,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Helpers and mocks
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -116,4 +116,36 @@ describe("IdentityTokenResponse", () => {
|
||||
const identityTokenResponse = new IdentityTokenResponse(response);
|
||||
expect(identityTokenResponse.userDecryptionOptions).toBeDefined();
|
||||
});
|
||||
|
||||
it("should create response with accountKeys not present", () => {
|
||||
const response = {
|
||||
access_token: accessToken,
|
||||
token_type: tokenType,
|
||||
AccountKeys: null as unknown,
|
||||
};
|
||||
|
||||
const identityTokenResponse = new IdentityTokenResponse(response);
|
||||
expect(identityTokenResponse.accountKeysResponseModel).toBeNull();
|
||||
});
|
||||
|
||||
it("should create response with accountKeys present", () => {
|
||||
const accountKeysData = {
|
||||
publicKeyEncryptionKeyPair: {
|
||||
publicKey: "testPublicKey",
|
||||
wrappedPrivateKey: "testPrivateKey",
|
||||
},
|
||||
};
|
||||
|
||||
const response = {
|
||||
access_token: accessToken,
|
||||
token_type: tokenType,
|
||||
AccountKeys: accountKeysData,
|
||||
};
|
||||
|
||||
const identityTokenResponse = new IdentityTokenResponse(response);
|
||||
expect(identityTokenResponse.accountKeysResponseModel).toBeDefined();
|
||||
expect(
|
||||
identityTokenResponse.accountKeysResponseModel?.publicKeyEncryptionKeyPair,
|
||||
).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { Argon2KdfConfig, KdfConfig, KdfType, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { PrivateKeysResponseModel } from "../../../key-management/keys/response/private-keys.response";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
import { MasterPasswordPolicyResponse } from "./master-password-policy.response";
|
||||
@@ -19,6 +20,7 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
|
||||
// Decryption Information
|
||||
privateKey: string; // userKeyEncryptedPrivateKey
|
||||
accountKeysResponseModel: PrivateKeysResponseModel | null = null;
|
||||
key?: EncString; // masterKeyEncryptedUserKey
|
||||
twoFactorToken: string;
|
||||
kdfConfig: KdfConfig;
|
||||
@@ -52,6 +54,11 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
export abstract class AccountCryptographicStateService {
|
||||
/**
|
||||
* Emits the provided user's account cryptographic state or null if there is no account cryptographic state present for the user.
|
||||
*/
|
||||
abstract accountCryptographicState$(
|
||||
userId: UserId,
|
||||
): Observable<WrappedAccountCryptographicState | null>;
|
||||
|
||||
/**
|
||||
* Sets the account cryptographic state.
|
||||
* This is not yet validated, and is only validated upon SDK initialization.
|
||||
*/
|
||||
abstract setAccountCryptographicState(
|
||||
accountCryptographicState: WrappedAccountCryptographicState,
|
||||
userId: UserId,
|
||||
): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal";
|
||||
import { FakeStateProvider } from "@bitwarden/state-test-utils";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec";
|
||||
|
||||
import {
|
||||
ACCOUNT_CRYPTOGRAPHIC_STATE,
|
||||
DefaultAccountCryptographicStateService,
|
||||
} from "./default-account-cryptographic-state.service";
|
||||
|
||||
describe("DefaultAccountCryptographicStateService", () => {
|
||||
let service: DefaultAccountCryptographicStateService;
|
||||
let stateProvider: FakeStateProvider;
|
||||
let accountService: FakeAccountService;
|
||||
|
||||
const mockUserId = "user-id" as UserId;
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
service = new DefaultAccountCryptographicStateService(stateProvider);
|
||||
});
|
||||
|
||||
describe("accountCryptographicState$", () => {
|
||||
it("returns null when no state is set", async () => {
|
||||
const result = await firstValueFrom(service.accountCryptographicState$(mockUserId));
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns the account cryptographic state when set (V1)", async () => {
|
||||
const mockState: WrappedAccountCryptographicState = {
|
||||
V1: {
|
||||
private_key: "test-wrapped-state" as any,
|
||||
},
|
||||
};
|
||||
await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState, mockUserId);
|
||||
const result = await firstValueFrom(service.accountCryptographicState$(mockUserId));
|
||||
expect(result).toEqual(mockState);
|
||||
});
|
||||
|
||||
it("returns the account cryptographic state when set (V2)", async () => {
|
||||
const mockState: WrappedAccountCryptographicState = {
|
||||
V2: {
|
||||
private_key: "test-wrapped-private-key" as any,
|
||||
signing_key: "test-wrapped-signing-key" as any,
|
||||
signed_public_key: "test-signed-public-key" as any,
|
||||
security_state: "test-security-state",
|
||||
},
|
||||
};
|
||||
await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState, mockUserId);
|
||||
const result = await firstValueFrom(service.accountCryptographicState$(mockUserId));
|
||||
expect(result).toEqual(mockState);
|
||||
});
|
||||
|
||||
it("emits updated state when state changes", async () => {
|
||||
const mockState1: any = {
|
||||
V1: {
|
||||
private_key: "test-state-1" as any,
|
||||
},
|
||||
};
|
||||
const mockState2: any = {
|
||||
V1: {
|
||||
private_key: "test-state-2" as any,
|
||||
},
|
||||
};
|
||||
|
||||
await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState1, mockUserId);
|
||||
|
||||
const observable = service.accountCryptographicState$(mockUserId);
|
||||
const results: (WrappedAccountCryptographicState | null)[] = [];
|
||||
const subscription = observable.subscribe((state) => results.push(state));
|
||||
|
||||
await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState2, mockUserId);
|
||||
|
||||
subscription.unsubscribe();
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results[0]).toEqual(mockState1);
|
||||
expect(results[1]).toEqual(mockState2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setAccountCryptographicState", () => {
|
||||
it("sets the account cryptographic state", async () => {
|
||||
const mockState: WrappedAccountCryptographicState = {
|
||||
V1: {
|
||||
private_key: "test-wrapped-state" as any,
|
||||
},
|
||||
};
|
||||
|
||||
await service.setAccountCryptographicState(mockState, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(service.accountCryptographicState$(mockUserId));
|
||||
|
||||
expect(result).toEqual(mockState);
|
||||
});
|
||||
|
||||
it("overwrites existing state", async () => {
|
||||
const mockState1: WrappedAccountCryptographicState = {
|
||||
V1: {
|
||||
private_key: "test-state-1" as any,
|
||||
},
|
||||
};
|
||||
const mockState2: WrappedAccountCryptographicState = {
|
||||
V1: {
|
||||
private_key: "test-state-2" as any,
|
||||
},
|
||||
};
|
||||
|
||||
await service.setAccountCryptographicState(mockState1, mockUserId);
|
||||
await service.setAccountCryptographicState(mockState2, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(service.accountCryptographicState$(mockUserId));
|
||||
|
||||
expect(result).toEqual(mockState2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ACCOUNT_CRYPTOGRAPHIC_STATE key definition", () => {
|
||||
it("deserializer returns object as-is", () => {
|
||||
const mockState: any = {
|
||||
V1: {
|
||||
private_key: "test" as any,
|
||||
},
|
||||
};
|
||||
const result = ACCOUNT_CRYPTOGRAPHIC_STATE.deserializer(mockState);
|
||||
expect(result).toBe(mockState);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal";
|
||||
import { CRYPTO_DISK, StateProvider, UserKeyDefinition } from "@bitwarden/state";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { AccountCryptographicStateService } from "./account-cryptographic-state.service";
|
||||
|
||||
export const ACCOUNT_CRYPTOGRAPHIC_STATE = new UserKeyDefinition<WrappedAccountCryptographicState>(
|
||||
CRYPTO_DISK,
|
||||
"accountCryptographicState",
|
||||
{
|
||||
deserializer: (obj) => obj as WrappedAccountCryptographicState,
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,31 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
isV2Encryption(): boolean {
|
||||
return this.signatureKeyPair !== null && this.securityState !== null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { EncryptionType } from "../../enums";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
@@ -27,7 +27,9 @@ describe("Encrypted private key", () => {
|
||||
it("should deserialize encrypted private key", () => {
|
||||
const encryptedPrivateKey = makeEncString().encryptedString;
|
||||
|
||||
const result = sut.deserializer(JSON.parse(JSON.stringify(encryptedPrivateKey)));
|
||||
const result = sut.deserializer(
|
||||
JSON.parse(JSON.stringify(encryptedPrivateKey as unknown)) as unknown as EncryptedString,
|
||||
);
|
||||
|
||||
expect(result).toEqual(encryptedPrivateKey);
|
||||
});
|
||||
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
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.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||
@@ -29,6 +27,8 @@ import { TokenService } from "../../auth/abstractions/token.service";
|
||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||
import { DomainSettingsService } from "../../autofill/services/domain-settings.service";
|
||||
import { BillingAccountProfileStateService } from "../../billing/abstractions";
|
||||
import { AccountCryptographicStateService } from "../../key-management/account-cryptography/account-cryptographic-state.service";
|
||||
import { EncString } from "../../key-management/crypto/models/enc-string";
|
||||
import { KeyConnectorService } from "../../key-management/key-connector/abstractions/key-connector.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "../../key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import {
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
MasterPasswordSalt,
|
||||
MasterPasswordUnlockData,
|
||||
} from "../../key-management/master-password/types/master-password.types";
|
||||
import { SecurityStateService } from "../../key-management/security-state/abstractions/security-state.service";
|
||||
import { SendApiService } from "../../tools/send/services/send-api.service.abstraction";
|
||||
import { InternalSendService } from "../../tools/send/services/send.service.abstraction";
|
||||
import { UserId } from "../../types/guid";
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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,18 @@ 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.accountCryptographicStateService.setAccountCryptographicState(
|
||||
response.accountKeys.toWrappedAccountCryptographicState(),
|
||||
response.id,
|
||||
);
|
||||
|
||||
// V1 and V2 users
|
||||
await this.keyService.setPrivateKey(
|
||||
response.accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey,
|
||||
response.id,
|
||||
);
|
||||
if (response.accountKeys.signatureKeyPair !== null) {
|
||||
// User is V2 user
|
||||
// V2 users only
|
||||
if (response.accountKeys.isV2Encryption()) {
|
||||
await this.keyService.setUserSigningKey(
|
||||
response.accountKeys.signatureKeyPair.wrappedSigningKey,
|
||||
response.id,
|
||||
|
||||
Reference in New Issue
Block a user