[PS-2365] Kdf Configuration Options for Argon2 (#4578)
* Implement argon2 config * Remove argon2 webassembly warning * Replace magic numbers by enum * Implement kdf configuration * Update UI according to design feedback * Further updates to follow design feedback * Add oxford comma in argon2 description * Fix typos in argon2 descriptions * move key creation into promise with API call * change casing on PBKDF2 * general improvements * kdf config on set pin component * SHA-256 hash argon2 salt * Change argon2 defaults * Change argon2 salt hash to cryptoFunctionService * Fix isLowKdfIteration check --------- Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
parent
b1a1068906
commit
01091fe260
|
@ -410,7 +410,7 @@ export class LoginCommand {
|
||||||
this.policyService.masterPasswordPolicyOptions$()
|
this.policyService.masterPasswordPolicyOptions$()
|
||||||
);
|
);
|
||||||
const kdf = await this.stateService.getKdfType();
|
const kdf = await this.stateService.getKdfType();
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
enforcedPolicyOptions != null &&
|
enforcedPolicyOptions != null &&
|
||||||
|
@ -431,7 +431,7 @@ export class LoginCommand {
|
||||||
masterPassword,
|
masterPassword,
|
||||||
this.email.trim().toLowerCase(),
|
this.email.trim().toLowerCase(),
|
||||||
kdf,
|
kdf,
|
||||||
kdfIterations
|
kdfConfig
|
||||||
);
|
);
|
||||||
const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey);
|
const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey);
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,8 @@ export class UnlockCommand {
|
||||||
await this.setNewSessionKey();
|
await this.setNewSessionKey();
|
||||||
const email = await this.stateService.getEmail();
|
const email = await this.stateService.getEmail();
|
||||||
const kdf = await this.stateService.getKdfType();
|
const kdf = await this.stateService.getKdfType();
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
const key = await this.cryptoService.makeKey(password, email, kdf, kdfIterations);
|
const key = await this.cryptoService.makeKey(password, email, kdf, kdfConfig);
|
||||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||||
|
|
||||||
let passwordValid = false;
|
let passwordValid = false;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||||
|
import { KdfConfig } from "@bitwarden/common/models/domain/kdf-config";
|
||||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
|
@ -156,6 +157,8 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
const kdfType = response.kdf;
|
const kdfType = response.kdf;
|
||||||
const kdfIterations = response.kdfIterations;
|
const kdfIterations = response.kdfIterations;
|
||||||
|
const kdfMemory = response.kdfMemory;
|
||||||
|
const kdfParallelism = response.kdfParallelism;
|
||||||
const resetPasswordKey = response.resetPasswordKey;
|
const resetPasswordKey = response.resetPasswordKey;
|
||||||
const encryptedPrivateKey = response.encryptedPrivateKey;
|
const encryptedPrivateKey = response.encryptedPrivateKey;
|
||||||
|
|
||||||
|
@ -175,7 +178,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||||
this.newPassword,
|
this.newPassword,
|
||||||
this.email.trim().toLowerCase(),
|
this.email.trim().toLowerCase(),
|
||||||
kdfType,
|
kdfType,
|
||||||
kdfIterations
|
new KdfConfig(kdfIterations, kdfMemory, kdfParallelism)
|
||||||
);
|
);
|
||||||
const newPasswordHash = await this.cryptoService.hashPassword(this.newPassword, newKey);
|
const newPasswordHash = await this.cryptoService.hashPassword(this.newPassword, newKey);
|
||||||
|
|
||||||
|
|
|
@ -66,12 +66,12 @@ export class ChangeEmailComponent implements OnInit {
|
||||||
request.newEmail = this.newEmail;
|
request.newEmail = this.newEmail;
|
||||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
||||||
const kdf = await this.stateService.getKdfType();
|
const kdf = await this.stateService.getKdfType();
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
const newKey = await this.cryptoService.makeKey(
|
const newKey = await this.cryptoService.makeKey(
|
||||||
this.masterPassword,
|
this.masterPassword,
|
||||||
this.newEmail,
|
this.newEmail,
|
||||||
kdf,
|
kdf,
|
||||||
kdfIterations
|
kdfConfig
|
||||||
);
|
);
|
||||||
request.newMasterPasswordHash = await this.cryptoService.hashPassword(
|
request.newMasterPasswordHash = await this.cryptoService.hashPassword(
|
||||||
this.masterPassword,
|
this.masterPassword,
|
||||||
|
|
|
@ -32,43 +32,94 @@
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
<select id="kdf" name="Kdf" [(ngModel)]="kdf" class="form-control" required>
|
<select
|
||||||
|
id="kdf"
|
||||||
|
name="Kdf"
|
||||||
|
[(ngModel)]="kdf"
|
||||||
|
(ngModelChange)="onChangeKdf($event)"
|
||||||
|
class="form-control mb-3"
|
||||||
|
required
|
||||||
|
>
|
||||||
<option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{ o.name }}</option>
|
<option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
<ng-container *ngIf="kdf == kdfType.Argon2id">
|
||||||
|
<label for="kdfMemory">{{ "kdfMemory" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="kdfMemory"
|
||||||
|
type="number"
|
||||||
|
min="16"
|
||||||
|
max="1024"
|
||||||
|
name="Memory"
|
||||||
|
class="form-control mb-3"
|
||||||
|
[(ngModel)]="kdfConfig.memory"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="form-group mb-0">
|
<div class="form-group mb-0">
|
||||||
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
|
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256">
|
||||||
<a
|
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
|
||||||
class="ml-auto"
|
<a
|
||||||
href="https://bitwarden.com/help/what-encryption-is-used/#pbkdf2"
|
class="ml-auto"
|
||||||
target="_blank"
|
href="https://bitwarden.com/help/kdf-algorithms"
|
||||||
rel="noopener"
|
target="_blank"
|
||||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
rel="noopener"
|
||||||
>
|
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
>
|
||||||
</a>
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
<input
|
</a>
|
||||||
id="kdfIterations"
|
<input
|
||||||
type="number"
|
id="kdfIterations"
|
||||||
min="5000"
|
type="number"
|
||||||
max="2000000"
|
min="5000"
|
||||||
name="KdfIterations"
|
max="2000000"
|
||||||
class="form-control"
|
name="KdfIterations"
|
||||||
[(ngModel)]="kdfIterations"
|
class="form-control"
|
||||||
required
|
[(ngModel)]="kdfConfig.iterations"
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="kdf == kdfType.Argon2id">
|
||||||
|
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="iterations"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="1024"
|
||||||
|
name="Iterations"
|
||||||
|
class="form-control mb-3"
|
||||||
|
[(ngModel)]="kdfConfig.iterations"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label for="kdfParallelism">{{ "kdfParallelism" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="kdfParallelism"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="16"
|
||||||
|
name="Parallelism"
|
||||||
|
class="form-control"
|
||||||
|
[(ngModel)]="kdfConfig.parallelism"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="form-group">
|
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256">
|
||||||
<div class="small form-text text-muted">
|
<p class="small form-text text-muted">
|
||||||
<p>{{ "kdfIterationsDesc" | i18n: (recommendedKdfIterations | number) }}</p>
|
{{ "kdfIterationsDesc" | i18n: (recommendedPbkdf2Iterations | number) }}
|
||||||
<strong>{{ "warning" | i18n }}</strong
|
</p>
|
||||||
>: {{ "kdfIterationsWarning" | i18n: (50000 | number) }}
|
<bit-callout type="warning">
|
||||||
</div>
|
{{ "kdfIterationsWarning" | i18n: (100000 | number) }}
|
||||||
</div>
|
</bit-callout>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="kdf == kdfType.Argon2id">
|
||||||
|
<p class="small form-text text-muted">{{ "argon2Desc" | i18n }}</p>
|
||||||
|
<bit-callout type="warning"> {{ "argon2Warning" | i18n }}</bit-callout>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||||
|
|
|
@ -7,7 +7,15 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { DEFAULT_PBKDF2_ITERATIONS, KdfType } from "@bitwarden/common/enums/kdfType";
|
import {
|
||||||
|
DEFAULT_KDF_CONFIG,
|
||||||
|
DEFAULT_PBKDF2_ITERATIONS,
|
||||||
|
DEFAULT_ARGON2_ITERATIONS,
|
||||||
|
DEFAULT_ARGON2_MEMORY,
|
||||||
|
DEFAULT_ARGON2_PARALLELISM,
|
||||||
|
KdfType,
|
||||||
|
} from "@bitwarden/common/enums/kdfType";
|
||||||
|
import { KdfConfig } from "@bitwarden/common/models/domain/kdf-config";
|
||||||
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
|
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -16,11 +24,12 @@ import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
|
||||||
})
|
})
|
||||||
export class ChangeKdfComponent implements OnInit {
|
export class ChangeKdfComponent implements OnInit {
|
||||||
masterPassword: string;
|
masterPassword: string;
|
||||||
kdfIterations: number;
|
|
||||||
kdf = KdfType.PBKDF2_SHA256;
|
kdf = KdfType.PBKDF2_SHA256;
|
||||||
|
kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG;
|
||||||
|
kdfType = KdfType;
|
||||||
kdfOptions: any[] = [];
|
kdfOptions: any[] = [];
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
recommendedKdfIterations = DEFAULT_PBKDF2_ITERATIONS;
|
recommendedPbkdf2Iterations = DEFAULT_PBKDF2_ITERATIONS;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
|
@ -31,12 +40,15 @@ export class ChangeKdfComponent implements OnInit {
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private stateService: StateService
|
private stateService: StateService
|
||||||
) {
|
) {
|
||||||
this.kdfOptions = [{ name: "PBKDF2 SHA-256", value: KdfType.PBKDF2_SHA256 }];
|
this.kdfOptions = [
|
||||||
|
{ name: "PBKDF2 SHA-256", value: KdfType.PBKDF2_SHA256 },
|
||||||
|
{ name: "Argon2id", value: KdfType.Argon2id },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.kdf = await this.stateService.getKdfType();
|
this.kdf = await this.stateService.getKdfType();
|
||||||
this.kdfIterations = await this.stateService.getKdfIterations();
|
this.kdfConfig = await this.stateService.getKdfConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
|
@ -46,25 +58,8 @@ export class ChangeKdfComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = new KdfRequest();
|
|
||||||
request.kdf = this.kdf;
|
|
||||||
request.kdfIterations = this.kdfIterations;
|
|
||||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
|
||||||
const email = await this.stateService.getEmail();
|
|
||||||
const newKey = await this.cryptoService.makeKey(
|
|
||||||
this.masterPassword,
|
|
||||||
email,
|
|
||||||
this.kdf,
|
|
||||||
this.kdfIterations
|
|
||||||
);
|
|
||||||
request.newMasterPasswordHash = await this.cryptoService.hashPassword(
|
|
||||||
this.masterPassword,
|
|
||||||
newKey
|
|
||||||
);
|
|
||||||
const newEncKey = await this.cryptoService.remakeEncKey(newKey);
|
|
||||||
request.key = newEncKey[1].encryptedString;
|
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.apiService.postAccountKdf(request);
|
this.formPromise = this.makeKeyAndSaveAsync();
|
||||||
await this.formPromise;
|
await this.formPromise;
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
|
@ -76,4 +71,42 @@ export class ChangeKdfComponent implements OnInit {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onChangeKdf(newValue: KdfType) {
|
||||||
|
if (newValue === KdfType.PBKDF2_SHA256) {
|
||||||
|
this.kdfConfig = new KdfConfig(DEFAULT_PBKDF2_ITERATIONS);
|
||||||
|
} else if (newValue === KdfType.Argon2id) {
|
||||||
|
this.kdfConfig = new KdfConfig(
|
||||||
|
DEFAULT_ARGON2_ITERATIONS,
|
||||||
|
DEFAULT_ARGON2_MEMORY,
|
||||||
|
DEFAULT_ARGON2_PARALLELISM
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error("Unknown KDF type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async makeKeyAndSaveAsync() {
|
||||||
|
const request = new KdfRequest();
|
||||||
|
request.kdf = this.kdf;
|
||||||
|
request.kdfIterations = this.kdfConfig.iterations;
|
||||||
|
request.kdfMemory = this.kdfConfig.memory;
|
||||||
|
request.kdfParallelism = this.kdfConfig.parallelism;
|
||||||
|
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
||||||
|
const email = await this.stateService.getEmail();
|
||||||
|
const newKey = await this.cryptoService.makeKey(
|
||||||
|
this.masterPassword,
|
||||||
|
email,
|
||||||
|
this.kdf,
|
||||||
|
this.kdfConfig
|
||||||
|
);
|
||||||
|
request.newMasterPasswordHash = await this.cryptoService.hashPassword(
|
||||||
|
this.masterPassword,
|
||||||
|
newKey
|
||||||
|
);
|
||||||
|
const newEncKey = await this.cryptoService.remakeEncKey(newKey);
|
||||||
|
request.key = newEncKey[1].encryptedString;
|
||||||
|
|
||||||
|
await this.apiService.postAccountKdf(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { KdfType } from "@bitwarden/common/enums/kdfType";
|
import { KdfType } from "@bitwarden/common/enums/kdfType";
|
||||||
import { PolicyData } from "@bitwarden/common/models/data/policy.data";
|
import { PolicyData } from "@bitwarden/common/models/data/policy.data";
|
||||||
|
import { KdfConfig } from "@bitwarden/common/models/domain/kdf-config";
|
||||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||||
import { EmergencyAccessPasswordRequest } from "@bitwarden/common/models/request/emergency-access-password.request";
|
import { EmergencyAccessPasswordRequest } from "@bitwarden/common/models/request/emergency-access-password.request";
|
||||||
|
@ -102,7 +103,11 @@ export class EmergencyAccessTakeoverComponent
|
||||||
this.masterPassword,
|
this.masterPassword,
|
||||||
this.email,
|
this.email,
|
||||||
takeoverResponse.kdf,
|
takeoverResponse.kdf,
|
||||||
takeoverResponse.kdfIterations
|
new KdfConfig(
|
||||||
|
takeoverResponse.kdfIterations,
|
||||||
|
takeoverResponse.kdfMemory,
|
||||||
|
takeoverResponse.kdfParallelism
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||||
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
||||||
import { DEFAULT_PBKDF2_ITERATIONS } from "@bitwarden/common/enums/kdfType";
|
import { KdfType, DEFAULT_PBKDF2_ITERATIONS } from "@bitwarden/common/enums/kdfType";
|
||||||
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
|
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
|
||||||
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||||
|
@ -393,9 +393,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
async isLowKdfIteration() {
|
async isLowKdfIteration() {
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
const kdfType = await this.stateService.getKdfType();
|
||||||
|
const kdfOptions = await this.stateService.getKdfConfig();
|
||||||
return kdfIterations < DEFAULT_PBKDF2_ITERATIONS;
|
return kdfType === KdfType.PBKDF2_SHA256 && kdfOptions.iterations < DEFAULT_PBKDF2_ITERATIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
get breadcrumbs(): TreeNode<CollectionFilter>[] {
|
get breadcrumbs(): TreeNode<CollectionFilter>[] {
|
||||||
|
|
|
@ -1150,7 +1150,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"kdfIterationsWarning": {
|
"kdfIterationsWarning": {
|
||||||
"message": "Setting your KDF iterations too high could result in poor performance when logging into (and unlocking) Bitwarden on devices with slower CPUs. We recommend that you increase the value in increments of $INCREMENT$ and then test all of your devices.",
|
"message": "Setting your KDF iterations too high could result in poor performance when logging into (and unlocking) Bitwarden on slower or older devices. We recommend that you increase the value in increments of $INCREMENT$ and then test all of your devices.",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"increment": {
|
"increment": {
|
||||||
"content": "$1",
|
"content": "$1",
|
||||||
|
@ -1158,6 +1158,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"kdfMemory": {
|
||||||
|
"message": "KDF memory (MB)",
|
||||||
|
"description": "Memory refers to computer memory (RAM). MB is short for megabytes."
|
||||||
|
},
|
||||||
|
"argon2Warning": {
|
||||||
|
"message": "Setting your KDF iterations, memory, and parallelism too high could result in poor performance when logging into (and unlocking) Bitwarden on slower or older devices. We recommend changing these individually in small increments and then test all of your devices."
|
||||||
|
},
|
||||||
|
"kdfParallelism": {
|
||||||
|
"message": "KDF parallelism"
|
||||||
|
},
|
||||||
|
"argon2Desc": {
|
||||||
|
"message": "Higher KDF iterations, memory, and parallelism can help protect your master password from being brute forced by an attacker."
|
||||||
|
},
|
||||||
"changeKdf": {
|
"changeKdf": {
|
||||||
"message": "Change KDF"
|
"message": "Change KDF"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { KdfType } from "@bitwarden/common/enums/kdfType";
|
import { KdfType } from "@bitwarden/common/enums/kdfType";
|
||||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||||
|
import { KdfConfig } from "@bitwarden/common/models/domain/kdf-config";
|
||||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
protected email: string;
|
protected email: string;
|
||||||
protected kdf: KdfType;
|
protected kdf: KdfType;
|
||||||
protected kdfIterations: number;
|
protected kdfConfig: KdfConfig;
|
||||||
|
|
||||||
protected destroy$ = new Subject<void>();
|
protected destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
@ -70,14 +71,14 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
|
||||||
if (this.kdf == null) {
|
if (this.kdf == null) {
|
||||||
this.kdf = await this.stateService.getKdfType();
|
this.kdf = await this.stateService.getKdfType();
|
||||||
}
|
}
|
||||||
if (this.kdfIterations == null) {
|
if (this.kdfConfig == null) {
|
||||||
this.kdfIterations = await this.stateService.getKdfIterations();
|
this.kdfConfig = await this.stateService.getKdfConfig();
|
||||||
}
|
}
|
||||||
const key = await this.cryptoService.makeKey(
|
const key = await this.cryptoService.makeKey(
|
||||||
this.masterPassword,
|
this.masterPassword,
|
||||||
email.trim().toLowerCase(),
|
email.trim().toLowerCase(),
|
||||||
this.kdf,
|
this.kdf,
|
||||||
this.kdfIterations
|
this.kdfConfig
|
||||||
);
|
);
|
||||||
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||||
|
|
||||||
|
|
|
@ -136,13 +136,13 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||||
let failed = true;
|
let failed = true;
|
||||||
try {
|
try {
|
||||||
const kdf = await this.stateService.getKdfType();
|
const kdf = await this.stateService.getKdfType();
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
if (this.pinSet[0]) {
|
if (this.pinSet[0]) {
|
||||||
const key = await this.cryptoService.makeKeyFromPin(
|
const key = await this.cryptoService.makeKeyFromPin(
|
||||||
this.pin,
|
this.pin,
|
||||||
this.email,
|
this.email,
|
||||||
kdf,
|
kdf,
|
||||||
kdfIterations,
|
kdfConfig,
|
||||||
await this.stateService.getDecryptedPinProtected()
|
await this.stateService.getDecryptedPinProtected()
|
||||||
);
|
);
|
||||||
const encKey = await this.cryptoService.getEncKey(key);
|
const encKey = await this.cryptoService.getEncKey(key);
|
||||||
|
@ -153,12 +153,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||||
await this.setKeyAndContinue(key);
|
await this.setKeyAndContinue(key);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const key = await this.cryptoService.makeKeyFromPin(
|
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfConfig);
|
||||||
this.pin,
|
|
||||||
this.email,
|
|
||||||
kdf,
|
|
||||||
kdfIterations
|
|
||||||
);
|
|
||||||
failed = false;
|
failed = false;
|
||||||
await this.setKeyAndContinue(key);
|
await this.setKeyAndContinue(key);
|
||||||
}
|
}
|
||||||
|
@ -194,14 +189,9 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
private async doUnlockWithMasterPassword() {
|
private async doUnlockWithMasterPassword() {
|
||||||
const kdf = await this.stateService.getKdfType();
|
const kdf = await this.stateService.getKdfType();
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
|
|
||||||
const key = await this.cryptoService.makeKey(
|
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfConfig);
|
||||||
this.masterPassword,
|
|
||||||
this.email,
|
|
||||||
kdf,
|
|
||||||
kdfIterations
|
|
||||||
);
|
|
||||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||||
|
|
||||||
let passwordValid = false;
|
let passwordValid = false;
|
||||||
|
@ -244,7 +234,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||||
const protectedPin = await this.stateService.getProtectedPin();
|
const protectedPin = await this.stateService.getProtectedPin();
|
||||||
const encKey = await this.cryptoService.getEncKey(key);
|
const encKey = await this.cryptoService.getEncKey(key);
|
||||||
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
|
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
|
||||||
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfIterations);
|
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfConfig);
|
||||||
await this.stateService.setDecryptedPinProtected(
|
await this.stateService.setDecryptedPinProtected(
|
||||||
await this.cryptoService.encrypt(key.key, pinKey)
|
await this.cryptoService.encrypt(key.key, pinKey)
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { DEFAULT_KDF_TYPE, DEFAULT_PBKDF2_ITERATIONS } from "@bitwarden/common/enums/kdfType";
|
import { DEFAULT_KDF_CONFIG, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
|
||||||
import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
|
import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
|
||||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||||
import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request";
|
import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request";
|
||||||
|
@ -269,8 +269,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||||
): Promise<RegisterRequest> {
|
): Promise<RegisterRequest> {
|
||||||
const hint = this.formGroup.value.hint;
|
const hint = this.formGroup.value.hint;
|
||||||
const kdf = DEFAULT_KDF_TYPE;
|
const kdf = DEFAULT_KDF_TYPE;
|
||||||
const kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
|
const kdfConfig = DEFAULT_KDF_CONFIG;
|
||||||
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
|
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfConfig);
|
||||||
const encKey = await this.cryptoService.makeEncKey(key);
|
const encKey = await this.cryptoService.makeEncKey(key);
|
||||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
||||||
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
|
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
|
||||||
|
@ -280,10 +280,12 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||||
hashedPassword,
|
hashedPassword,
|
||||||
hint,
|
hint,
|
||||||
encKey[1].encryptedString,
|
encKey[1].encryptedString,
|
||||||
kdf,
|
|
||||||
kdfIterations,
|
|
||||||
this.referenceData,
|
this.referenceData,
|
||||||
this.captchaToken
|
this.captchaToken,
|
||||||
|
kdf,
|
||||||
|
kdfConfig.iterations,
|
||||||
|
kdfConfig.memory,
|
||||||
|
kdfConfig.parallelism
|
||||||
);
|
);
|
||||||
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
|
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
|
||||||
const orgInvite = await this.stateService.getOrganizationInvitation();
|
const orgInvite = await this.stateService.getOrganizationInvitation();
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||||
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
|
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
|
||||||
import { DEFAULT_KDF_TYPE, DEFAULT_PBKDF2_ITERATIONS } from "@bitwarden/common/enums/kdfType";
|
import { DEFAULT_KDF_TYPE, DEFAULT_KDF_CONFIG } from "@bitwarden/common/enums/kdfType";
|
||||||
import { Utils } from "@bitwarden/common/misc/utils";
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||||
|
@ -93,7 +93,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||||
|
|
||||||
async setupSubmitActions() {
|
async setupSubmitActions() {
|
||||||
this.kdf = DEFAULT_KDF_TYPE;
|
this.kdf = DEFAULT_KDF_TYPE;
|
||||||
this.kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
|
this.kdfConfig = DEFAULT_KDF_CONFIG;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,10 +107,12 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||||
masterPasswordHash,
|
masterPasswordHash,
|
||||||
encKey[1].encryptedString,
|
encKey[1].encryptedString,
|
||||||
this.hint,
|
this.hint,
|
||||||
this.kdf,
|
|
||||||
this.kdfIterations,
|
|
||||||
this.identifier,
|
this.identifier,
|
||||||
new KeysRequest(keys[0], keys[1].encryptedString)
|
new KeysRequest(keys[0], keys[1].encryptedString),
|
||||||
|
this.kdf,
|
||||||
|
this.kdfConfig.iterations,
|
||||||
|
this.kdfConfig.memory,
|
||||||
|
this.kdfConfig.parallelism
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
if (this.resetPasswordAutoEnroll) {
|
if (this.resetPasswordAutoEnroll) {
|
||||||
|
@ -173,7 +175,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||||
keys: [string, EncString]
|
keys: [string, EncString]
|
||||||
) {
|
) {
|
||||||
await this.stateService.setKdfType(this.kdf);
|
await this.stateService.setKdfType(this.kdf);
|
||||||
await this.stateService.setKdfIterations(this.kdfIterations);
|
await this.stateService.setKdfConfig(this.kdfConfig);
|
||||||
await this.cryptoService.setKey(key);
|
await this.cryptoService.setKey(key);
|
||||||
await this.cryptoService.setEncKey(encKey[1].encryptedString);
|
await this.cryptoService.setEncKey(encKey[1].encryptedString);
|
||||||
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
|
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
|
||||||
|
|
|
@ -36,9 +36,9 @@ export class SetPinComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
const kdf = await this.stateService.getKdfType();
|
const kdf = await this.stateService.getKdfType();
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
const email = await this.stateService.getEmail();
|
const email = await this.stateService.getEmail();
|
||||||
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations);
|
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfConfig);
|
||||||
const key = await this.cryptoService.getKey();
|
const key = await this.cryptoService.getKey();
|
||||||
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
|
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
|
||||||
if (this.masterPassOnRestart) {
|
if (this.masterPassOnRestart) {
|
||||||
|
|
|
@ -86,7 +86,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.kdf = await this.stateService.getKdfType();
|
this.kdf = await this.stateService.getKdfType();
|
||||||
this.kdfIterations = await this.stateService.getKdfIterations();
|
this.kdfConfig = await this.stateService.getKdfConfig();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
|
||||||
async setupSubmitActions(): Promise<boolean> {
|
async setupSubmitActions(): Promise<boolean> {
|
||||||
this.email = await this.stateService.getEmail();
|
this.email = await this.stateService.getEmail();
|
||||||
this.kdf = await this.stateService.getKdfType();
|
this.kdf = await this.stateService.getKdfType();
|
||||||
this.kdfIterations = await this.stateService.getKdfIterations();
|
this.kdfConfig = await this.stateService.getKdfConfig();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
|
||||||
this.masterPassword,
|
this.masterPassword,
|
||||||
this.email.trim().toLowerCase(),
|
this.email.trim().toLowerCase(),
|
||||||
this.kdf,
|
this.kdf,
|
||||||
this.kdfIterations
|
this.kdfConfig
|
||||||
);
|
);
|
||||||
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
|
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { KdfType } from "../enums/kdfType";
|
||||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||||
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
|
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
|
||||||
import { EncString } from "../models/domain/enc-string";
|
import { EncString } from "../models/domain/enc-string";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||||
import { ProfileOrganizationResponse } from "../models/response/profile-organization.response";
|
import { ProfileOrganizationResponse } from "../models/response/profile-organization.response";
|
||||||
import { ProfileProviderOrganizationResponse } from "../models/response/profile-provider-organization.response";
|
import { ProfileProviderOrganizationResponse } from "../models/response/profile-provider-organization.response";
|
||||||
|
@ -47,13 +48,13 @@ export abstract class CryptoService {
|
||||||
password: string,
|
password: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfIterations: number
|
kdfConfig: KdfConfig
|
||||||
) => Promise<SymmetricCryptoKey>;
|
) => Promise<SymmetricCryptoKey>;
|
||||||
makeKeyFromPin: (
|
makeKeyFromPin: (
|
||||||
pin: string,
|
pin: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfIterations: number,
|
kdfConfig: KdfConfig,
|
||||||
protectedKeyCs?: EncString
|
protectedKeyCs?: EncString
|
||||||
) => Promise<SymmetricCryptoKey>;
|
) => Promise<SymmetricCryptoKey>;
|
||||||
makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>;
|
makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>;
|
||||||
|
@ -62,7 +63,7 @@ export abstract class CryptoService {
|
||||||
pin: string,
|
pin: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfIterations: number
|
kdfConfig: KdfConfig
|
||||||
) => Promise<SymmetricCryptoKey>;
|
) => Promise<SymmetricCryptoKey>;
|
||||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||||
hashPassword: (
|
hashPassword: (
|
||||||
|
|
|
@ -61,6 +61,8 @@ export class OrganizationUserDetailsResponse extends OrganizationUserResponse {
|
||||||
export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse {
|
export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse {
|
||||||
kdf: KdfType;
|
kdf: KdfType;
|
||||||
kdfIterations: number;
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
resetPasswordKey: string;
|
resetPasswordKey: string;
|
||||||
encryptedPrivateKey: string;
|
encryptedPrivateKey: string;
|
||||||
|
|
||||||
|
@ -68,6 +70,8 @@ export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse {
|
||||||
super(response);
|
super(response);
|
||||||
this.kdf = this.getResponseProperty("Kdf");
|
this.kdf = this.getResponseProperty("Kdf");
|
||||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||||
|
this.kdfMemory = this.getResponseProperty("KdfMemory");
|
||||||
|
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
|
||||||
this.resetPasswordKey = this.getResponseProperty("ResetPasswordKey");
|
this.resetPasswordKey = this.getResponseProperty("ResetPasswordKey");
|
||||||
this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey");
|
this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey");
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { Account, AccountSettingsSettings } from "../models/domain/account";
|
||||||
import { EncString } from "../models/domain/enc-string";
|
import { EncString } from "../models/domain/enc-string";
|
||||||
import { EnvironmentUrls } from "../models/domain/environment-urls";
|
import { EnvironmentUrls } from "../models/domain/environment-urls";
|
||||||
import { GeneratedPasswordHistory } from "../models/domain/generated-password-history";
|
import { GeneratedPasswordHistory } from "../models/domain/generated-password-history";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import { Policy } from "../models/domain/policy";
|
import { Policy } from "../models/domain/policy";
|
||||||
import { StorageOptions } from "../models/domain/storage-options";
|
import { StorageOptions } from "../models/domain/storage-options";
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||||
|
@ -252,8 +253,8 @@ export abstract class StateService<T extends Account = Account> {
|
||||||
getInstalledVersion: (options?: StorageOptions) => Promise<string>;
|
getInstalledVersion: (options?: StorageOptions) => Promise<string>;
|
||||||
setInstalledVersion: (value: string, options?: StorageOptions) => Promise<void>;
|
setInstalledVersion: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
|
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
|
||||||
getKdfIterations: (options?: StorageOptions) => Promise<number>;
|
getKdfConfig: (options?: StorageOptions) => Promise<KdfConfig>;
|
||||||
setKdfIterations: (value: number, options?: StorageOptions) => Promise<void>;
|
setKdfConfig: (kdfConfig: KdfConfig, options?: StorageOptions) => Promise<void>;
|
||||||
getKdfType: (options?: StorageOptions) => Promise<KdfType>;
|
getKdfType: (options?: StorageOptions) => Promise<KdfType>;
|
||||||
setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>;
|
setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>;
|
||||||
getKeyHash: (options?: StorageOptions) => Promise<string>;
|
getKeyHash: (options?: StorageOptions) => Promise<string>;
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
|
|
||||||
export enum KdfType {
|
export enum KdfType {
|
||||||
PBKDF2_SHA256 = 0,
|
PBKDF2_SHA256 = 0,
|
||||||
Argon2id = 1,
|
Argon2id = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_ARGON2_MEMORY = 19;
|
export const DEFAULT_ARGON2_MEMORY = 64;
|
||||||
export const DEFAULT_ARGON2_PARALLELISM = 1;
|
export const DEFAULT_ARGON2_PARALLELISM = 4;
|
||||||
export const DEFAULT_ARGON2_ITERATIONS = 2;
|
export const DEFAULT_ARGON2_ITERATIONS = 3;
|
||||||
|
|
||||||
export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256;
|
export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256;
|
||||||
export const DEFAULT_PBKDF2_ITERATIONS = 600000;
|
export const DEFAULT_PBKDF2_ITERATIONS = 600000;
|
||||||
|
export const DEFAULT_KDF_CONFIG = new KdfConfig(DEFAULT_PBKDF2_ITERATIONS);
|
||||||
export const SEND_KDF_ITERATIONS = 100000;
|
export const SEND_KDF_ITERATIONS = 100000;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { I18nService } from "../abstractions/i18n.service";
|
||||||
import { KdfType } from "../enums/kdfType";
|
import { KdfType } from "../enums/kdfType";
|
||||||
import { EncString } from "../models/domain/enc-string";
|
import { EncString } from "../models/domain/enc-string";
|
||||||
import { ImportResult } from "../models/domain/import-result";
|
import { ImportResult } from "../models/domain/import-result";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
||||||
|
@ -49,7 +50,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
||||||
this.password,
|
this.password,
|
||||||
jdoc.salt,
|
jdoc.salt,
|
||||||
KdfType.PBKDF2_SHA256,
|
KdfType.PBKDF2_SHA256,
|
||||||
jdoc.kdfIterations
|
new KdfConfig(jdoc.kdfIterations)
|
||||||
);
|
);
|
||||||
|
|
||||||
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
||||||
|
|
|
@ -107,6 +107,8 @@ export abstract class LogInStrategy {
|
||||||
email: accountInformation.email,
|
email: accountInformation.email,
|
||||||
hasPremiumPersonally: accountInformation.premium,
|
hasPremiumPersonally: accountInformation.premium,
|
||||||
kdfIterations: tokenResponse.kdfIterations,
|
kdfIterations: tokenResponse.kdfIterations,
|
||||||
|
kdfMemory: tokenResponse.kdfMemory,
|
||||||
|
kdfParallelism: tokenResponse.kdfParallelism,
|
||||||
kdfType: tokenResponse.kdf,
|
kdfType: tokenResponse.kdf,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -189,6 +189,8 @@ export class AccountProfile {
|
||||||
usesKeyConnector?: boolean;
|
usesKeyConnector?: boolean;
|
||||||
keyHash?: string;
|
keyHash?: string;
|
||||||
kdfIterations?: number;
|
kdfIterations?: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
kdfType?: KdfType;
|
kdfType?: KdfType;
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<AccountProfile>): AccountProfile {
|
static fromJSON(obj: Jsonify<AccountProfile>): AccountProfile {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class KdfConfig {
|
||||||
|
iterations: number;
|
||||||
|
memory?: number;
|
||||||
|
parallelism?: number;
|
||||||
|
|
||||||
|
constructor(iterations: number, memory?: number, parallelism?: number) {
|
||||||
|
this.iterations = iterations;
|
||||||
|
this.memory = memory;
|
||||||
|
this.parallelism = parallelism;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,4 +5,6 @@ import { PasswordRequest } from "./password.request";
|
||||||
export class KdfRequest extends PasswordRequest {
|
export class KdfRequest extends PasswordRequest {
|
||||||
kdf: KdfType;
|
kdf: KdfType;
|
||||||
kdfIterations: number;
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,12 @@ export class RegisterRequest implements CaptchaProtectedRequest {
|
||||||
public masterPasswordHash: string,
|
public masterPasswordHash: string,
|
||||||
masterPasswordHint: string,
|
masterPasswordHint: string,
|
||||||
public key: string,
|
public key: string,
|
||||||
|
public referenceData: ReferenceEventRequest,
|
||||||
|
public captchaResponse: string,
|
||||||
public kdf: KdfType,
|
public kdf: KdfType,
|
||||||
public kdfIterations: number,
|
public kdfIterations: number,
|
||||||
public referenceData: ReferenceEventRequest,
|
public kdfMemory?: number,
|
||||||
public captchaResponse: string
|
public kdfParallelism?: number
|
||||||
) {
|
) {
|
||||||
this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null;
|
this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,22 +9,28 @@ export class SetPasswordRequest {
|
||||||
keys: KeysRequest;
|
keys: KeysRequest;
|
||||||
kdf: KdfType;
|
kdf: KdfType;
|
||||||
kdfIterations: number;
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
orgIdentifier: string;
|
orgIdentifier: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
masterPasswordHash: string,
|
masterPasswordHash: string,
|
||||||
key: string,
|
key: string,
|
||||||
masterPasswordHint: string,
|
masterPasswordHint: string,
|
||||||
|
orgIdentifier: string,
|
||||||
|
keys: KeysRequest,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfIterations: number,
|
kdfIterations: number,
|
||||||
orgIdentifier: string,
|
kdfMemory?: number,
|
||||||
keys: KeysRequest
|
kdfParallelism?: number
|
||||||
) {
|
) {
|
||||||
this.masterPasswordHash = masterPasswordHash;
|
this.masterPasswordHash = masterPasswordHash;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.masterPasswordHint = masterPasswordHint;
|
this.masterPasswordHint = masterPasswordHint;
|
||||||
this.kdf = kdf;
|
this.kdf = kdf;
|
||||||
this.kdfIterations = kdfIterations;
|
this.kdfIterations = kdfIterations;
|
||||||
|
this.kdfMemory = kdfMemory;
|
||||||
|
this.kdfParallelism = kdfParallelism;
|
||||||
this.orgIdentifier = orgIdentifier;
|
this.orgIdentifier = orgIdentifier;
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,8 @@ export class EmergencyAccessTakeoverResponse extends BaseResponse {
|
||||||
keyEncrypted: string;
|
keyEncrypted: string;
|
||||||
kdf: KdfType;
|
kdf: KdfType;
|
||||||
kdfIterations: number;
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
|
@ -66,6 +68,8 @@ export class EmergencyAccessTakeoverResponse extends BaseResponse {
|
||||||
this.keyEncrypted = this.getResponseProperty("KeyEncrypted");
|
this.keyEncrypted = this.getResponseProperty("KeyEncrypted");
|
||||||
this.kdf = this.getResponseProperty("Kdf");
|
this.kdf = this.getResponseProperty("Kdf");
|
||||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||||
|
this.kdfMemory = this.getResponseProperty("KdfMemory");
|
||||||
|
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||||
twoFactorToken: string;
|
twoFactorToken: string;
|
||||||
kdf: KdfType;
|
kdf: KdfType;
|
||||||
kdfIterations: number;
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
forcePasswordReset: boolean;
|
forcePasswordReset: boolean;
|
||||||
apiUseKeyConnector: boolean;
|
apiUseKeyConnector: boolean;
|
||||||
keyConnectorUrl: string;
|
keyConnectorUrl: string;
|
||||||
|
@ -31,6 +33,8 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||||
this.twoFactorToken = this.getResponseProperty("TwoFactorToken");
|
this.twoFactorToken = this.getResponseProperty("TwoFactorToken");
|
||||||
this.kdf = this.getResponseProperty("Kdf");
|
this.kdf = this.getResponseProperty("Kdf");
|
||||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||||
|
this.kdfMemory = this.getResponseProperty("KdfMemory");
|
||||||
|
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
|
||||||
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset");
|
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset");
|
||||||
this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector");
|
this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector");
|
||||||
this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl");
|
this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl");
|
||||||
|
|
|
@ -5,10 +5,14 @@ import { BaseResponse } from "./base.response";
|
||||||
export class PreloginResponse extends BaseResponse {
|
export class PreloginResponse extends BaseResponse {
|
||||||
kdf: KdfType;
|
kdf: KdfType;
|
||||||
kdfIterations: number;
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.kdf = this.getResponseProperty("Kdf");
|
this.kdf = this.getResponseProperty("Kdf");
|
||||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||||
|
this.kdfMemory = this.getResponseProperty("KdfMemory");
|
||||||
|
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessL
|
||||||
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
|
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
|
||||||
import { UserApiLogInStrategy } from "../misc/logInStrategies/user-api-login.strategy";
|
import { UserApiLogInStrategy } from "../misc/logInStrategies/user-api-login.strategy";
|
||||||
import { AuthResult } from "../models/domain/auth-result";
|
import { AuthResult } from "../models/domain/auth-result";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import {
|
import {
|
||||||
UserApiLogInCredentials,
|
UserApiLogInCredentials,
|
||||||
PasswordLogInCredentials,
|
PasswordLogInCredentials,
|
||||||
|
@ -247,19 +248,23 @@ export class AuthService implements AuthServiceAbstraction {
|
||||||
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
|
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
|
||||||
email = email.trim().toLowerCase();
|
email = email.trim().toLowerCase();
|
||||||
let kdf: KdfType = null;
|
let kdf: KdfType = null;
|
||||||
let kdfIterations: number = null;
|
let kdfConfig: KdfConfig = null;
|
||||||
try {
|
try {
|
||||||
const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email));
|
const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email));
|
||||||
if (preloginResponse != null) {
|
if (preloginResponse != null) {
|
||||||
kdf = preloginResponse.kdf;
|
kdf = preloginResponse.kdf;
|
||||||
kdfIterations = preloginResponse.kdfIterations;
|
kdfConfig = new KdfConfig(
|
||||||
|
preloginResponse.kdfIterations,
|
||||||
|
preloginResponse.kdfMemory,
|
||||||
|
preloginResponse.kdfParallelism
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e == null || e.statusCode !== 404) {
|
if (e == null || e.statusCode !== 404) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
|
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
async authResponsePushNotifiction(notification: AuthRequestPushNotification): Promise<any> {
|
async authResponsePushNotifiction(notification: AuthRequestPushNotification): Promise<any> {
|
||||||
|
|
|
@ -8,7 +8,12 @@ import { PlatformUtilsService } from "../abstractions/platformUtils.service";
|
||||||
import { StateService } from "../abstractions/state.service";
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { EncryptionType } from "../enums/encryptionType";
|
import { EncryptionType } from "../enums/encryptionType";
|
||||||
import { HashPurpose } from "../enums/hashPurpose";
|
import { HashPurpose } from "../enums/hashPurpose";
|
||||||
import { DEFAULT_ARGON2_ITERATIONS, KdfType } from "../enums/kdfType";
|
import {
|
||||||
|
DEFAULT_ARGON2_ITERATIONS,
|
||||||
|
DEFAULT_ARGON2_MEMORY,
|
||||||
|
DEFAULT_ARGON2_PARALLELISM,
|
||||||
|
KdfType,
|
||||||
|
} from "../enums/kdfType";
|
||||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||||
import { sequentialize } from "../misc/sequentialize";
|
import { sequentialize } from "../misc/sequentialize";
|
||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
|
@ -17,6 +22,7 @@ import { EncryptedOrganizationKeyData } from "../models/data/encrypted-organizat
|
||||||
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
|
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
|
||||||
import { EncString } from "../models/domain/enc-string";
|
import { EncString } from "../models/domain/enc-string";
|
||||||
import { BaseEncryptedOrganizationKey } from "../models/domain/encrypted-organization-key";
|
import { BaseEncryptedOrganizationKey } from "../models/domain/encrypted-organization-key";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||||
import { ProfileOrganizationResponse } from "../models/response/profile-organization.response";
|
import { ProfileOrganizationResponse } from "../models/response/profile-organization.response";
|
||||||
import { ProfileProviderOrganizationResponse } from "../models/response/profile-provider-organization.response";
|
import { ProfileProviderOrganizationResponse } from "../models/response/profile-provider-organization.response";
|
||||||
|
@ -408,28 +414,44 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
password: string,
|
password: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfIterations: number
|
kdfConfig: KdfConfig
|
||||||
): Promise<SymmetricCryptoKey> {
|
): Promise<SymmetricCryptoKey> {
|
||||||
let key: ArrayBuffer = null;
|
let key: ArrayBuffer = null;
|
||||||
if (kdf == null || kdf === KdfType.PBKDF2_SHA256) {
|
if (kdf == null || kdf === KdfType.PBKDF2_SHA256) {
|
||||||
if (kdfIterations == null) {
|
if (kdfConfig.iterations == null) {
|
||||||
kdfIterations = 5000;
|
kdfConfig.iterations = 5000;
|
||||||
} else if (kdfIterations < 5000) {
|
} else if (kdfConfig.iterations < 5000) {
|
||||||
throw new Error("PBKDF2 iteration minimum is 5000.");
|
throw new Error("PBKDF2 iteration minimum is 5000.");
|
||||||
}
|
}
|
||||||
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfIterations);
|
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfConfig.iterations);
|
||||||
} else if (kdf == KdfType.Argon2id) {
|
} else if (kdf == KdfType.Argon2id) {
|
||||||
if (kdfIterations == null) {
|
if (kdfConfig.iterations == null) {
|
||||||
kdfIterations = DEFAULT_ARGON2_ITERATIONS;
|
kdfConfig.iterations = DEFAULT_ARGON2_ITERATIONS;
|
||||||
} else if (kdfIterations < 2) {
|
} else if (kdfConfig.iterations < 2) {
|
||||||
throw new Error("Argon2 iteration minimum is 2.");
|
throw new Error("Argon2 iteration minimum is 2.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kdfConfig.memory == null) {
|
||||||
|
kdfConfig.memory = DEFAULT_ARGON2_MEMORY;
|
||||||
|
} else if (kdfConfig.memory < 16) {
|
||||||
|
throw new Error("Argon2 memory minimum is 16 MB");
|
||||||
|
} else if (kdfConfig.memory > 1024) {
|
||||||
|
throw new Error("Argon2 memory maximum is 1024 MB");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kdfConfig.parallelism == null) {
|
||||||
|
kdfConfig.parallelism = DEFAULT_ARGON2_PARALLELISM;
|
||||||
|
} else if (kdfConfig.parallelism < 1) {
|
||||||
|
throw new Error("Argon2 parallelism minimum is 1.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const saltHash = await this.cryptoFunctionService.hash(salt, "sha256");
|
||||||
key = await this.cryptoFunctionService.argon2(
|
key = await this.cryptoFunctionService.argon2(
|
||||||
password,
|
password,
|
||||||
salt,
|
saltHash,
|
||||||
kdfIterations,
|
kdfConfig.iterations,
|
||||||
16 * 1024, // convert to KiB from MiB
|
kdfConfig.memory * 1024, // convert to KiB from MiB
|
||||||
2
|
kdfConfig.parallelism
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unknown Kdf.");
|
throw new Error("Unknown Kdf.");
|
||||||
|
@ -441,7 +463,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
pin: string,
|
pin: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfIterations: number,
|
kdfConfig: KdfConfig,
|
||||||
protectedKeyCs: EncString = null
|
protectedKeyCs: EncString = null
|
||||||
): Promise<SymmetricCryptoKey> {
|
): Promise<SymmetricCryptoKey> {
|
||||||
if (protectedKeyCs == null) {
|
if (protectedKeyCs == null) {
|
||||||
|
@ -451,7 +473,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
}
|
}
|
||||||
protectedKeyCs = new EncString(pinProtectedKey);
|
protectedKeyCs = new EncString(pinProtectedKey);
|
||||||
}
|
}
|
||||||
const pinKey = await this.makePinKey(pin, salt, kdf, kdfIterations);
|
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig);
|
||||||
const decKey = await this.decryptToBytes(protectedKeyCs, pinKey);
|
const decKey = await this.decryptToBytes(protectedKeyCs, pinKey);
|
||||||
return new SymmetricCryptoKey(decKey);
|
return new SymmetricCryptoKey(decKey);
|
||||||
}
|
}
|
||||||
|
@ -474,9 +496,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
pin: string,
|
pin: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfIterations: number
|
kdfConfig: KdfConfig
|
||||||
): Promise<SymmetricCryptoKey> {
|
): Promise<SymmetricCryptoKey> {
|
||||||
const pinKey = await this.makeKey(pin, salt, kdf, kdfIterations);
|
const pinKey = await this.makeKey(pin, salt, kdf, kdfConfig);
|
||||||
return await this.stretchKey(pinKey);
|
return await this.stretchKey(pinKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { CollectionData } from "../models/data/collection.data";
|
||||||
import { Cipher } from "../models/domain/cipher";
|
import { Cipher } from "../models/domain/cipher";
|
||||||
import { Collection } from "../models/domain/collection";
|
import { Collection } from "../models/domain/collection";
|
||||||
import { Folder } from "../models/domain/folder";
|
import { Folder } from "../models/domain/folder";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import { CipherWithIdExport as CipherExport } from "../models/export/cipher-with-ids.export";
|
import { CipherWithIdExport as CipherExport } from "../models/export/cipher-with-ids.export";
|
||||||
import { CollectionWithIdExport as CollectionExport } from "../models/export/collection-with-id.export";
|
import { CollectionWithIdExport as CollectionExport } from "../models/export/collection-with-id.export";
|
||||||
import { EventExport } from "../models/export/event.export";
|
import { EventExport } from "../models/export/event.export";
|
||||||
|
@ -54,12 +55,12 @@ export class ExportService implements ExportServiceAbstraction {
|
||||||
: await this.getExport("json");
|
: await this.getExport("json");
|
||||||
|
|
||||||
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
||||||
const kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
|
const kdfConfig = new KdfConfig(DEFAULT_PBKDF2_ITERATIONS);
|
||||||
const key = await this.cryptoService.makePinKey(
|
const key = await this.cryptoService.makePinKey(
|
||||||
password,
|
password,
|
||||||
salt,
|
salt,
|
||||||
KdfType.PBKDF2_SHA256,
|
KdfType.PBKDF2_SHA256,
|
||||||
kdfIterations
|
kdfConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), key);
|
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), key);
|
||||||
|
@ -69,7 +70,7 @@ export class ExportService implements ExportServiceAbstraction {
|
||||||
encrypted: true,
|
encrypted: true,
|
||||||
passwordProtected: true,
|
passwordProtected: true,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
kdfIterations: kdfIterations,
|
kdfIterations: kdfConfig.iterations,
|
||||||
kdfType: KdfType.PBKDF2_SHA256,
|
kdfType: KdfType.PBKDF2_SHA256,
|
||||||
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
||||||
data: encText.encryptedString,
|
data: encText.encryptedString,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { StateService } from "../abstractions/state.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { OrganizationUserType } from "../enums/organizationUserType";
|
import { OrganizationUserType } from "../enums/organizationUserType";
|
||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||||
import { SetKeyConnectorKeyRequest } from "../models/request/account/set-key-connector-key.request";
|
import { SetKeyConnectorKeyRequest } from "../models/request/account/set-key-connector-key.request";
|
||||||
import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request";
|
import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request";
|
||||||
|
@ -82,14 +83,14 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async convertNewSsoUserToKeyConnector(tokenResponse: IdentityTokenResponse, orgId: string) {
|
async convertNewSsoUserToKeyConnector(tokenResponse: IdentityTokenResponse, orgId: string) {
|
||||||
const { kdf, kdfIterations, keyConnectorUrl } = tokenResponse;
|
const { kdf, kdfIterations, kdfMemory, kdfParallelism, keyConnectorUrl } = tokenResponse;
|
||||||
const password = await this.cryptoFunctionService.randomBytes(64);
|
const password = await this.cryptoFunctionService.randomBytes(64);
|
||||||
|
|
||||||
const k = await this.cryptoService.makeKey(
|
const k = await this.cryptoService.makeKey(
|
||||||
Utils.fromBufferToB64(password),
|
Utils.fromBufferToB64(password),
|
||||||
await this.tokenService.getEmail(),
|
await this.tokenService.getEmail(),
|
||||||
kdf,
|
kdf,
|
||||||
kdfIterations
|
new KdfConfig(kdfIterations, kdfMemory, kdfParallelism)
|
||||||
);
|
);
|
||||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64);
|
const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64);
|
||||||
await this.cryptoService.setKey(k);
|
await this.cryptoService.setKey(k);
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { EncString } from "../models/domain/enc-string";
|
||||||
import { EnvironmentUrls } from "../models/domain/environment-urls";
|
import { EnvironmentUrls } from "../models/domain/environment-urls";
|
||||||
import { GeneratedPasswordHistory } from "../models/domain/generated-password-history";
|
import { GeneratedPasswordHistory } from "../models/domain/generated-password-history";
|
||||||
import { GlobalState } from "../models/domain/global-state";
|
import { GlobalState } from "../models/domain/global-state";
|
||||||
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
import { Policy } from "../models/domain/policy";
|
import { Policy } from "../models/domain/policy";
|
||||||
import { State } from "../models/domain/state";
|
import { State } from "../models/domain/state";
|
||||||
import { StorageOptions } from "../models/domain/storage-options";
|
import { StorageOptions } from "../models/domain/storage-options";
|
||||||
|
@ -1657,17 +1658,26 @@ export class StateService<
|
||||||
return (await this.getAccessToken(options)) != null && (await this.getUserId(options)) != null;
|
return (await this.getAccessToken(options)) != null && (await this.getUserId(options)) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getKdfIterations(options?: StorageOptions): Promise<number> {
|
async getKdfConfig(options?: StorageOptions): Promise<KdfConfig> {
|
||||||
return (
|
const iterations = (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
)?.profile?.kdfIterations;
|
)?.profile?.kdfIterations;
|
||||||
|
const memory = (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.profile?.kdfMemory;
|
||||||
|
const parallelism = (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.profile?.kdfParallelism;
|
||||||
|
return new KdfConfig(iterations, memory, parallelism);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setKdfIterations(value: number, options?: StorageOptions): Promise<void> {
|
async setKdfConfig(config: KdfConfig, options?: StorageOptions): Promise<void> {
|
||||||
const account = await this.getAccount(
|
const account = await this.getAccount(
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
);
|
);
|
||||||
account.profile.kdfIterations = value;
|
account.profile.kdfIterations = config.iterations;
|
||||||
|
account.profile.kdfMemory = config.memory;
|
||||||
|
account.profile.kdfParallelism = config.parallelism;
|
||||||
await this.saveAccount(
|
await this.saveAccount(
|
||||||
account,
|
account,
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
|
Loading…
Reference in New Issue