mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[PS-2264] Make password protected exports support account's iterations and argon2 (#4479)
* Fix encrypted export using fixed PBKDF2 iterations * Replace hardcoded KdfType in importer * Clean up kdf handling in password-protected export * Extract BitwardenPasswordProtectedFileFormat * Rename bitwarden-json-types * Move StateService import to fix linting issue * Make linter happy * Use abstraction instead of implementation --------- Co-authored-by: Daniel James Smith <djsmith@web.de>
This commit is contained in:
@@ -466,7 +466,8 @@ export default class MainBackground {
|
|||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.cryptoFunctionService
|
this.cryptoFunctionService,
|
||||||
|
this.stateService
|
||||||
);
|
);
|
||||||
this.notificationsService = new NotificationsService(
|
this.notificationsService = new NotificationsService(
|
||||||
this.syncService,
|
this.syncService,
|
||||||
|
|||||||
@@ -378,7 +378,8 @@ export class Main {
|
|||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.cryptoFunctionService
|
this.cryptoFunctionService,
|
||||||
|
this.stateService
|
||||||
);
|
);
|
||||||
|
|
||||||
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
||||||
|
|||||||
@@ -472,6 +472,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
|||||||
ApiServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
CryptoServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
CryptoFunctionServiceAbstraction,
|
CryptoFunctionServiceAbstraction,
|
||||||
|
StateServiceAbstraction,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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 { CipherWithIdExport as CipherExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
|
import { CipherWithIdExport as CipherExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
|
||||||
import { ExportService } from "@bitwarden/common/services/export.service";
|
import { ExportService } from "@bitwarden/common/services/export.service";
|
||||||
|
import { StateService } from "@bitwarden/common/services/state.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
@@ -144,6 +145,7 @@ describe("ExportService", () => {
|
|||||||
let cipherService: SubstituteOf<CipherService>;
|
let cipherService: SubstituteOf<CipherService>;
|
||||||
let folderService: SubstituteOf<FolderService>;
|
let folderService: SubstituteOf<FolderService>;
|
||||||
let cryptoService: SubstituteOf<CryptoService>;
|
let cryptoService: SubstituteOf<CryptoService>;
|
||||||
|
let stateService: SubstituteOf<StateService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
apiService = Substitute.for<ApiService>();
|
apiService = Substitute.for<ApiService>();
|
||||||
@@ -160,7 +162,8 @@ describe("ExportService", () => {
|
|||||||
cipherService,
|
cipherService,
|
||||||
apiService,
|
apiService,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
cryptoFunctionService
|
cryptoFunctionService,
|
||||||
|
stateService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as papa from "papaparse";
|
import * as papa from "papaparse";
|
||||||
|
|
||||||
|
import { BitwardenPasswordProtectedFileFormat } from "@bitwarden/importer/src/importers/bitwarden/bitwarden-password-protected-types";
|
||||||
|
|
||||||
import { ApiService } from "../abstractions/api.service";
|
import { ApiService } from "../abstractions/api.service";
|
||||||
import { CryptoService } from "../abstractions/crypto.service";
|
import { CryptoService } from "../abstractions/crypto.service";
|
||||||
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
|
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
|
||||||
@@ -7,12 +9,13 @@ import {
|
|||||||
ExportFormat,
|
ExportFormat,
|
||||||
ExportService as ExportServiceAbstraction,
|
ExportService as ExportServiceAbstraction,
|
||||||
} from "../abstractions/export.service";
|
} from "../abstractions/export.service";
|
||||||
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { CollectionData } from "../admin-console/models/data/collection.data";
|
import { CollectionData } from "../admin-console/models/data/collection.data";
|
||||||
import { Collection } from "../admin-console/models/domain/collection";
|
import { Collection } from "../admin-console/models/domain/collection";
|
||||||
import { CollectionDetailsResponse } from "../admin-console/models/response/collection.response";
|
import { CollectionDetailsResponse } from "../admin-console/models/response/collection.response";
|
||||||
import { CollectionView } from "../admin-console/models/view/collection.view";
|
import { CollectionView } from "../admin-console/models/view/collection.view";
|
||||||
import { KdfConfig } from "../auth/models/domain/kdf-config";
|
import { KdfConfig } from "../auth/models/domain/kdf-config";
|
||||||
import { DEFAULT_PBKDF2_ITERATIONS, KdfType } from "../enums/kdfType";
|
import { KdfType } from "../enums/kdfType";
|
||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
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";
|
||||||
@@ -34,7 +37,8 @@ export class ExportService implements ExportServiceAbstraction {
|
|||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private cryptoFunctionService: CryptoFunctionService
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
private stateService: StateService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getExport(format: ExportFormat = "csv", organizationId?: string): Promise<string> {
|
async getExport(format: ExportFormat = "csv", organizationId?: string): Promise<string> {
|
||||||
@@ -54,24 +58,23 @@ export class ExportService implements ExportServiceAbstraction {
|
|||||||
? await this.getOrganizationExport(organizationId, "json")
|
? await this.getOrganizationExport(organizationId, "json")
|
||||||
: await this.getExport("json");
|
: await this.getExport("json");
|
||||||
|
|
||||||
|
const kdfType: KdfType = await this.stateService.getKdfType();
|
||||||
|
const kdfConfig: KdfConfig = await this.stateService.getKdfConfig();
|
||||||
|
|
||||||
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
||||||
const kdfConfig = new KdfConfig(DEFAULT_PBKDF2_ITERATIONS);
|
const key = await this.cryptoService.makePinKey(password, salt, kdfType, kdfConfig);
|
||||||
const key = await this.cryptoService.makePinKey(
|
|
||||||
password,
|
|
||||||
salt,
|
|
||||||
KdfType.PBKDF2_SHA256,
|
|
||||||
kdfConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), key);
|
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), key);
|
||||||
const encText = await this.cryptoService.encrypt(clearText, key);
|
const encText = await this.cryptoService.encrypt(clearText, key);
|
||||||
|
|
||||||
const jsonDoc: any = {
|
const jsonDoc: BitwardenPasswordProtectedFileFormat = {
|
||||||
encrypted: true,
|
encrypted: true,
|
||||||
passwordProtected: true,
|
passwordProtected: true,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
|
kdfType: kdfType,
|
||||||
kdfIterations: kdfConfig.iterations,
|
kdfIterations: kdfConfig.iterations,
|
||||||
kdfType: KdfType.PBKDF2_SHA256,
|
kdfMemory: kdfConfig.memory,
|
||||||
|
kdfParallelism: kdfConfig.parallelism,
|
||||||
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
||||||
data: encText.encryptedString,
|
data: encText.encryptedString,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,17 +9,7 @@ import { ImportResult } from "../../models/import-result";
|
|||||||
import { Importer } from "../importer";
|
import { Importer } from "../importer";
|
||||||
|
|
||||||
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
||||||
|
import { BitwardenPasswordProtectedFileFormat } from "./bitwarden-password-protected-types";
|
||||||
interface BitwardenPasswordProtectedFileFormat {
|
|
||||||
encrypted: boolean;
|
|
||||||
passwordProtected: boolean;
|
|
||||||
salt: string;
|
|
||||||
kdfIterations: number;
|
|
||||||
kdfType: number;
|
|
||||||
encKeyValidation_DO_NOT_EDIT: string;
|
|
||||||
data: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter implements Importer {
|
export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter implements Importer {
|
||||||
private key: SymmetricCryptoKey;
|
private key: SymmetricCryptoKey;
|
||||||
|
|
||||||
@@ -50,8 +40,8 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
|||||||
this.key = await this.cryptoService.makePinKey(
|
this.key = await this.cryptoService.makePinKey(
|
||||||
this.password,
|
this.password,
|
||||||
jdoc.salt,
|
jdoc.salt,
|
||||||
KdfType.PBKDF2_SHA256,
|
jdoc.kdfType,
|
||||||
new KdfConfig(jdoc.kdfIterations)
|
new KdfConfig(jdoc.kdfIterations, jdoc.kdfMemory, jdoc.kdfParallelism)
|
||||||
);
|
);
|
||||||
|
|
||||||
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export interface BitwardenPasswordProtectedFileFormat {
|
||||||
|
encrypted: boolean;
|
||||||
|
passwordProtected: boolean;
|
||||||
|
salt: string;
|
||||||
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
|
kdfType: number;
|
||||||
|
encKeyValidation_DO_NOT_EDIT: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user