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
5 changes: 3 additions & 2 deletions example/src/tests/subtle/digest_turboshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,12 @@ test(SUITE, 'KangarooTwelve rejects missing outputLength', async () => {
let threw = false;
try {
// outputLength deliberately omitted — required by WICG WebCrypto Modern
// Algos draft and Node webidl.js:880-897.
// Algos draft. Caught by strict WebIDL normalization (#1025) as
// TypeError before reaching the runtime length validator.
await subtle.digest({ name: 'KT128' }, new Uint8Array(0));
} catch (err) {
threw = true;
expect((err as Error).name).to.equal('OperationError');
expect((err as Error).name).to.equal('TypeError');
}
expect(threw).to.equal(true);
});
5 changes: 4 additions & 1 deletion example/src/tests/subtle/generateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,17 @@ async function testECKeyGen(
'NotSupportedError',
);
});
// Strict WebIDL normalization (#1025): an explicit `undefined` is
// indistinguishable from omission, so EcKeyGenParams.namedCurve is
// reported as missing rather than as an unrecognized curve.
await assertThrowsAsync(
async () =>
subtle.generateKey(
{ name, namedCurve: undefined },
true,
privateUsages,
),
"Unrecognized namedCurve 'undefined'",
"'namedCurve' is required",
);
},
);
Expand Down
194 changes: 164 additions & 30 deletions example/src/tests/subtle/supports.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,56 @@
import { expect } from 'chai';
import { Subtle } from 'react-native-quick-crypto';
import type { SubtleAlgorithm } from 'react-native-quick-crypto';
import { test } from '../util';

const SUITE = 'subtle.supports';

