Skip to content

Commit b585471

Browse files
authored
fix: improve pbkdf2 type safety to match Node.js API (#932)
1 parent 70a9650 commit b585471

4 files changed

Lines changed: 46 additions & 69 deletions

File tree

example/src/benchmarks/pbkdf2/pbkdf2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import rnqc, { type HashAlgorithm } from 'react-native-quick-crypto';
1+
import rnqc from 'react-native-quick-crypto';
22
import * as noble from '@noble/hashes/pbkdf2';
33
import { sha256 } from '@noble/hashes/sha2';
44
// @ts-expect-error - crypto-browserify is not typed
@@ -37,7 +37,7 @@ const pbkdf2_256_1_32_sync: BenchFn = () => {
3737

3838
bench
3939
.add('rnqc', () => {
40-
rnqc.pbkdf2Sync('password', 'salt', 1, 32, 'sha256' as HashAlgorithm);
40+
rnqc.pbkdf2Sync('password', 'salt', 1, 32, 'sha256');
4141
})
4242
.add('@noble/hashes/pbkdf2', () => {
4343
noble.pbkdf2(sha256, 'password', 'salt', { c: 1, dkLen: 32 });

example/src/tests/pbkdf2/fixtures.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// copied from https://github.com/crypto-browserify/pbkdf2/blob/master/test/fixtures.json
2-
export type Fixture = {
2+
interface FixtureBase {
33
description?: string;
44
key?: string;
55
keyHex?: string;
@@ -11,15 +11,26 @@ export type Fixture = {
1111
saltUint8Array?: number[];
1212
saltInt32Array?: number[];
1313
saltFloat64Array?: number[];
14+
}
15+
16+
export interface ValidFixture extends FixtureBase {
17+
iterations: number;
18+
dkLen: number;
19+
results: Record<string, string>;
20+
}
21+
22+
export interface InvalidFixture extends FixtureBase {
1423
iterations: number | string;
1524
dkLen: number | string;
16-
results?: Record<string, string>;
17-
exception?: string;
18-
};
19-
type Fixtures = {
20-
valid: Fixture[];
21-
invalid: Fixture[];
22-
};
25+
exception: string;
26+
}
27+
28+
export type Fixture = ValidFixture | InvalidFixture;
29+
30+
interface Fixtures {
31+
valid: ValidFixture[];
32+
invalid: InvalidFixture[];
33+
}
2334
export const fixtures: Fixtures = {
2435
valid: [
2536
{

example/src/tests/pbkdf2/pbkdf2_tests.ts

Lines changed: 20 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
import { Buffer } from 'safe-buffer';
33
import { expect } from 'chai';
44
import { test } from '../util';
5-
import { fixtures, type Fixture } from './fixtures';
5+
import { fixtures, type ValidFixture } from './fixtures';
66

77
import crypto from 'react-native-quick-crypto';
8-
import type { BinaryLike, HashAlgorithm } from 'react-native-quick-crypto';
8+
import type { BinaryLike } from 'react-native-quick-crypto';
99

1010
type TestFixture = [string, string, number, number, string];
1111

@@ -27,18 +27,11 @@ const SUITE = 'pbkdf2';
2727
length: number,
2828
expected: string,
2929
) => {
30-
crypto.pbkdf2(
31-
pass,
32-
salt,
33-
iterations,
34-
length,
35-
hash as HashAlgorithm,
36-
function (err, result) {
37-
expect(err).to.be.null;
38-
expect(result).not.to.be.null;
39-
expect(result?.toString('hex')).to.equal(expected);
40-
},
41-
);
30+
crypto.pbkdf2(pass, salt, iterations, length, hash, function (err, result) {
31+
expect(err).to.be.null;
32+
expect(result).not.to.be.null;
33+
expect(result?.toString('hex')).to.equal(expected);
34+
});
4235
};
4336

4437
const kTests: TestFixture[] = [
@@ -75,7 +68,7 @@ const SUITE = 'pbkdf2';
7568
}
7669

7770
test(SUITE, 'handles buffers', () => {
78-
const resultSync = crypto.pbkdf2Sync('password', 'salt', 1, 32);
71+
const resultSync = crypto.pbkdf2Sync('password', 'salt', 1, 32, 'sha1');
7972
expect(resultSync?.toString('hex')).to.equal(
8073
'0c60c80f961f0e71f3a9b524af6012062fe037a6e0f0eb94fe8fc46bdc637164',
8174
);
@@ -125,8 +118,7 @@ test(
125118

126119
const algos = ['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'ripemd160'];
127120
algos.forEach(function (algorithm) {
128-
fixtures.valid.forEach(function (f: Fixture) {
129-
// TODO: check these types once nitro port is done
121+
fixtures.valid.forEach(function (f: ValidFixture) {
130122
let key: BinaryLike, keyType: string, salt: BinaryLike, saltType: string;
131123
if (f.keyUint8Array) {
132124
key = new Uint8Array(f.keyUint8Array);
@@ -160,7 +152,7 @@ algos.forEach(function (algorithm) {
160152
salt = f.salt as BinaryLike;
161153
saltType = 'string';
162154
}
163-
const expected = f.results ? f.results[algorithm] : undefined;
155+
const expected = f.results[algorithm];
164156
const description =
165157
algorithm +
166158
' encodes "' +
@@ -180,9 +172,9 @@ algos.forEach(function (algorithm) {
180172
crypto.pbkdf2(
181173
key,
182174
salt,
183-
f.iterations as number,
184-
f.dkLen as number,
185-
algorithm as HashAlgorithm,
175+
f.iterations,
176+
f.dkLen,
177+
algorithm,
186178
function (err, result) {
187179
expect(err).to.be.null;
188180
expect(result).not.to.be.null;
@@ -195,42 +187,16 @@ algos.forEach(function (algorithm) {
195187
const result = crypto.pbkdf2Sync(
196188
key,
197189
salt,
198-
f.iterations as number,
199-
f.dkLen as number,
200-
algorithm as HashAlgorithm,
190+
f.iterations,
191+
f.dkLen,
192+
algorithm,
201193
);
202194
expect(result?.toString('hex')).to.equal(expected);
203195
});
204196
});
205197

206-
// // TODO: fix the 'invalid' tests
207-
// fixtures.invalid.forEach(function (f: Fixture) {
208-
// const description = algorithm + ' should throw ' + f.exception;
209-
210-
// test(SUITE, ' async w/ ' + description, function () {
211-
// function noop() {}
212-
// expect(() => {
213-
// crypto.pbkdf2(
214-
// f.key as BinaryLike,
215-
// f.salt as BinaryLike,
216-
// f.iterations as number,
217-
// f.dkLen as number,
218-
// algorithm as HashAlgorithm,
219-
// noop
220-
// )
221-
// }).to.throw(f.exception);
222-
// });
223-
224-
// test(SUITE, ' sync w/' + description, function () {
225-
// expect(() => {
226-
// crypto.pbkdf2Sync(
227-
// f.key as BinaryLike,
228-
// f.salt as BinaryLike,
229-
// f.iterations as number,
230-
// f.dkLen as number,
231-
// algorithm as HashAlgorithm,
232-
// )
233-
// }).to.throw(f.exception);
234-
// });
235-
// });
198+
// TODO(#931): enable invalid fixture tests once JS-side validation is added
199+
// for iterations/keylen. Currently invalid values (negative, NaN, Infinity)
200+
// reach the native C layer and trigger assert()/abort() instead of throwing.
201+
// See: fixtures.invalid
236202
});

packages/react-native-quick-crypto/src/pbkdf2.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
lazyDOMException,
99
normalizeHashName,
1010
} from './utils';
11-
import type { HashAlgorithm, SubtleAlgorithm } from './utils';
11+
import type { SubtleAlgorithm } from './utils';
1212
import type { Pbkdf2 } from './specs/pbkdf2.nitro';
1313
import { promisify } from 'util';
1414
import type { CryptoKey } from './keys';
@@ -79,12 +79,12 @@ export function pbkdf2Sync(
7979
salt: Salt,
8080
iterations: number,
8181
keylen: number,
82-
digest?: HashAlgorithm,
82+
digest: string,
8383
): Buffer {
8484
const sanitizedPassword = sanitizeInput(password, WRONG_PASS);
8585
const sanitizedSalt = sanitizeInput(salt, WRONG_SALT);
8686

87-
const algo = digest ? normalizeHashName(digest, HashContext.Node) : 'sha1';
87+
const algo = normalizeHashName(digest, HashContext.Node);
8888
getNative();
8989
const result: ArrayBuffer = native.pbkdf2Sync(
9090
sanitizedPassword,
@@ -104,7 +104,7 @@ const pbkdf2WithDigest = (
104104
salt: Salt,
105105
iterations: number,
106106
keylen: number,
107-
digest: HashAlgorithm,
107+
digest: string,
108108
callback: Pbkdf2Callback,
109109
) => pbkdf2(password, salt, iterations, keylen, digest, callback);
110110

@@ -142,7 +142,7 @@ export async function pbkdf2DeriveBits(
142142
sanitizedSalt,
143143
iterations,
144144
length / 8,
145-
normalizedHash as HashAlgorithm,
145+
normalizedHash,
146146
);
147147
if (!result) {
148148
throw lazyDOMException(

0 commit comments

Comments
 (0)