Skip to content

[PS-2251] Implement argon2 kdf #4468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/browser/src/manifest.v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"optional_permissions": ["nativeMessaging"],
"host_permissions": ["http://*/*", "https://*/*"],
"content_security_policy": {
"extension_page": "script-src 'self' ; object-src 'self'"
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
},
"commands": {
"_execute_action": {
Expand Down
22 changes: 21 additions & 1 deletion apps/browser/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ const moduleRules = [
test: /\.[jt]sx?$/,
loader: "@ngtools/webpack",
},
{
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
];

const requiredPlugins = [
Expand Down Expand Up @@ -203,13 +208,18 @@ const mainConfig = {
buffer: require.resolve("buffer/"),
util: require.resolve("util/"),
url: require.resolve("url/"),
fs: false,
path: false,
},
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "build"),
},
module: { rules: moduleRules },
module: {
noParse: /\.wasm$/,
rules: moduleRules,
},
plugins: plugins,
};

Expand Down Expand Up @@ -265,13 +275,23 @@ if (manifestVersion == 2) {
test: /\.tsx?$/,
loader: "ts-loader",
},
{
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
noParse: /\.wasm$/,
},
resolve: {
extensions: [".ts", ".js"],
symlinks: false,
modules: [path.resolve("../../node_modules")],
plugins: [new TsconfigPathsPlugin()],
fallback: {
fs: false,
path: false,
},
},
dependencies: ["main"],
plugins: [...requiredPlugins],
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/webpack.main.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const main = {
"./src/package.json",
{ from: "./src/images", to: "images" },
{ from: "./src/locales", to: "locales" },
"../../node_modules/argon2-browser/dist/argon2.wasm",
],
}),
new EnvironmentPlugin({
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop/webpack.renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ const common = {
},
type: "asset/resource",
},
{
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
},
plugins: [],
Expand Down Expand Up @@ -122,6 +127,11 @@ const renderer = {
test: /[\/\\]@angular[\/\\].+\.js$/,
parser: { system: true },
},
{
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
},
plugins: [
Expand Down
86 changes: 62 additions & 24 deletions apps/web/src/app/settings/change-kdf.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,41 +32,79 @@ <h1>{{ "encKeySettings" | i18n }}</h1>
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<select id="kdf" name="Kdf" [(ngModel)]="kdf" class="form-control" required>
<select
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need to remove this to a config PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.

id="kdf"
name="Kdf"
[(ngModel)]="kdf"
(ngModelChange)="onChangeKdf($event)"
class="form-control"
required
>
<option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
</div>
<div class="col-6">
<div class="form-group mb-0">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/what-encryption-is-used/#pbkdf2"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<input
id="kdfIterations"
type="number"
min="5000"
max="2000000"
name="KdfIterations"
class="form-control"
[(ngModel)]="kdfIterations"
required
/>
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/what-encryption-is-used/#pbkdf2"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<input
id="kdfIterations"
type="number"
min="5000"
max="2000000"
name="KdfIterations"
class="form-control"
[(ngModel)]="kdfIterations"
required
/>
</ng-container>
<ng-container *ngIf="kdf == kdfType.Argon2id">
<label for="kdfIterations">KDF Iterations</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/what-encryption-is-used/#argon2"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<input
id="iterations"
type="number"
min="1"
max="1024"
name="Iterations"
class="form-control mb-3"
[(ngModel)]="kdfIterations"
required
/>
</ng-container>
</div>
</div>
<div class="col-12">
<div class="form-group">
<div class="small form-text text-muted">
<p>{{ "kdfIterationsDesc" | i18n: (recommendedKdfIterations | number) }}</p>
<strong>{{ "warning" | i18n }}</strong
>: {{ "kdfIterationsWarning" | i18n: (50000 | number) }}
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256">
<p>{{ "kdfIterationsDesc" | i18n: (recommendedPBKDF2Iterations | number) }}</p>
<strong>{{ "warning" | i18n }}</strong
>: {{ "kdfIterationsWarning" | i18n: (50000 | number) }}
</ng-container>
<ng-container *ngIf="kdf == kdfType.Argon2id">
<p>
{{ "argon2MemoryDesc" | i18n }}
</p>
</ng-container>
</div>
</div>
</div>
Expand Down
26 changes: 22 additions & 4 deletions apps/web/src/app/settings/change-kdf.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { DEFAULT_KDF_ITERATIONS, KdfType } from "@bitwarden/common/enums/kdfType";
import {
DEFAULT_PBKDF2_ITERATIONS,
DEFAULT_ARGON2_ITERATIONS,
KdfType,
} from "@bitwarden/common/enums/kdfType";
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";

@Component({
Expand All @@ -16,11 +20,12 @@ import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
})
export class ChangeKdfComponent implements OnInit {
masterPassword: string;
kdfIterations: number;
kdf = KdfType.PBKDF2_SHA256;
kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
kdfType = KdfType;
kdfOptions: any[] = [];
formPromise: Promise<any>;
recommendedKdfIterations = DEFAULT_KDF_ITERATIONS;
recommendedPBKDF2Iterations = DEFAULT_PBKDF2_ITERATIONS;

constructor(
private apiService: ApiService,
Expand All @@ -31,7 +36,10 @@ export class ChangeKdfComponent implements OnInit {
private logService: LogService,
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() {
Expand Down Expand Up @@ -76,4 +84,14 @@ export class ChangeKdfComponent implements OnInit {
this.logService.error(e);
}
}

async onChangeKdf(newValue: KdfType) {
if (newValue === KdfType.PBKDF2_SHA256) {
this.kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
} else if (newValue === KdfType.Argon2id) {
this.kdfIterations = DEFAULT_ARGON2_ITERATIONS;
} else {
throw new Error("Unknown KDF type.");
}
}
}
3 changes: 3 additions & 0 deletions apps/web/src/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,9 @@
}
}
},
"argon2MemoryDesc": {
"message": "Higher Argon2 memory, iterations and parallelism can help protect your master password from being brute forced by an attacker."
},
"changeKdf": {
"message": "Change KDF"
},
Expand Down
15 changes: 14 additions & 1 deletion apps/web/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ const moduleRules = [
test: /\.[jt]sx?$/,
loader: "@ngtools/webpack",
},
{
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
];

const plugins = [
Expand Down Expand Up @@ -223,6 +228,8 @@ const devServer =
default-src 'self'
;script-src
'self'
'wasm-eval'
'wasm-unsafe-eval'
'sha256-ryoU+5+IUZTuUyTElqkrQGBJXr1brEv6r2CA62WUw8w='
https://js.stripe.com
https://js.braintreegateway.com
Expand Down Expand Up @@ -349,13 +356,19 @@ const webpackConfig = {
util: require.resolve("util/"),
assert: false,
url: false,
path: false,
fs: false,
process: false,
},
},
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname, "build"),
},
module: { rules: moduleRules },
module: {
noParse: /\.wasm$/,
rules: moduleRules,
},
plugins: plugins,
};