// --- Encrypt ---
test(SUITE, 'encrypt: AES-GCM is supported', () => {
expect(Subtle.supports('encrypt', 'AES-GCM')).to.equal(true);
// Strict WebIDL normalization (#1025): AeadParams.iv is required, so passing
// just the string returns false. Full params are needed to assert support.
test(SUITE, 'encrypt: AES-GCM with iv is supported', () => {
expect(
Subtle.supports('encrypt', {
name: 'AES-GCM',
iv: new Uint8Array(12),
}),
).to.equal(true);
});

test(SUITE, 'encrypt: AES-GCM without iv is not supported', () => {
expect(Subtle.supports('encrypt', 'AES-GCM')).to.equal(false);
});

test(SUITE, 'encrypt: AES-CBC with invalid iv length is not supported', () => {
expect(
Subtle.supports('encrypt', {
name: 'AES-CBC',
iv: new Uint8Array(12),
}),
).to.equal(false);
});

test(SUITE, 'encrypt: AES-GCM with invalid tagLength is not supported', () => {
expect(
Subtle.supports('encrypt', {
name: 'AES-GCM',
iv: new Uint8Array(12),
tagLength: 24,
}),
).to.equal(false);
});

test(SUITE, 'encrypt: RSA-OAEP is supported', () => {
expect(Subtle.supports('encrypt', 'RSA-OAEP')).to.equal(true);
});

test(SUITE, 'encrypt: ChaCha20-Poly1305 is supported', () => {
expect(Subtle.supports('encrypt', 'ChaCha20-Poly1305')).to.equal(true);
test(SUITE, 'encrypt: ChaCha20-Poly1305 with iv is supported', () => {
expect(
Subtle.supports('encrypt', {
name: 'ChaCha20-Poly1305',
iv: new Uint8Array(12),
}),
).to.equal(true);
});

test(SUITE, 'encrypt: HMAC is not supported', () => {
Expand All @@ -30,8 +66,21 @@ test(SUITE, 'sign: Ed25519 is supported', () => {
expect(Subtle.supports('sign', 'Ed25519')).to.equal(true);
});

test(SUITE, 'sign: ECDSA is supported', () => {
expect(Subtle.supports('sign', 'ECDSA')).to.equal(true);
// EcdsaParams.hash is required under strict normalization (#1025).
test(SUITE, 'sign: ECDSA with hash is supported', () => {
expect(Subtle.supports('sign', { name: 'ECDSA', hash: 'SHA-256' })).to.equal(
true,
);
});

test(SUITE, 'sign: ECDSA without hash is not supported', () => {
expect(Subtle.supports('sign', 'ECDSA')).to.equal(false);
});

test(SUITE, 'sign: ECDSA with non-SHA hash is not supported', () => {
expect(Subtle.supports('sign', { name: 'ECDSA', hash: 'MD5' })).to.equal(
false,
);
});

test(SUITE, 'sign: HMAC is supported', () => {
Expand All @@ -55,6 +104,33 @@ test(SUITE, 'digest: SHA-512 is supported', () => {
expect(Subtle.supports('digest', 'SHA-512')).to.equal(true);
});

test(
SUITE,
'digest: TurboSHAKE128 invalid outputLength is not supported',
() => {
expect(
Subtle.supports('digest', {
name: 'TurboSHAKE128',
outputLength: 0,
}),
).to.equal(false);
},
);

test(
SUITE,
'digest: TurboSHAKE128 invalid domainSeparation is not supported',
() => {
expect(
Subtle.supports('digest', {
name: 'TurboSHAKE128',
outputLength: 256,
domainSeparation: 0x80,
}),
).to.equal(false);
},
);

// --- GenerateKey ---
test(SUITE, 'generateKey: Ed25519 is supported', () => {
expect(Subtle.supports('generateKey', 'Ed25519')).to.equal(true);
Expand All @@ -70,20 +146,54 @@ test(SUITE, 'generateKey: HKDF is not supported', () => {

// --- DeriveBits ---
// HKDF/PBKDF2/Argon2 require an explicit length per Node webcrypto.js:1689-1714.
test(SUITE, 'deriveBits: HKDF with length is supported', () => {
expect(Subtle.supports('deriveBits', 'HKDF', 256)).to.equal(true);
// Under strict normalization (#1025) the dictionary members are also required.
const HKDF_FULL: SubtleAlgorithm = {
name: 'HKDF',
hash: 'SHA-256',
salt: new Uint8Array(0),
info: new Uint8Array(0),
};
const PBKDF2_FULL: SubtleAlgorithm = {
name: 'PBKDF2',
hash: 'SHA-256',
salt: new Uint8Array(8),
iterations: 1000,
};

test(SUITE, 'deriveBits: HKDF with full params + length is supported', () => {
expect(Subtle.supports('deriveBits', HKDF_FULL, 256)).to.equal(true);
});

test(SUITE, 'deriveBits: PBKDF2 with length is supported', () => {
expect(Subtle.supports('deriveBits', 'PBKDF2', 256)).to.equal(true);
test(SUITE, 'deriveBits: PBKDF2 with full params + length is supported', () => {
expect(Subtle.supports('deriveBits', PBKDF2_FULL, 256)).to.equal(true);
});

test(SUITE, 'deriveBits: PBKDF2 with zero iterations is not supported', () => {
expect(
Subtle.supports(
'deriveBits',
{
...PBKDF2_FULL,
iterations: 0,
},
256,
),
).to.equal(false);
});

test(SUITE, 'deriveBits: HKDF missing salt/info is not supported', () => {
expect(Subtle.supports('deriveBits', 'HKDF', 256)).to.equal(false);
});

test(SUITE, 'deriveBits: HKDF without length is not supported', () => {
expect(Subtle.supports('deriveBits', 'HKDF')).to.equal(false);
expect(Subtle.supports('deriveBits', HKDF_FULL)).to.equal(false);
});

test(SUITE, 'deriveBits: X25519 is supported', () => {
expect(Subtle.supports('deriveBits', 'X25519')).to.equal(true);
// EcdhKeyDeriveParams.public (a CryptoKey) is required, so calling
// supports('deriveBits', 'X25519') without it returns false under strict
// normalization — mirrors Node's behavior.
test(SUITE, 'deriveBits: X25519 without public key is not supported', () => {
expect(Subtle.supports('deriveBits', 'X25519')).to.equal(false);
});

test(SUITE, 'deriveBits: AES-GCM is not supported', () => {
Expand All @@ -93,20 +203,23 @@ test(SUITE, 'deriveBits: AES-GCM is not supported', () => {
// --- DeriveKey ---
test(SUITE, 'deriveKey: HKDF + AES-GCM with length 256 is supported', () => {
expect(
Subtle.supports('deriveKey', 'HKDF', { name: 'AES-GCM', length: 256 }),
Subtle.supports('deriveKey', HKDF_FULL, {
name: 'AES-GCM',
length: 256,
}),
).to.equal(true);
});

// AES key length is required for getKeyLength — Node webcrypto.js:269-279.
test(SUITE, 'deriveKey: HKDF + AES-GCM without length is not supported', () => {
expect(Subtle.supports('deriveKey', 'HKDF', 'AES-GCM')).to.equal(false);
expect(Subtle.supports('deriveKey', HKDF_FULL, 'AES-GCM')).to.equal(false);
});

test(
SUITE,
'deriveKey: HKDF without additional algorithm returns false',
() => {
expect(Subtle.supports('deriveKey', 'HKDF')).to.equal(false);
expect(Subtle.supports('deriveKey', HKDF_FULL)).to.equal(false);
},
);

Expand Down Expand Up @@ -176,24 +289,30 @@ test(SUITE, 'encapsulateKey: ML-KEM-768 + Ed25519 is not supported', () => {
);
});

// Under strict normalization (#1025), HmacImportParams.hash is required.
test(
SUITE,
'encapsulateKey: ML-KEM-768 + HMAC default length supported',
'encapsulateKey: ML-KEM-768 + HMAC without hash is not supported',
() => {
expect(Subtle.supports('encapsulateKey', 'ML-KEM-768', 'HMAC')).to.equal(
true,
false,
);
},
);

test(SUITE, 'encapsulateKey: ML-KEM-768 + HMAC length 256 supported', () => {
expect(
Subtle.supports('encapsulateKey', 'ML-KEM-768', {
name: 'HMAC',
length: 256,
}),
).to.equal(true);
});
test(
SUITE,
'encapsulateKey: ML-KEM-768 + HMAC with hash + length 256 supported',
() => {
expect(
Subtle.supports('encapsulateKey', 'ML-KEM-768', {
name: 'HMAC',
hash: 'SHA-256',
length: 256,
}),
).to.equal(true);
},
);

test(
SUITE,
Expand All @@ -202,6 +321,7 @@ test(
expect(
Subtle.supports('encapsulateKey', 'ML-KEM-768', {
name: 'HMAC',
hash: 'SHA-256',
length: 512,
}),
).to.equal(false);
Expand All @@ -210,19 +330,33 @@ test(

// --- DeriveBits per-algorithm length validators ---
test(SUITE, 'deriveBits: HKDF with non-multiple-of-8 length rejected', () => {
expect(Subtle.supports('deriveBits', 'HKDF', 257)).to.equal(false);
expect(Subtle.supports('deriveBits', HKDF_FULL, 257)).to.equal(false);
});

test(SUITE, 'deriveBits: PBKDF2 with non-multiple-of-8 length rejected', () => {
expect(Subtle.supports('deriveBits', 'PBKDF2', 257)).to.equal(false);
expect(Subtle.supports('deriveBits', PBKDF2_FULL, 257)).to.equal(false);
});

const ARGON2_FULL: SubtleAlgorithm = {
name: 'Argon2id',
nonce: new Uint8Array(16),
parallelism: 1,
memory: 8,
passes: 2,
};

test(SUITE, 'deriveBits: Argon2id length below 32 rejected', () => {
expect(Subtle.supports('deriveBits', 'Argon2id', 16)).to.equal(false);
expect(Subtle.supports('deriveBits', ARGON2_FULL, 16)).to.equal(false);
});

test(SUITE, 'deriveBits: Argon2id length 32 supported', () => {
expect(Subtle.supports('deriveBits', 'Argon2id', 32)).to.equal(true);
expect(Subtle.supports('deriveBits', ARGON2_FULL, 32)).to.equal(true);
});

// New: regression for #1025 — strict normalization rejects missing required
// dictionary members during deriveBits length validation.
test(SUITE, 'deriveBits: Argon2id missing required members rejected', () => {
expect(Subtle.supports('deriveBits', 'Argon2id', 32)).to.equal(false);
});

// --- Invalid operation ---
Expand Down
Loading
Loading