Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
43 changes: 0 additions & 43 deletions .claude/plans/quick-wins.md

This file was deleted.

23 changes: 12 additions & 11 deletions .docs/implementation-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ These algorithms provide quantum-resistant cryptography.
* ✅ `diffieHellman.getPublicKey([encoding])`
* ✅ `diffieHellman.setPrivateKey(privateKey[, encoding])`
* ✅ `diffieHellman.setPublicKey(publicKey[, encoding])`
* `diffieHellman.verifyError`
* `diffieHellman.verifyError`
* ✅ Class: `DiffieHellmanGroup`
* ✅ Class: `ECDH`
* ❌ static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])`
Expand All @@ -65,8 +65,8 @@ These algorithms provide quantum-resistant cryptography.
* ❌ `keyObject.asymmetricKeyDetails`
* ✅ `keyObject.asymmetricKeyType`
* ✅ `keyObject.export([options])`
* `keyObject.equals(otherKeyObject)`
* `keyObject.symmetricKeySize`
* `keyObject.equals(otherKeyObject)`
* `keyObject.symmetricKeySize`
* ❌ `keyObject.toCryptoKey(algorithm, extractable, keyUsages)`
* ✅ `keyObject.type`
* ✅ Class: `Sign`
Expand Down Expand Up @@ -111,6 +111,7 @@ These algorithms provide quantum-resistant cryptography.
* ✅ `crypto.createDecipheriv(algorithm, key, iv[, options])`
* ✅ `crypto.createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])`
* ✅ `crypto.createDiffieHellman(primeLength[, generator])`
* ✅ `crypto.createDiffieHellmanGroup(groupName)`
* ✅ `crypto.getDiffieHellman(groupName)`
* ✅ `crypto.createECDH(curveName)`
* ✅ `crypto.createHash(algorithm[, options])`
Expand All @@ -136,7 +137,7 @@ These algorithms provide quantum-resistant cryptography.
* ❌ `crypto.getFips()`
* ✅ `crypto.getHashes()`
* ✅ `crypto.getRandomValues(typedArray)`
* `crypto.hash(algorithm, data[, options])`
* `crypto.hash(algorithm, data[, outputEncoding])`
* ✅ `crypto.hkdf(digest, ikm, salt, info, keylen, callback)`
* ✅ `crypto.hkdfSync(digest, ikm, salt, info, keylen)`
* ✅ `crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)`
Expand Down Expand Up @@ -263,8 +264,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
* ❌ `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)`
* ❌ `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)`
* ✅ `subtle.decrypt(algorithm, key, data)`
* 🚧 `subtle.deriveBits(algorithm, baseKey, length)`
* 🚧 `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)`
* `subtle.deriveBits(algorithm, baseKey, length)`
* `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)`
* 🚧 `subtle.digest(algorithm, data)`
* ❌ `subtle.encapsulateBits(encapsulationAlgorithm, encapsulationKey)`
* ❌ `subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)`
Expand Down Expand Up @@ -299,7 +300,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
## `subtle.deriveKey`
| Algorithm | Status |
| --------- | :----: |
| `ECDH` | |
| `ECDH` | |
| `HKDF` | ✅ |
| `PBKDF2` | ✅ |
| `X25519` | ✅ |
Expand Down Expand Up @@ -339,8 +340,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
| `Ed25519` | ✅ | ✅ | | ✅ | | ❌ | |
| `Ed448` | ✅ | ✅ | | ✅ | | ❌ | |
| `Ed25519` | ✅ | ✅ | | ✅ | | ❌ | |
| `Ed448` | ✅ | ✅ | | ✅ | | ❌ | |
| `HMAC` | | | ✅ | ✅ | ✅ | | |
| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ |
| `ML-DSA-65` | ✅ | ✅ | ✅ | | | ✅ | ✅ |
Expand Down Expand Up @@ -399,8 +400,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
| `Ed25519` | ✅ | ✅ | | | | ❌ | |
| `Ed448` | ✅ | ✅ | | | | ❌ | |
| `Ed25519` | ✅ | ✅ | | | | ❌ | |
| `Ed448` | ✅ | ✅ | | | | ❌ | |
| `HKDF` | | | | ✅ | ❌ | | |
| `HMAC` | | | ✅ | ✅ | ✅ | | |
| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ |
Expand Down
18 changes: 9 additions & 9 deletions docs/data/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
{ name: 'export', status: 'implemented' },
{ name: 'type', status: 'implemented' },
{ name: 'asymmetricKeyDetails', status: 'missing' },
{ name: 'equals', status: 'missing' },
{ name: 'symmetricKeySize', status: 'missing' },
{ name: 'equals', status: 'implemented' },
{ name: 'symmetricKeySize', status: 'implemented' },
{ name: 'toCryptoKey', status: 'missing' },
{ name: 'from', status: 'missing', note: 'static' },
],
Expand All @@ -136,7 +136,7 @@ export const COVERAGE_DATA: CoverageCategory[] = [
{ name: 'createCipheriv', status: 'implemented' },
{ name: 'createDecipheriv', status: 'implemented' },
{ name: 'createDiffieHellman', status: 'implemented' },
{ name: 'createDiffieHellmanGroup', status: 'missing' },
{ name: 'createDiffieHellmanGroup', status: 'implemented' },
{ name: 'createECDH', status: 'implemented' },
{ name: 'createHash', status: 'implemented' },
{ name: 'createHmac', status: 'implemented' },
Expand Down Expand Up @@ -209,7 +209,7 @@ export const COVERAGE_DATA: CoverageCategory[] = [
{ name: 'getFips', status: 'missing' },
{ name: 'getHashes', status: 'implemented' },
{ name: 'getRandomValues', status: 'implemented' },
{ name: 'hash', status: 'missing' },
{ name: 'hash', status: 'implemented' },
{ name: 'hkdf', status: 'implemented' },
{ name: 'pbkdf2', status: 'implemented' },
{ name: 'privateDecrypt / privateEncrypt', status: 'implemented' },
Expand Down Expand Up @@ -284,7 +284,7 @@ export const COVERAGE_DATA: CoverageCategory[] = [
{
name: 'crypto.subtle.deriveKey',
subItems: [
{ name: 'ECDH', status: 'missing' },
{ name: 'ECDH', status: 'implemented' },
{ name: 'HKDF', status: 'implemented' },
{ name: 'PBKDF2', status: 'implemented' },
{ name: 'X25519', status: 'implemented' },
Expand Down Expand Up @@ -339,8 +339,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
status: 'partial',
note: 'spki, pkcs8, jwk, raw, raw-public',
},
{ name: 'Ed25519', status: 'partial', note: 'spki, pkcs8, raw' },
{ name: 'Ed448', status: 'partial', note: 'spki, pkcs8, raw' },
{ name: 'Ed25519', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
{ name: 'Ed448', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
{ name: 'HMAC', status: 'partial', note: 'jwk, raw, raw-secret' },
{
name: 'ML-DSA-44',
Expand Down Expand Up @@ -415,8 +415,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
status: 'partial',
note: 'spki, pkcs8, jwk, raw, raw-public',
},
{ name: 'Ed25519', status: 'partial', note: 'spki, pkcs8' },
{ name: 'Ed448', status: 'partial', note: 'spki, pkcs8' },
{ name: 'Ed25519', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
{ name: 'Ed448', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
{ name: 'HKDF', status: 'partial', note: 'raw' },
{ name: 'HMAC', status: 'partial', note: 'jwk, raw, raw-secret' },
{
Expand Down
31 changes: 31 additions & 0 deletions example/src/tests/dh/dh_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,34 @@ test(SUITE, 'should reject prime length below 2048 bits', () => {
crypto.createDiffieHellman(512);
}, /prime length must be at least 2048 bits/);
});

// createDiffieHellmanGroup alias
test(
SUITE,
'createDiffieHellmanGroup should be an alias for getDiffieHellman',
() => {
const dh1 = crypto.getDiffieHellman('modp14');
const dh2 = crypto.createDiffieHellmanGroup('modp14');

assert.strictEqual(dh1.getPrime('hex'), dh2.getPrime('hex'));
assert.strictEqual(dh1.getGenerator('hex'), dh2.getGenerator('hex'));
},
);

test(SUITE, 'createDiffieHellmanGroup should throw for unknown group', () => {
assert.throws(() => {
crypto.createDiffieHellmanGroup('modp999');
}, /Unknown group/);
});

// verifyError property
test(SUITE, 'verifyError should return 0 for valid DH params', () => {
const dh = crypto.getDiffieHellman('modp14');
assert.strictEqual(dh.verifyError, 0);
});

test(SUITE, 'verifyError should return 0 for created DH', () => {
const prime = Buffer.from(MODP14_PRIME, 'hex');
const dh = crypto.createDiffieHellman(prime, 2);
assert.strictEqual(dh.verifyError, 0);
});
38 changes: 38 additions & 0 deletions example/src/tests/hash/hash_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {
Buffer,
createHash,
hash,
getHashes,
type Encoding,
} from 'react-native-quick-crypto';
Expand Down Expand Up @@ -283,3 +284,40 @@ test(SUITE, 'createHash with null outputLength', () => {
createHash('shake128', { outputLength: null });
}).to.throw(/Output length must be a number/);
});

// crypto.hash() oneshot function tests
test(SUITE, 'hash() oneshot - sha256 hex', () => {
const result = hash('sha256', 'Test123', 'hex');
const expected = createHash('sha256').update('Test123').digest('hex');
expect(result).to.equal(expected);
});

test(SUITE, 'hash() oneshot - sha256 base64', () => {
const result = hash('sha256', 'Test123', 'base64');
const expected = createHash('sha256').update('Test123').digest('base64');
expect(result).to.equal(expected);
});

test(SUITE, 'hash() oneshot - returns Buffer without encoding', () => {
const result = hash('sha256', 'Test123');
expect(Buffer.isBuffer(result)).to.equal(true);
expect(typeof result).to.not.equal('string');
});

test(SUITE, 'hash() oneshot - sha512', () => {
const result = hash('sha512', 'hello world', 'hex');
const expected = createHash('sha512').update('hello world').digest('hex');
expect(result).to.equal(expected);
});

test(SUITE, 'hash() oneshot - md5', () => {
const result = hash('md5', 'Test123', 'hex');
expect(result).to.equal('68eacb97d86f0c4621fa2b0e17cabd8c');
});

test(SUITE, 'hash() oneshot - Buffer input', () => {
const data = Buffer.from('hello');
const result = hash('sha256', data, 'hex');
const expected = createHash('sha256').update(data).digest('hex');
expect(result).to.equal(expected);
});
44 changes: 44 additions & 0 deletions example/src/tests/keys/create_keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,47 @@ test(
}, '');
},
);

// --- KeyObject.equals() Tests ---

test(SUITE, 'equals - same secret keys are equal', () => {
const keyData = randomBytes(32);
const key1 = createSecretKey(keyData);
const key2 = createSecretKey(keyData);
expect(key1.equals(key2)).to.equal(true);
});

test(SUITE, 'equals - different secret keys are not equal', () => {
const key1 = createSecretKey(randomBytes(32));
const key2 = createSecretKey(randomBytes(32));
expect(key1.equals(key2)).to.equal(false);
});

test(SUITE, 'equals - same RSA public keys are equal', () => {
const key1 = createPublicKey(rsaPublicKeyPem);
const key2 = createPublicKey(rsaPublicKeyPem);
expect(key1.equals(key2)).to.equal(true);
});

test(SUITE, 'equals - different key types are not equal', () => {
const secretKey = createSecretKey(randomBytes(32));
const publicKey = createPublicKey(rsaPublicKeyPem);
expect(secretKey.equals(publicKey)).to.equal(false);
});

// --- KeyObject.symmetricKeySize Tests ---

test(SUITE, 'symmetricKeySize - 16 byte key', () => {
const key = createSecretKey(randomBytes(16));
expect(key.symmetricKeySize).to.equal(16);
});

test(SUITE, 'symmetricKeySize - 32 byte key', () => {
const key = createSecretKey(randomBytes(32));
expect(key.symmetricKeySize).to.equal(32);
});

test(SUITE, 'symmetricKeySize - 64 byte key', () => {
const key = createSecretKey(randomBytes(64));
expect(key.symmetricKeySize).to.equal(64);
});
64 changes: 64 additions & 0 deletions example/src/tests/subtle/derive_key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,67 @@ test(SUITE, 'X25519 deriveKey to AES-GCM', async () => {
Buffer.from(bobRaw as ArrayBuffer).toString('hex'),
);
});

// Test 3: ECDH deriveKey
test(SUITE, 'ECDH P-256 deriveKey to AES-GCM', async () => {
const aliceKeyPair = await subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveKey', 'deriveBits'],
);

const bobKeyPair = await subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveKey', 'deriveBits'],
);

const aliceDerivedKey = await subtleAny.deriveKey(
{
name: 'ECDH',
public: (aliceKeyPair as CryptoKeyPair).publicKey,
},
(bobKeyPair as CryptoKeyPair).privateKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt'],
);

const bobDerivedKey = await subtleAny.deriveKey(
{
name: 'ECDH',
public: (bobKeyPair as CryptoKeyPair).publicKey,
},
(aliceKeyPair as CryptoKeyPair).privateKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt'],
);

const aliceRaw = await subtle.exportKey('raw', aliceDerivedKey as CryptoKey);
const bobRaw = await subtle.exportKey('raw', bobDerivedKey as CryptoKey);

expect(Buffer.from(aliceRaw as ArrayBuffer).toString('hex')).to.equal(
Buffer.from(bobRaw as ArrayBuffer).toString('hex'),
);

// Verify key works for encrypt/decrypt
const plaintext = new Uint8Array([1, 2, 3, 4]);
const iv = getRandomValues(new Uint8Array(12));

const ciphertext = await subtle.encrypt(
{ name: 'AES-GCM', iv },
aliceDerivedKey as CryptoKey,
plaintext,
);

const decrypted = await subtle.decrypt(
{ name: 'AES-GCM', iv },
bobDerivedKey as CryptoKey,
ciphertext,
);

expect(Buffer.from(decrypted).toString('hex')).to.equal(
Buffer.from(plaintext).toString('hex'),
);
});
Loading
Loading