1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 00:33:44 +00:00

[PM-5735] Create kdf Service (#8715)

* key connector migration initial

* migrator complete

* fix dependencies

* finalized tests

* fix deps and sync main

* clean up definition file

* fixing tests

* fixed tests

* fixing CLI, Browser, Desktop builds

* fixed factory options

* reverting exports

* implemented UserKeyDefinition clearOn

* Initial Kdf Service Changes

* rename and account setting kdfconfig

* fixing tests and renaming migration

* fixed DI ordering for browser

* rename and fix DI

* Clean up Migrations

* fixing migrations

* begin data structure changes for kdf config

* Make KDF more type safe; co-author: jlf0dev

* fixing tests

* Fixed CLI login and comments

* set now accepts userId and test updates

---------

Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
Ike
2024-04-25 11:26:01 -07:00
committed by GitHub
parent dba910d0b9
commit 1e4158fd87
82 changed files with 896 additions and 361 deletions

View File

@@ -7,10 +7,15 @@ import {
OrganizationUserResetPasswordRequest,
OrganizationUserResetPasswordWithIdRequest,
} from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KdfType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@@ -90,12 +95,17 @@ export class OrganizationUserResetPasswordService {
const decValue = await this.cryptoService.rsaDecrypt(response.resetPasswordKey, decPrivateKey);
const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey;
// determine Kdf Algorithm
const kdfConfig: KdfConfig =
response.kdf === KdfType.PBKDF2_SHA256
? new PBKDF2KdfConfig(response.kdfIterations)
: new Argon2KdfConfig(response.kdfIterations, response.kdfMemory, response.kdfParallelism);
// Create new master key and hash new password
const newMasterKey = await this.cryptoService.makeMasterKey(
newMasterPassword,
email.trim().toLowerCase(),
response.kdf,
new KdfConfig(response.kdfIterations, response.kdfMemory, response.kdfParallelism),
kdfConfig,
);
const newMasterKeyHash = await this.cryptoService.hashMasterKey(
newMasterPassword,

View File

@@ -3,10 +3,15 @@ import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { KdfType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@@ -231,16 +236,22 @@ export class EmergencyAccessService {
const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey;
const masterKey = await this.cryptoService.makeMasterKey(
masterPassword,
email,
takeoverResponse.kdf,
new KdfConfig(
takeoverResponse.kdfIterations,
takeoverResponse.kdfMemory,
takeoverResponse.kdfParallelism,
),
);
let config: KdfConfig;
switch (takeoverResponse.kdf) {
case KdfType.PBKDF2_SHA256:
config = new PBKDF2KdfConfig(takeoverResponse.kdfIterations);
break;
case KdfType.Argon2id:
config = new Argon2KdfConfig(
takeoverResponse.kdfIterations,
takeoverResponse.kdfMemory,
takeoverResponse.kdfParallelism,
);
break;
}
const masterKey = await this.cryptoService.makeMasterKey(masterPassword, email, config);
const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey);
const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, grantorUserKey);

View File

@@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -47,6 +48,7 @@ describe("KeyRotationService", () => {
let mockEncryptService: MockProxy<EncryptService>;
let mockStateService: MockProxy<StateService>;
let mockConfigService: MockProxy<ConfigService>;
let mockKdfConfigService: MockProxy<KdfConfigService>;
const mockUserId = Utils.newGuid() as UserId;
const mockAccountService: FakeAccountService = mockAccountServiceWith(mockUserId);
@@ -65,6 +67,7 @@ describe("KeyRotationService", () => {
mockEncryptService = mock<EncryptService>();
mockStateService = mock<StateService>();
mockConfigService = mock<ConfigService>();
mockKdfConfigService = mock<KdfConfigService>();
keyRotationService = new UserKeyRotationService(
mockMasterPasswordService,
@@ -80,6 +83,7 @@ describe("KeyRotationService", () => {
mockStateService,
mockAccountService,
mockConfigService,
mockKdfConfigService,
);
});

View File

@@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -39,6 +40,7 @@ export class UserKeyRotationService {
private stateService: StateService,
private accountService: AccountService,
private configService: ConfigService,
private kdfConfigService: KdfConfigService,
) {}
/**
@@ -54,8 +56,7 @@ export class UserKeyRotationService {
const masterKey = await this.cryptoService.makeMasterKey(
masterPassword,
await this.stateService.getEmail(),
await this.stateService.getKdfType(),
await this.stateService.getKdfConfig(),
await this.kdfConfigService.getKdfConfig(),
);
if (!masterKey) {

View File

@@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request";
import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request";
@@ -37,6 +38,7 @@ export class ChangeEmailComponent implements OnInit {
private logService: LogService,
private stateService: StateService,
private formBuilder: FormBuilder,
private kdfConfigService: KdfConfigService,
) {}
async ngOnInit() {
@@ -83,12 +85,10 @@ export class ChangeEmailComponent implements OnInit {
step1Value.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(step1Value.masterPassword),
);
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const newMasterKey = await this.cryptoService.makeMasterKey(
step1Value.masterPassword,
newEmail,
kdf,
kdfConfig,
);
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(

View File

@@ -5,6 +5,7 @@ import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitward
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -48,6 +49,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
dialogService: DialogService,
private userVerificationService: UserVerificationService,
private keyRotationService: UserKeyRotationService,
kdfConfigService: KdfConfigService,
) {
super(
i18nService,
@@ -58,6 +60,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
policyService,
stateService,
dialogService,
kdfConfigService,
);
}

View File

@@ -5,6 +5,7 @@ import { takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -58,6 +59,7 @@ export class EmergencyAccessTakeoverComponent
private logService: LogService,
dialogService: DialogService,
private dialogRef: DialogRef<EmergencyAccessTakeoverResultType>,
kdfConfigService: KdfConfigService,
) {
super(
i18nService,
@@ -68,6 +70,7 @@ export class EmergencyAccessTakeoverComponent
policyService,
stateService,
dialogService,
kdfConfigService,
);
}

View File

@@ -3,6 +3,7 @@ import { Component, Inject } from "@angular/core";
import { FormGroup, FormControl, Validators } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -18,7 +19,6 @@ import { KdfType } from "@bitwarden/common/platform/enums";
templateUrl: "change-kdf-confirmation.component.html",
})
export class ChangeKdfConfirmationComponent {
kdf: KdfType;
kdfConfig: KdfConfig;
form = new FormGroup({
@@ -37,9 +37,9 @@ export class ChangeKdfConfirmationComponent {
private messagingService: MessagingService,
private stateService: StateService,
private logService: LogService,
@Inject(DIALOG_DATA) params: { kdf: KdfType; kdfConfig: KdfConfig },
private kdfConfigService: KdfConfigService,
@Inject(DIALOG_DATA) params: { kdfConfig: KdfConfig },
) {
this.kdf = params.kdf;
this.kdfConfig = params.kdfConfig;
this.masterPassword = null;
}
@@ -65,22 +65,24 @@ export class ChangeKdfConfirmationComponent {
private async makeKeyAndSaveAsync() {
const masterPassword = this.form.value.masterPassword;
// Ensure the KDF config is valid.
this.kdfConfig.validateKdfConfig();
const request = new KdfRequest();
request.kdf = this.kdf;
request.kdf = this.kdfConfig.kdfType;
request.kdfIterations = this.kdfConfig.iterations;
request.kdfMemory = this.kdfConfig.memory;
request.kdfParallelism = this.kdfConfig.parallelism;
if (this.kdfConfig.kdfType === KdfType.Argon2id) {
request.kdfMemory = this.kdfConfig.memory;
request.kdfParallelism = this.kdfConfig.parallelism;
}
const masterKey = await this.cryptoService.getOrDeriveMasterKey(masterPassword);
request.masterPasswordHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey);
const email = await this.stateService.getEmail();
// Ensure the KDF config is valid.
this.cryptoService.validateKdfConfig(this.kdf, this.kdfConfig);
const newMasterKey = await this.cryptoService.makeMasterKey(
masterPassword,
email,
this.kdf,
this.kdfConfig,
);
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(

View File

@@ -19,14 +19,14 @@
<select
id="kdf"
name="Kdf"
[(ngModel)]="kdf"
[(ngModel)]="kdfConfig.kdfType"
(ngModelChange)="onChangeKdf($event)"
class="form-control mb-3"
required
>
<option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<ng-container *ngIf="kdf == kdfType.Argon2id">
<ng-container *ngIf="isArgon2(kdfConfig)">
<label for="kdfMemory">{{ "kdfMemory" | i18n }}</label>
<input
id="kdfMemory"
@@ -43,7 +43,7 @@
</div>
<div class="col-6">
<div class="form-group mb-0">
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256">
<ng-container *ngIf="isPBKDF2(kdfConfig)">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<a
class="ml-auto"
@@ -65,7 +65,7 @@
required
/>
</ng-container>
<ng-container *ngIf="kdf == kdfType.Argon2id">
<ng-container *ngIf="isArgon2(kdfConfig)">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<input
id="iterations"
@@ -92,7 +92,7 @@
</div>
</div>
<div class="col-12">
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256">
<ng-container *ngIf="isPBKDF2(kdfConfig)">
<p class="small form-text text-muted">
{{ "kdfIterationsDesc" | i18n: (PBKDF2_ITERATIONS.defaultValue | number) }}
</p>
@@ -100,7 +100,7 @@
{{ "kdfIterationsWarning" | i18n: (100000 | number) }}
</bit-callout>
</ng-container>
<ng-container *ngIf="kdf == kdfType.Argon2id">
<ng-container *ngIf="isArgon2(kdfConfig)">
<p class="small form-text text-muted">{{ "argon2Desc" | i18n }}</p>
<bit-callout type="warning"> {{ "argon2Warning" | i18n }}</bit-callout>
</ng-container>

View File

@@ -1,7 +1,11 @@
import { Component, OnInit } from "@angular/core";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import {
DEFAULT_KDF_CONFIG,
PBKDF2_ITERATIONS,
@@ -19,7 +23,6 @@ import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.compon
templateUrl: "change-kdf.component.html",
})
export class ChangeKdfComponent implements OnInit {
kdf = KdfType.PBKDF2_SHA256;
kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG;
kdfType = KdfType;
kdfOptions: any[] = [];
@@ -31,8 +34,8 @@ export class ChangeKdfComponent implements OnInit {
protected ARGON2_PARALLELISM = ARGON2_PARALLELISM;
constructor(
private stateService: StateService,
private dialogService: DialogService,
private kdfConfigService: KdfConfigService,
) {
this.kdfOptions = [
{ name: "PBKDF2 SHA-256", value: KdfType.PBKDF2_SHA256 },
@@ -41,19 +44,22 @@ export class ChangeKdfComponent implements OnInit {
}
async ngOnInit() {
this.kdf = await this.stateService.getKdfType();
this.kdfConfig = await this.stateService.getKdfConfig();
this.kdfConfig = await this.kdfConfigService.getKdfConfig();
}
isPBKDF2(t: KdfConfig): t is PBKDF2KdfConfig {
return t instanceof PBKDF2KdfConfig;
}
isArgon2(t: KdfConfig): t is Argon2KdfConfig {
return t instanceof Argon2KdfConfig;
}
async onChangeKdf(newValue: KdfType) {
if (newValue === KdfType.PBKDF2_SHA256) {
this.kdfConfig = new KdfConfig(PBKDF2_ITERATIONS.defaultValue);
this.kdfConfig = new PBKDF2KdfConfig();
} else if (newValue === KdfType.Argon2id) {
this.kdfConfig = new KdfConfig(
ARGON2_ITERATIONS.defaultValue,
ARGON2_MEMORY.defaultValue,
ARGON2_PARALLELISM.defaultValue,
);
this.kdfConfig = new Argon2KdfConfig();
} else {
throw new Error("Unknown KDF type.");
}
@@ -62,7 +68,6 @@ export class ChangeKdfComponent implements OnInit {
async openConfirmationModal() {
this.dialogService.open(ChangeKdfConfirmationComponent, {
data: {
kdf: this.kdf,
kdfConfig: this.kdfConfig,
},
});

View File

@@ -4,6 +4,7 @@ import { Router } from "@angular/router";
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -32,6 +33,7 @@ export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
stateService: StateService,
userVerificationService: UserVerificationService,
dialogService: DialogService,
kdfConfigService: KdfConfigService,
) {
super(
router,
@@ -46,6 +48,7 @@ export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
userVerificationService,
logService,
dialogService,
kdfConfigService,
);
}
}

View File

@@ -35,6 +35,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
@@ -184,6 +185,7 @@ export class VaultComponent implements OnInit, OnDestroy {
private apiService: ApiService,
private userVerificationService: UserVerificationService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
protected kdfConfigService: KdfConfigService,
) {}
async ngOnInit() {
@@ -972,10 +974,10 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async isLowKdfIteration() {
const kdfType = await this.stateService.getKdfType();
const kdfOptions = await this.stateService.getKdfConfig();
const kdfConfig = await this.kdfConfigService.getKdfConfig();
return (
kdfType === KdfType.PBKDF2_SHA256 && kdfOptions.iterations < PBKDF2_ITERATIONS.defaultValue
kdfConfig.kdfType === KdfType.PBKDF2_SHA256 &&
kdfConfig.iterations < PBKDF2_ITERATIONS.defaultValue
);
}