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
13 changes: 7 additions & 6 deletions example/src/tests/subtle/digest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,29 @@ kTests.forEach(([algorithm, legacyName, bitLength]) => {

// cSHAKE tests (XOF - extendable output functions)
test(SUITE, 'hash: cSHAKE128', async () => {
const outputLength = 32;
const checkValue = createHash('shake128', { outputLength })
const outputBytes = 32;
const checkValue = createHash('shake128', { outputLength: outputBytes })
.update(kData)
.digest()
.toString('hex');

// CShakeParams.outputLength is in bits per spec.
const result = await subtle.digest(
{ name: 'cSHAKE128', length: outputLength },
{ name: 'cSHAKE128', outputLength: outputBytes * 8 },
kData,
);
expect(ab2str(result)).to.equal(checkValue);
});

test(SUITE, 'hash: cSHAKE256', async () => {
const outputLength = 64;
const checkValue = createHash('shake256', { outputLength })
const outputBytes = 64;
const checkValue = createHash('shake256', { outputLength: outputBytes })
.update(kData)
.digest()
.toString('hex');

const result = await subtle.digest(
{ name: 'cSHAKE256', length: outputLength },
{ name: 'cSHAKE256', outputLength: outputBytes * 8 },
kData,
);
expect(ab2str(result)).to.equal(checkValue);
Expand Down
10 changes: 7 additions & 3 deletions example/src/tests/subtle/import_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3542,14 +3542,18 @@ for (const algorithm of ['KMAC128', 'KMAC256'] as const) {
);

const data = new TextEncoder().encode('jwk round-trip test');
const length = algorithm === 'KMAC128' ? 256 : 512;
const outputLength = algorithm === 'KMAC128' ? 256 : 512;

const sig1 = await subtle.sign(
{ name: algorithm, length },
{ name: algorithm, outputLength },
key as CryptoKey,
data,
);
const sig2 = await subtle.sign({ name: algorithm, length }, imported, data);
const sig2 = await subtle.sign(
{ name: algorithm, outputLength },
imported,
data,
);
expect(ab2str(sig1, 'hex')).to.equal(ab2str(sig2, 'hex'));
});
}
Expand Down
20 changes: 10 additions & 10 deletions example/src/tests/subtle/sign_verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,7 @@ for (const vec of kmacNistVectors) {
const signature = await subtle.sign(
{
name: vec.algorithm,
length: vec.length,
outputLength: vec.length,
customization: vec.customization,
},
key,
Expand Down Expand Up @@ -1239,7 +1239,7 @@ for (const vec of kmacNistVectors) {
const result = await subtle.verify(
{
name: vec.algorithm,
length: vec.length,
outputLength: vec.length,
customization: vec.customization,
},
key,
Expand All @@ -1259,18 +1259,18 @@ for (const algorithm of ['KMAC128', 'KMAC256'] as const) {
]);

const data = new TextEncoder().encode('Hello KMAC!');
const length = algorithm === 'KMAC128' ? 256 : 512;
const outputLength = algorithm === 'KMAC128' ? 256 : 512;

const signature = await subtle.sign(
{ name: algorithm, length },
{ name: algorithm, outputLength },
key as CryptoKey,
data,
);

expect(signature.byteLength).to.equal(length / 8);
expect(signature.byteLength).to.equal(outputLength / 8);

const valid = await subtle.verify(
{ name: algorithm, length },
{ name: algorithm, outputLength },
key as CryptoKey,
signature,
data,
Expand All @@ -1289,7 +1289,7 @@ test(SUITE, 'KMAC verify returns false for wrong signature', async () => {
const data = new TextEncoder().encode('test data');

const signature = await subtle.sign(
{ name: 'KMAC256', length: 256 },
{ name: 'KMAC256', outputLength: 256 },
key as CryptoKey,
data,
);
Expand All @@ -1298,7 +1298,7 @@ test(SUITE, 'KMAC verify returns false for wrong signature', async () => {
corrupted[0] = corrupted[0]! ^ 0xff;

const valid = await subtle.verify(
{ name: 'KMAC256', length: 256 },
{ name: 'KMAC256', outputLength: 256 },
key as CryptoKey,
corrupted,
data,
Expand All @@ -1318,7 +1318,7 @@ test(
const sig1 = await subtle.sign(
{
name: 'KMAC128',
length: 256,
outputLength: 256,
customization: new TextEncoder().encode('App A'),
},
key as CryptoKey,
Expand All @@ -1328,7 +1328,7 @@ test(
const sig2 = await subtle.sign(
{
name: 'KMAC128',
length: 256,
outputLength: 256,
customization: new TextEncoder().encode('App B'),
},
key as CryptoKey,
Expand Down
117 changes: 109 additions & 8 deletions example/src/tests/subtle/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,17 @@ test(SUITE, 'generateKey: HKDF is not supported', () => {
});

// --- DeriveBits ---
test(SUITE, 'deriveBits: HKDF is supported', () => {
expect(Subtle.supports('deriveBits', 'HKDF')).to.equal(true);
// 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);
});

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

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

test(SUITE, 'deriveBits: X25519 is supported', () => {
Expand All @@ -86,14 +91,25 @@ test(SUITE, 'deriveBits: AES-GCM is not supported', () => {
});

// --- DeriveKey ---
test(SUITE, 'deriveKey: HKDF is supported', () => {
expect(Subtle.supports('deriveKey', 'HKDF')).to.equal(true);
test(SUITE, 'deriveKey: HKDF + AES-GCM with length 256 is supported', () => {
expect(
Subtle.supports('deriveKey', 'HKDF', { name: 'AES-GCM', length: 256 }),
).to.equal(true);
});

test(SUITE, 'deriveKey: HKDF with AES-GCM output is supported', () => {
expect(Subtle.supports('deriveKey', 'HKDF', 'AES-GCM')).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);
});

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

// --- GetPublicKey ---
test(SUITE, 'getPublicKey: Ed25519 is supported', () => {
expect(Subtle.supports('getPublicKey', 'Ed25519')).to.equal(true);
Expand Down Expand Up @@ -124,6 +140,91 @@ test(SUITE, 'wrapKey: Ed25519 is not supported', () => {
expect(Subtle.supports('wrapKey', 'Ed25519')).to.equal(false);
});

test(SUITE, 'wrapKey: AES-KW with AES-GCM exportKey decomposition', () => {
expect(Subtle.supports('wrapKey', 'AES-KW', 'AES-GCM')).to.equal(true);
});

test(SUITE, 'wrapKey: AES-KW with FAKE exportKey decomposition', () => {
expect(Subtle.supports('wrapKey', 'AES-KW', 'FAKE' as never)).to.equal(false);
});

// --- UnwrapKey ---
test(SUITE, 'unwrapKey: AES-KW is supported', () => {
expect(Subtle.supports('unwrapKey', 'AES-KW')).to.equal(true);
});

test(SUITE, 'unwrapKey: AES-KW with AES-GCM importKey decomposition', () => {
expect(Subtle.supports('unwrapKey', 'AES-KW', 'AES-GCM')).to.equal(true);
});

test(SUITE, 'unwrapKey: AES-KW with FAKE importKey decomposition', () => {
expect(Subtle.supports('unwrapKey', 'AES-KW', 'FAKE' as never)).to.equal(
false,
);
});

// --- EncapsulateKey ---
test(SUITE, 'encapsulateKey: ML-KEM-768 + AES-GCM is supported', () => {
expect(Subtle.supports('encapsulateKey', 'ML-KEM-768', 'AES-GCM')).to.equal(
true,
);
});

test(SUITE, 'encapsulateKey: ML-KEM-768 + Ed25519 is not supported', () => {
expect(Subtle.supports('encapsulateKey', 'ML-KEM-768', 'Ed25519')).to.equal(
false,
);
});

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

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 non-default length not supported',
() => {
expect(
Subtle.supports('encapsulateKey', 'ML-KEM-768', {
name: 'HMAC',
length: 512,
}),
).to.equal(false);
},
);

// --- 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);
});

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

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

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

// --- Invalid operation ---
test(SUITE, 'invalid operation returns false', () => {
expect(Subtle.supports('nonexistent', 'AES-GCM')).to.equal(false);
Expand Down
16 changes: 11 additions & 5 deletions packages/react-native-quick-crypto/src/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,19 +267,25 @@ export const asyncDigest = async (
}

if (name === 'cSHAKE128' || name === 'cSHAKE256') {
if (typeof algorithm.length !== 'number' || algorithm.length <= 0) {
// CShakeParams.outputLength is required (in bits) per the WICG modern-algos
// spec, renamed from `length` (commit ab8dc2b84c2). Mirror Node's
// hash.js:223-228 / webidl.js:570-595.
if (
typeof algorithm.outputLength !== 'number' ||
algorithm.outputLength <= 0
) {
throw lazyDOMException(
'cSHAKE requires a length parameter',
'CShakeParams.outputLength is required',
'OperationError',
);
}
if (algorithm.length % 8) {
if (algorithm.outputLength % 8) {
throw lazyDOMException(
'Unsupported CShakeParams length',
'Unsupported CShakeParams outputLength',
'NotSupportedError',
);
}
return internalDigest(algorithm, data, algorithm.length);
return internalDigest(algorithm, data, algorithm.outputLength / 8);
}

throw lazyDOMException(
Expand Down
Loading
Loading