Expand Down
4 changes: 2 additions & 2 deletions libs/angular/src/components/register.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { DEFAULT_KDF_ITERATIONS, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
import { DEFAULT_KDF_TYPE, DEFAULT_PBKDF2_ITERATIONS } from "@bitwarden/common/enums/kdfType";
import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request";
Expand Down Expand Up @@ -234,7 +234,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
): Promise<RegisterRequest> {
const hint = this.formGroup.value.hint;
const kdf = DEFAULT_KDF_TYPE;
const kdfIterations = DEFAULT_KDF_ITERATIONS;
const kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
const encKey = await this.cryptoService.makeEncKey(key);
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
Expand Down
4 changes: 2 additions & 2 deletions libs/angular/src/components/set-password.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
import { DEFAULT_KDF_ITERATIONS, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
import { DEFAULT_KDF_TYPE, DEFAULT_PBKDF2_ITERATIONS } from "@bitwarden/common/enums/kdfType";
import { Utils } from "@bitwarden/common/misc/utils";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
Expand Down Expand Up @@ -93,7 +93,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {

async setupSubmitActions() {
this.kdf = DEFAULT_KDF_TYPE;
this.kdfIterations = DEFAULT_KDF_ITERATIONS;
this.kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
return true;
}

Expand Down
4 changes: 4 additions & 0 deletions libs/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@
"clean": "rimraf dist/**/*",
"build": "npm run clean && tsc",
"build:watch": "npm run clean && tsc -watch"
},
"dependencies": {
"argon2-browser": "^1.18.0",
"base64-loader": "^1.0.0"
}
}
4 changes: 2 additions & 2 deletions libs/common/spec/services/export.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { KdfType, DEFAULT_KDF_ITERATIONS } from "@bitwarden/common/enums/kdfType";
import { KdfType, DEFAULT_PBKDF2_ITERATIONS } from "@bitwarden/common/enums/kdfType";
import { Utils } from "@bitwarden/common/misc/utils";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { EncString } from "@bitwarden/common/models/domain/enc-string";
Expand Down Expand Up @@ -232,7 +232,7 @@ describe("ExportService", () => {
});

it("specifies kdfIterations", () => {
expect(exportObject.kdfIterations).toEqual(DEFAULT_KDF_ITERATIONS);
expect(exportObject.kdfIterations).toEqual(DEFAULT_PBKDF2_ITERATIONS);
});

it("has kdfType", () => {
Expand Down
7 changes: 7 additions & 0 deletions libs/common/src/abstractions/cryptoFunction.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ export abstract class CryptoFunctionService {
algorithm: "sha256" | "sha512",
iterations: number
) => Promise<ArrayBuffer>;
argon2: (
password: string,
salt: string,
iterations: number,
memory: number,
parallelism: number
) => Promise<ArrayBuffer>;
hkdf: (
ikm: ArrayBuffer,
salt: string | ArrayBuffer,
Expand Down
7 changes: 6 additions & 1 deletion libs/common/src/enums/kdfType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
export enum KdfType {
PBKDF2_SHA256 = 0,
Argon2id = 1,
}

export const DEFAULT_ARGON2_MEMORY = 16;
export const DEFAULT_ARGON2_PARALLELISM = 2;
export const DEFAULT_ARGON2_ITERATIONS = 2;

export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256;
export const DEFAULT_KDF_ITERATIONS = 350000;
export const DEFAULT_PBKDF2_ITERATIONS = 350000;
export const SEND_KDF_ITERATIONS = 100000;
Loading