Skip to content

Commit 6ffd3f1

Browse files
authored
feat: implement 6 missing crypto APIs: 15 ❌ → ✅ (#921)
1 parent a62c1bc commit 6ffd3f1

51 files changed

Lines changed: 1896 additions & 53 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.docs/implementation-coverage.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ These algorithms provide quantum-resistant cryptography.
1818

1919
# `Crypto`
2020

21-
* Class: `Certificate`
22-
* Static method: `Certificate.exportChallenge(spkac[, encoding])`
23-
* Static method: `Certificate.exportPublicKey(spkac[, encoding])`
24-
* Static method: `Certificate.verifySpkac(spkac[, encoding])`
21+
* Class: `Certificate`
22+
* Static method: `Certificate.exportChallenge(spkac[, encoding])`
23+
* Static method: `Certificate.exportPublicKey(spkac[, encoding])`
24+
* Static method: `Certificate.verifySpkac(spkac[, encoding])`
2525
* ✅ Class: `Cipheriv`
2626
*`cipher.final([outputEncoding])`
2727
*`cipher.getAuthTag()`
@@ -46,7 +46,7 @@ These algorithms provide quantum-resistant cryptography.
4646
*`diffieHellman.verifyError`
4747
* ✅ Class: `DiffieHellmanGroup`
4848
* ✅ Class: `ECDH`
49-
* static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])`
49+
* static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])`
5050
*`ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
5151
*`ecdh.generateKeys([encoding[, format]])`
5252
*`ecdh.getPrivateKey([encoding])`
@@ -60,14 +60,14 @@ These algorithms provide quantum-resistant cryptography.
6060
* ✅ Class: `Hmac`
6161
*`hmac.digest([encoding])`
6262
*`hmac.update(data[, inputEncoding])`
63-
* 🚧 Class: `KeyObject`
64-
* static `KeyObject.from(key)`
65-
* `keyObject.asymmetricKeyDetails`
63+
* Class: `KeyObject`
64+
* static `KeyObject.from(key)`
65+
* `keyObject.asymmetricKeyDetails`
6666
*`keyObject.asymmetricKeyType`
6767
*`keyObject.export([options])`
6868
*`keyObject.equals(otherKeyObject)`
6969
*`keyObject.symmetricKeySize`
70-
* `keyObject.toCryptoKey(algorithm, extractable, keyUsages)`
70+
* `keyObject.toCryptoKey(algorithm, extractable, keyUsages)`
7171
*`keyObject.type`
7272
* ✅ Class: `Sign`
7373
*`sign.sign(privateKey[, outputEncoding])`
@@ -102,10 +102,10 @@ These algorithms provide quantum-resistant cryptography.
102102
*`x509.validTo`
103103
*`x509.verify(publicKey)`
104104
* 🚧 node:crypto module methods and properties
105-
* `crypto.argon2(algorithm, parameters, callback)`
106-
* `crypto.argon2Sync(algorithm, parameters)`
107-
* `crypto.checkPrime(candidate[, options], callback)`
108-
* `crypto.checkPrimeSync(candidate[, options])`
105+
* `crypto.argon2(algorithm, parameters, callback)`
106+
* `crypto.argon2Sync(algorithm, parameters)`
107+
* `crypto.checkPrime(candidate[, options], callback)`
108+
* `crypto.checkPrimeSync(candidate[, options])`
109109
*`crypto.constants`
110110
*`crypto.createCipheriv(algorithm, key, iv[, options])`
111111
*`crypto.createDecipheriv(algorithm, key, iv[, options])`
@@ -129,11 +129,11 @@ These algorithms provide quantum-resistant cryptography.
129129
* 🚧 `crypto.generateKeyPair(type, options, callback)`
130130
* 🚧 `crypto.generateKeyPairSync(type, options)`
131131
* 🚧 `crypto.generateKeySync(type, options)`
132-
* `crypto.generatePrime(size[, options[, callback]])`
133-
* `crypto.generatePrimeSync(size[, options])`
134-
* `crypto.getCipherInfo(nameOrNid[, options])`
132+
* `crypto.generatePrime(size[, options[, callback]])`
133+
* `crypto.generatePrimeSync(size[, options])`
134+
* `crypto.getCipherInfo(nameOrNid[, options])`
135135
*`crypto.getCiphers()`
136-
* `crypto.getCurves()`
136+
* `crypto.getCurves()`
137137
*`crypto.getFips()`
138138
*`crypto.getHashes()`
139139
*`crypto.getRandomValues(typedArray)`
@@ -157,10 +157,10 @@ These algorithms provide quantum-resistant cryptography.
157157
*`crypto.setEngine(engine[, flags])`
158158
*`crypto.setFips(bool)`
159159
*`crypto.sign(algorithm, data, key[, callback])`
160-
* 🚧 `crypto.subtle` (see below)
160+
* `crypto.subtle` (see below)
161161
*`crypto.timingSafeEqual(a, b)`
162162
*`crypto.verify(algorithm, data, key, signature[, callback])`
163-
* 🚧 `crypto.webcrypto` (see below)
163+
* `crypto.webcrypto` (see below)
164164

165165
## `crypto.diffieHellman`
166166
| type | Status |
@@ -242,10 +242,10 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
242242
243243
# `WebCrypto`
244244

245-
* Class: `Crypto`
246-
* `crypto.subtle`
247-
* `crypto.getRandomValues(typedArray)`
248-
* `crypto.randomUUID()`
245+
* Class: `Crypto`
246+
* `crypto.subtle`
247+
* `crypto.getRandomValues(typedArray)`
248+
* `crypto.randomUUID()`
249249
* ✅ Class: `CryptoKey`
250250
*`cryptoKey.algorithm`
251251
*`cryptoKey.extractable`
@@ -254,12 +254,12 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
254254
* ✅ Class: `CryptoKeyPair`
255255
*`cryptoKeyPair.privateKey`
256256
*`cryptoKeyPair.publicKey`
257-
* Class: `CryptoSubtle`
257+
* 🚧 Class: `CryptoSubtle`
258258
* (see below)
259259

260260
# `SubtleCrypto`
261261

262-
* Class: `SubtleCrypto`
262+
* 🚧 Class: `SubtleCrypto`
263263
* ❌ static `supports(operation, algorithm[, lengthOrAdditionalAlgorithm])`
264264
*`subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)`
265265
*`subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)`

docs/data/coverage.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ export const COVERAGE_DATA: CoverageCategory[] = [
4444
{
4545
name: 'Certificate',
4646
subItems: [
47-
{ name: 'exportChallenge', status: 'missing' },
48-
{ name: 'exportPublicKey', status: 'missing' },
49-
{ name: 'verifySpkac', status: 'missing' },
47+
{ name: 'exportChallenge', status: 'implemented' },
48+
{ name: 'exportPublicKey', status: 'implemented' },
49+
{ name: 'verifySpkac', status: 'implemented' },
5050
],
5151
},
5252
{
@@ -76,8 +76,15 @@ export const COVERAGE_DATA: CoverageCategory[] = [
7676
},
7777
{
7878
name: 'ECDH',
79-
status: 'implemented',
80-
note: 'Use simple ECDH methods instead',
79+
subItems: [
80+
{ name: 'convertKey', status: 'implemented', note: 'static' },
81+
{ name: 'computeSecret', status: 'implemented' },
82+
{ name: 'generateKeys', status: 'implemented' },
83+
{ name: 'getPrivateKey', status: 'implemented' },
84+
{ name: 'getPublicKey', status: 'implemented' },
85+
{ name: 'setPrivateKey', status: 'implemented' },
86+
{ name: 'setPublicKey', status: 'implemented' },
87+
],
8188
},
8289
{
8390
name: 'Hash',
@@ -114,11 +121,11 @@ export const COVERAGE_DATA: CoverageCategory[] = [
114121
{ name: 'asymmetricKeyType', status: 'implemented' },
115122
{ name: 'export', status: 'implemented' },
116123
{ name: 'type', status: 'implemented' },
117-
{ name: 'asymmetricKeyDetails', status: 'missing' },
124+
{ name: 'asymmetricKeyDetails', status: 'implemented' },
118125
{ name: 'equals', status: 'implemented' },
119126
{ name: 'symmetricKeySize', status: 'implemented' },
120-
{ name: 'toCryptoKey', status: 'missing' },
121-
{ name: 'from', status: 'missing', note: 'static' },
127+
{ name: 'toCryptoKey', status: 'implemented' },
128+
{ name: 'from', status: 'implemented', note: 'static' },
122129
],
123130
},
124131
{
@@ -130,8 +137,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
130137
{
131138
title: 'Crypto Methods',
132139
items: [
133-
{ name: 'argon2', status: 'missing' },
134-
{ name: 'checkPrime', status: 'missing' },
140+
{ name: 'argon2', status: 'implemented' },
141+
{ name: 'checkPrime', status: 'implemented' },
135142
{ name: 'constants', status: 'implemented' },
136143
{ name: 'createCipheriv', status: 'implemented' },
137144
{ name: 'createDecipheriv', status: 'implemented' },
@@ -201,8 +208,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
201208
{ name: 'hmac', status: 'implemented' },
202209
],
203210
},
204-
{ name: 'generatePrime', status: 'missing' },
205-
{ name: 'getCipherInfo', status: 'missing' },
211+
{ name: 'generatePrime', status: 'implemented' },
212+
{ name: 'getCipherInfo', status: 'implemented' },
206213
{ name: 'getCiphers', status: 'implemented' },
207214
{ name: 'getCurves', status: 'implemented' },
208215
{ name: 'getDiffieHellman', status: 'implemented' },

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2811,7 +2811,7 @@ SPEC CHECKSUMS:
28112811
MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df
28122812
NitroMmkv: afbc5b2fbf963be567c6c545aa1efcf6a9cec68e
28132813
NitroModules: 11bba9d065af151eae51e38a6425e04c3b223ff3
2814-
QuickCrypto: 91cda93ba3146b0cb92039d1058fbbaec100edd1
2814+
QuickCrypto: aad2c3dbe94e5eb4322d20e1bdccfa66464e2ae0
28152815
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
28162816
RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077
28172817
RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a

example/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"lint:fix": "eslint \"src/**/*.{js,ts,tsx}\" --fix",
1616
"format": "prettier --check \"**/*.{js,ts,tsx}\"",
1717
"format:fix": "prettier --write \"**/*.{js,ts,tsx}\"",
18-
"start": "react-native start",
18+
"start": "react-native start --client-logs",
1919
"dev": "sh -c 'react-native start --client-logs \"$@\" 2>&1 | tee /tmp/rnqc-metro.log' --",
2020
"pods": "RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1 bundle install && bundle exec pod install --project-directory=ios",
2121
"build:android": "cd android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a",
@@ -40,7 +40,7 @@
4040
"react-native-mmkv": "4.0.1",
4141
"react-native-nitro-modules": "0.33.2",
4242
"react-native-quick-base64": "2.2.2",
43-
"react-native-quick-crypto": "1.0.10",
43+
"react-native-quick-crypto": "workspace:*",
4444
"react-native-safe-area-context": "5.6.2",
4545
"react-native-screens": "4.18.0",
4646
"react-native-vector-icons": "10.3.0",

example/src/hooks/useTestsList.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,31 @@ import { useState, useCallback } from 'react';
22
import type { TestSuites } from '../types/tests';
33
import { TestsContext } from '../tests/util';
44

5+
import '../tests/argon2/argon2_tests';
56
import '../tests/blake3/blake3_tests';
6-
import '../tests/cipher/cipher_tests';
7+
import '../tests/certificate/certificate_tests';
78
import '../tests/cipher/chacha_tests';
8-
import '../tests/cipher/xsalsa20_tests';
9-
import '../tests/cipher/xsalsa20_poly1305_tests';
9+
import '../tests/cipher/cipher_tests';
10+
import '../tests/cipher/cipherinfo_tests';
1011
import '../tests/cipher/xchacha20_poly1305_tests';
12+
import '../tests/cipher/xsalsa20_poly1305_tests';
13+
import '../tests/cipher/xsalsa20_tests';
1114
import '../tests/dh/dh_tests';
15+
import '../tests/ecdh/ecdh_convertkey_tests';
1216
import '../tests/ecdh/ecdh_tests';
1317
import '../tests/hash/hash_tests';
14-
import '../tests/hmac/hmac_tests';
1518
import '../tests/hkdf/hkdf_tests';
19+
import '../tests/hmac/hmac_tests';
1620
import '../tests/jose/jose';
1721
import '../tests/keys/create_keys';
1822
import '../tests/keys/generate_key';
1923
import '../tests/keys/generate_keypair';
24+
import '../tests/keys/keyobject_from_tocryptokey_tests';
2025
import '../tests/keys/public_cipher';
21-
import '../tests/keys/sign_verify_streaming';
2226
import '../tests/keys/sign_verify_oneshot';
27+
import '../tests/keys/sign_verify_streaming';
2328
import '../tests/pbkdf2/pbkdf2_tests';
29+
import '../tests/prime/prime_tests';
2430
import '../tests/random/random_tests';
2531
import '../tests/scrypt/scrypt_tests';
2632
import '../tests/subtle/deriveBits';
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { test } from '../util';
2+
import { argon2Sync, argon2, Buffer } from 'react-native-quick-crypto';
3+
import { assert } from 'chai';
4+
5+
const SUITE = 'argon2';
6+
7+
// RFC 9106 test vector for argon2id
8+
const RFC_PARAMS = {
9+
message: Buffer.from(
10+
'0101010101010101010101010101010101010101010101010101010101010101',
11+
'hex',
12+
),
13+
nonce: Buffer.from('02020202020202020202020202020202', 'hex'),
14+
parallelism: 4,
15+
tagLength: 32,
16+
memory: 32, // 32 KiB
17+
passes: 3,
18+
secret: Buffer.from('0303030303030303', 'hex'),
19+
associatedData: Buffer.from('040404040404040404040404', 'hex'),
20+
version: 0x13,
21+
};
22+
23+
test(SUITE, 'argon2Sync: argon2id produces expected output', () => {
24+
const result = argon2Sync('argon2id', RFC_PARAMS);
25+
assert.isOk(result);
26+
assert.strictEqual(result.length, 32);
27+
});
28+
29+
test(SUITE, 'argon2Sync: argon2i produces output', () => {
30+
const result = argon2Sync('argon2i', {
31+
message: Buffer.from('password'),
32+
nonce: Buffer.from('somesalt0000'),
33+
parallelism: 1,
34+
tagLength: 32,
35+
memory: 64,
36+
passes: 3,
37+
});
38+
assert.isOk(result);
39+
assert.strictEqual(result.length, 32);
40+
});
41+
42+
test(SUITE, 'argon2Sync: argon2d produces output', () => {
43+
const result = argon2Sync('argon2d', {
44+
message: Buffer.from('password'),
45+
nonce: Buffer.from('somesalt0000'),
46+
parallelism: 1,
47+
tagLength: 32,
48+
memory: 64,
49+
passes: 3,
50+
});
51+
assert.isOk(result);
52+
assert.strictEqual(result.length, 32);
53+
});
54+
55+
test(SUITE, 'argon2Sync: different algorithms produce different output', () => {
56+
const params = {
57+
message: Buffer.from('password'),
58+
nonce: Buffer.from('somesalt0000'),
59+
parallelism: 1,
60+
tagLength: 32,
61+
memory: 64,
62+
passes: 3,
63+
};
64+
const d = argon2Sync('argon2d', params);
65+
const i = argon2Sync('argon2i', params);
66+
const id = argon2Sync('argon2id', params);
67+
assert.notDeepEqual(d, i);
68+
assert.notDeepEqual(i, id);
69+
assert.notDeepEqual(d, id);
70+
});
71+
72+
test(SUITE, 'argon2Sync: respects tagLength', () => {
73+
const result = argon2Sync('argon2id', {
74+
message: Buffer.from('password'),
75+
nonce: Buffer.from('somesalt0000'),
76+
parallelism: 1,
77+
tagLength: 64,
78+
memory: 64,
79+
passes: 3,
80+
});
81+
assert.strictEqual(result.length, 64);
82+
});
83+
84+
test(SUITE, 'argon2Sync: throws on invalid algorithm', () => {
85+
assert.throws(() => {
86+
argon2Sync('argon2x', {
87+
message: Buffer.from('password'),
88+
nonce: Buffer.from('somesalt0000'),
89+
parallelism: 1,
90+
tagLength: 32,
91+
memory: 64,
92+
passes: 3,
93+
});
94+
}, /Unknown argon2 algorithm/);
95+
});
96+
97+
test(SUITE, 'argon2: async produces same result as sync', () => {
98+
return new Promise<void>((resolve, reject) => {
99+
const params = {
100+
message: Buffer.from('password'),
101+
nonce: Buffer.from('somesalt0000'),
102+
parallelism: 1,
103+
tagLength: 32,
104+
memory: 64,
105+
passes: 3,
106+
};
107+
const syncResult = argon2Sync('argon2id', params);
108+
argon2('argon2id', params, (err, asyncResult) => {
109+
try {
110+
assert.isNull(err);
111+
assert.deepEqual(
112+
Buffer.from(asyncResult).toString('hex'),
113+
Buffer.from(syncResult).toString('hex'),
114+
);
115+
resolve();
116+
} catch (e) {
117+
reject(e);
118+
}
119+
});
120+
});
121+
});
122+
123+
test(SUITE, 'argon2Sync: deterministic with same inputs', () => {
124+
const params = {
125+
message: Buffer.from('password'),
126+
nonce: Buffer.from('somesalt0000'),
127+
parallelism: 1,
128+
tagLength: 32,
129+
memory: 64,
130+
passes: 3,
131+
};
132+
const r1 = argon2Sync('argon2id', params);
133+
const r2 = argon2Sync('argon2id', params);
134+
assert.deepEqual(
135+
Buffer.from(r1).toString('hex'),
136+
Buffer.from(r2).toString('hex'),
137+
);
138+
});

0 commit comments

Comments
 (0)