Skip to content

Commit cbb07b5

Browse files
committed
feat: password hashing limits can be applied with CLI parameters
- `--password-ops-limit` and `--password-mem-limit` are both CLI parameters for Agent start and bootstrap. - Some methods for reading the keyPair now returns the limits alongside the keys in a tuple. - Wrapping the memory error with a keys' domain error.
1 parent 64414c1 commit cbb07b5

File tree

20 files changed

+151
-77
lines changed

20 files changed

+151
-77
lines changed

src/bin/agent/CommandStart.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class CommandStart extends CommandPolykey {
3737
this.addOption(binOptions.backgroundErrFile);
3838
this.addOption(binOptions.fresh);
3939
this.addOption(binOptions.privateKeyFile);
40+
this.addOption(binOptions.passwordOpsLimit);
41+
this.addOption(binOptions.passwordMemLimit);
4042
this.action(async (options) => {
4143
options.clientHost =
4244
options.clientHost ?? config.defaults.networkConfig.clientHost;
@@ -89,19 +91,16 @@ class CommandStart extends CommandPolykey {
8991
const [seedNodes, defaults] = options.seedNodes;
9092
let seedNodes_ = seedNodes;
9193
if (defaults) seedNodes_ = { ...options.network, ...seedNodes };
92-
const fastPasswordHash = process.env.PK_FAST_PASSWORD_HASH === 'true';
9394
const agentConfig = {
9495
password,
9596
nodePath: options.nodePath,
9697
keyRingConfig: {
9798
recoveryCode: recoveryCodeIn,
9899
privateKeyPath: options.privateKeyFile,
99-
passwordOpsLimit: fastPasswordHash
100-
? keysUtils.passwordOpsLimits.min
101-
: undefined,
102-
passwordMemLimit: fastPasswordHash
103-
? keysUtils.passwordMemLimits.min
104-
: undefined,
100+
passwordOpsLimit:
101+
keysUtils.passwordOpsLimits[options.passwordOpsLimit],
102+
passwordMemLimit:
103+
keysUtils.passwordMemLimits[options.passwordMemLimit],
105104
},
106105
proxyConfig: {
107106
connConnectTime: options.connectionTimeout,

src/bin/bootstrap/CommandBootstrap.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ class CommandBootstrap extends CommandPolykey {
1111
this.addOption(binOptions.recoveryCodeFile);
1212
this.addOption(binOptions.fresh);
1313
this.addOption(binOptions.privateKeyFile);
14+
this.addOption(binOptions.passwordOpsLimit);
15+
this.addOption(binOptions.passwordMemLimit);
1416
this.action(async (options) => {
1517
const bootstrapUtils = await import('../../bootstrap/utils');
18+
const keysUtils = await import('../../keys/utils/index');
1619
const password = await binProcessors.processNewPassword(
1720
options.passwordFile,
1821
this.fs,
@@ -27,6 +30,10 @@ class CommandBootstrap extends CommandPolykey {
2730
keyRingConfig: {
2831
recoveryCode: recoveryCodeIn,
2932
privateKeyPath: options.privateKeyFile,
33+
passwordOpsLimit:
34+
keysUtils.passwordOpsLimits[options.passwordOpsLimit],
35+
passwordMemLimit:
36+
keysUtils.passwordMemLimits[options.passwordMemLimit],
3037
},
3138
fresh: options.fresh,
3239
fs: this.fs,

src/bin/utils/options.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const nodePath = new commander.Option(
2929
const format = new commander.Option('-f, --format <format>', 'Output Format')
3030
.choices(['human', 'json'])
3131
.default('human');
32-
3332
/**
3433
* Sets log level, defaults to 0, multiple uses will increase verbosity level
3534
*/
@@ -158,6 +157,30 @@ const noPing = new commander.Option('--no-ping', 'Skip ping step').default(
158157
true,
159158
);
160159

160+
// We can't reference the object here so we recreate the list of choices
161+
const passwordLimitChoices = [
162+
'min',
163+
'max',
164+
'interactive',
165+
'moderate',
166+
'sensitive',
167+
];
168+
const passwordOpsLimit = new commander.Option(
169+
'--password-ops-limit <passwordOpsLimit>',
170+
'Limit the password generation operations',
171+
)
172+
.choices(passwordLimitChoices)
173+
.env('PK_PASSWORD_OPS_LIMIT')
174+
.default('moderate');
175+
176+
const passwordMemLimit = new commander.Option(
177+
'--password-mem-limit <passwordMemLimit>',
178+
'Limit the password generation memory',
179+
)
180+
.choices(passwordLimitChoices)
181+
.env('PK_PASSWORD_MEM_LIMIT')
182+
.default('moderate');
183+
161184
const privateKeyFile = new commander.Option(
162185
'--private-key-file <privateKeyFile>',
163186
'Override key creation with a private key JWE from a file',
@@ -187,4 +210,6 @@ export {
187210
forceNodeAdd,
188211
noPing,
189212
privateKeyFile,
213+
passwordOpsLimit,
214+
passwordMemLimit,
190215
};

src/bootstrap/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FileSystem } from '../types';
22
import type { RecoveryCode, Key, PrivateKey } from '../keys/types';
3+
import type { PasswordMemLimit, PasswordOpsLimit } from '../keys/types';
34
import path from 'path';
45
import Logger from '@matrixai/logger';
56
import { DB } from '@matrixai/db';
@@ -40,6 +41,8 @@ async function bootstrapState({
4041
recoveryCode?: RecoveryCode;
4142
privateKey?: PrivateKey;
4243
privateKeyPath?: string;
44+
passwordOpsLimit?: PasswordOpsLimit;
45+
passwordMemLimit?: PasswordMemLimit;
4346
};
4447
fresh?: boolean;
4548
fs?: FileSystem;

src/keys/KeyRing.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -707,12 +707,7 @@ class KeyRing {
707707
'ciphertext' in privateObject &&
708708
privateObject.ciphertext != null
709709
) {
710-
const privateJWK = keysUtils.unwrapWithPassword(
711-
password,
712-
privateObject,
713-
this.passwordOpsLimit,
714-
this.passwordMemLimit,
715-
);
710+
const privateJWK = keysUtils.unwrapWithPassword(password, privateObject);
716711
if (privateJWK == null) {
717712
throw new keysErrors.ErrorKeyPairParse(
718713
`Private key path ${privateKeyPath} is not a valid encrypted JWK`,

src/keys/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ class ErrorBufferLock<T> extends ErrorKeys<T> {
9898
exitCode = sysexits.TEMPFAIL;
9999
}
100100

101+
class ErrorPasswordHash<T> extends ErrorKeys<T> {
102+
static description = 'Failed to hash password';
103+
exitCode = sysexits.UNAVAILABLE;
104+
}
105+
101106
export {
102107
ErrorKeys,
103108
ErrorKeyRingRunning,
@@ -119,4 +124,5 @@ export {
119124
ErrorCertsReset,
120125
ErrorCertsGC,
121126
ErrorBufferLock,
127+
ErrorPasswordHash,
122128
};

src/keys/utils/password.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
} from '../types';
77
import sodium from 'sodium-native';
88
import { getRandomBytes } from './random';
9+
import * as keysErrors from '../errors';
910

1011
/**
1112
* Use the `min` limit during testing to improve performance.
@@ -80,15 +81,19 @@ function hashPassword(
8081
undefined,
8182
false,
8283
) as PasswordSalt;
83-
sodium.crypto_pwhash(
84-
hash,
85-
Buffer.from(password, 'utf-8'),
86-
salt,
87-
opsLimit,
88-
memLimit,
89-
sodium.crypto_pwhash_ALG_ARGON2ID13,
90-
);
91-
return [hash as PasswordHash, salt];
84+
try {
85+
sodium.crypto_pwhash(
86+
hash,
87+
Buffer.from(password, 'utf-8'),
88+
salt,
89+
opsLimit,
90+
memLimit,
91+
sodium.crypto_pwhash_ALG_ARGON2ID13,
92+
);
93+
return [hash as PasswordHash, salt];
94+
} catch (e) {
95+
throw new keysErrors.ErrorPasswordHash(undefined, { cause: e });
96+
}
9297
}
9398

9499
/**

src/keys/utils/symmetric.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,7 @@ function wrapWithPassword(
215215
* Key unwrapping with password.
216216
* The password can be an empty string.
217217
*/
218-
function unwrapWithPassword(
219-
password: string,
220-
keyJWE: any,
221-
opsLimit: PasswordOpsLimit = passwordOpsLimitDefault,
222-
memLimit: PasswordMemLimit = passwordMemLimitDefault,
223-
): JWK | undefined {
218+
function unwrapWithPassword(password: string, keyJWE: any): JWK | undefined {
224219
if (typeof keyJWE !== 'object' || keyJWE == null) {
225220
return;
226221
}
@@ -256,9 +251,7 @@ function unwrapWithPassword(
256251
// then it may be maliciously trying to DOS this agent
257252
if (
258253
header.ops < passwordOpsLimits.min ||
259-
header.ops > opsLimit ||
260-
header.mem < passwordMemLimits.min ||
261-
header.mem > memLimit
254+
header.mem < passwordMemLimits.min
262255
) {
263256
return;
264257
}

0 commit comments

Comments
 (0)