Skip to content

Commit 3e06617

Browse files
authored
feat: add KMAC128/KMAC256 support to subtle API (#944)
1 parent ad5a961 commit 3e06617

16 files changed

Lines changed: 800 additions & 119 deletions

File tree

.docs/implementation-coverage.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,12 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
265265
- ✅ Class: `CryptoKeyPair`
266266
-`cryptoKeyPair.privateKey`
267267
-`cryptoKeyPair.publicKey`
268-
- 🚧 Class: `CryptoSubtle`
268+
- Class: `CryptoSubtle`
269269
- (see below)
270270

271271
# `SubtleCrypto`
272272

273-
- 🚧 Class: `SubtleCrypto`
273+
- Class: `SubtleCrypto`
274274
- ✅ static `supports(operation, algorithm[, lengthOrAdditionalAlgorithm])`
275275
-`subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)`
276276
-`subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)`
@@ -282,12 +282,12 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
282282
-`subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)`
283283
-`subtle.encrypt(algorithm, key, data)`
284284
-`subtle.exportKey(format, key)`
285-
- 🚧 `subtle.generateKey(algorithm, extractable, keyUsages)`
285+
- `subtle.generateKey(algorithm, extractable, keyUsages)`
286286
-`subtle.getPublicKey(key, keyUsages)`
287287
-`subtle.importKey(format, keyData, algorithm, extractable, keyUsages)`
288-
- 🚧 `subtle.sign(algorithm, key, data)`
288+
- `subtle.sign(algorithm, key, data)`
289289
-`subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)`
290-
- 🚧 `subtle.verify(algorithm, key, signature, data)`
290+
- `subtle.verify(algorithm, key, signature, data)`
291291
-`subtle.wrapKey(format, key, wrappingKey, wrapAlgo)`
292292

293293
## `subtle.decrypt`
@@ -369,6 +369,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
369369
| `Ed25519` ||||| || |
370370
| `Ed448` ||||| || |
371371
| `HMAC` | | |||| | |
372+
| `KMAC128` | | |||| | |
373+
| `KMAC256` | | |||| | |
372374
| `ML-DSA-44` |||| | |||
373375
| `ML-DSA-65` |||| | |||
374376
| `ML-DSA-87` |||| | |||
@@ -416,8 +418,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
416418
| `AES-OCB` ||
417419
| `ChaCha20-Poly1305` ||
418420
| `HMAC` ||
419-
| `KMAC128` | |
420-
| `KMAC256` | |
421+
| `KMAC128` | |
422+
| `KMAC256` | |
421423

422424
## `subtle.importKey`
423425

@@ -438,6 +440,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
438440
| `Ed448` ||||| || |
439441
| `HKDF` | | | ||| | |
440442
| `HMAC` | | |||| | |
443+
| `KMAC128` | | |||| | |
444+
| `KMAC256` | | |||| | |
441445
| `ML-DSA-44` |||| | |||
442446
| `ML-DSA-65` |||| | |||
443447
| `ML-DSA-87` |||| | |||
@@ -459,8 +463,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
459463
| `Ed25519` ||
460464
| `Ed448` ||
461465
| `HMAC` ||
462-
| `KMAC128` | |
463-
| `KMAC256` | |
466+
| `KMAC128` | |
467+
| `KMAC256` | |
464468
| `ML-DSA-44` ||
465469
| `ML-DSA-65` ||
466470
| `ML-DSA-87` ||
@@ -516,8 +520,8 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
516520
| `Ed25519` ||
517521
| `Ed448` ||
518522
| `HMAC` ||
519-
| `KMAC128` | |
520-
| `KMAC256` | |
523+
| `KMAC128` | |
524+
| `KMAC256` | |
521525
| `ML-DSA-44` ||
522526
| `ML-DSA-65` ||
523527
| `ML-DSA-87` ||

docs/data/coverage.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,16 @@ export const COVERAGE_DATA: CoverageCategory[] = [
401401
note: 'spki, pkcs8, raw, jwk, raw-public',
402402
},
403403
{ name: 'HMAC', status: 'implemented' },
404+
{
405+
name: 'KMAC128',
406+
status: 'implemented',
407+
note: 'raw, raw-secret, jwk',
408+
},
409+
{
410+
name: 'KMAC256',
411+
status: 'implemented',
412+
note: 'raw, raw-secret, jwk',
413+
},
404414
{
405415
name: 'ML-DSA-44',
406416
status: 'partial',
@@ -453,8 +463,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
453463
{ name: 'AES-OCB', status: 'implemented' },
454464
{ name: 'ChaCha20-Poly1305', status: 'implemented' },
455465
{ name: 'HMAC', status: 'implemented' },
456-
{ name: 'KMAC128', status: 'missing' },
457-
{ name: 'KMAC256', status: 'missing' },
466+
{ name: 'KMAC128', status: 'implemented' },
467+
{ name: 'KMAC256', status: 'implemented' },
458468
],
459469
},
460470
{
@@ -491,6 +501,16 @@ export const COVERAGE_DATA: CoverageCategory[] = [
491501
},
492502
{ name: 'HKDF', status: 'implemented', note: 'raw, raw-secret' },
493503
{ name: 'HMAC', status: 'implemented' },
504+
{
505+
name: 'KMAC128',
506+
status: 'implemented',
507+
note: 'raw, raw-secret, jwk',
508+
},
509+
{
510+
name: 'KMAC256',
511+
status: 'implemented',
512+
note: 'raw, raw-secret, jwk',
513+
},
494514
{
495515
name: 'ML-DSA-44',
496516
status: 'partial',
@@ -536,8 +556,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
536556
{ name: 'Ed25519', status: 'implemented' },
537557
{ name: 'Ed448', status: 'implemented' },
538558
{ name: 'HMAC', status: 'implemented' },
539-
{ name: 'KMAC128', status: 'missing' },
540-
{ name: 'KMAC256', status: 'missing' },
559+
{ name: 'KMAC128', status: 'implemented' },
560+
{ name: 'KMAC256', status: 'implemented' },
541561
{ name: 'ML-DSA-44', status: 'implemented' },
542562
{ name: 'ML-DSA-65', status: 'implemented' },
543563
{ name: 'ML-DSA-87', status: 'implemented' },
@@ -564,8 +584,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
564584
{ name: 'Ed25519', status: 'implemented' },
565585
{ name: 'Ed448', status: 'implemented' },
566586
{ name: 'HMAC', status: 'implemented' },
567-
{ name: 'KMAC128', status: 'missing' },
568-
{ name: 'KMAC256', status: 'missing' },
587+
{ name: 'KMAC128', status: 'implemented' },
588+
{ name: 'KMAC256', status: 'implemented' },
569589
{ name: 'ML-DSA-44', status: 'implemented' },
570590
{ name: 'ML-DSA-65', status: 'implemented' },
571591
{ name: 'ML-DSA-87', status: 'implemented' },

example/src/tests/subtle/import_export.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2893,3 +2893,58 @@ test(SUITE, 'ML-KEM-768 unwrapKey with AES-GCM', async () => {
28932893
);
28942894
expect(new Uint8Array(decapsulated)).to.deep.equal(new Uint8Array(sharedKey));
28952895
});
2896+
2897+
// --- KMAC Import/Export Tests ---
2898+
2899+
for (const algorithm of ['KMAC128', 'KMAC256'] as const) {
2900+
test(SUITE, `KMAC import/export raw (${algorithm})`, async () => {
2901+
const keyBytes = new Uint8Array(32);
2902+
globalThis.crypto.getRandomValues(keyBytes);
2903+
2904+
const key = await subtle.importKey(
2905+
'raw',
2906+
keyBytes,
2907+
{ name: algorithm },
2908+
true,
2909+
['sign', 'verify'],
2910+
);
2911+
2912+
const exported = await subtle.exportKey('raw', key);
2913+
expect(ab2str(exported as ArrayBuffer, 'hex')).to.equal(
2914+
ab2str(keyBytes.buffer as ArrayBuffer, 'hex'),
2915+
);
2916+
});
2917+
}
2918+
2919+
for (const algorithm of ['KMAC128', 'KMAC256'] as const) {
2920+
test(SUITE, `KMAC import/export jwk (${algorithm})`, async () => {
2921+
const key = await subtle.generateKey({ name: algorithm }, true, [
2922+
'sign',
2923+
'verify',
2924+
]);
2925+
2926+
const jwk = await subtle.exportKey('jwk', key as CryptoKey);
2927+
const jwkObj = jwk as Record<string, unknown>;
2928+
expect(jwkObj.alg).to.equal(algorithm === 'KMAC128' ? 'K128' : 'K256');
2929+
expect(jwkObj.kty).to.equal('oct');
2930+
2931+
const imported = await subtle.importKey(
2932+
'jwk',
2933+
jwk,
2934+
{ name: algorithm },
2935+
true,
2936+
['sign', 'verify'],
2937+
);
2938+
2939+
const data = new TextEncoder().encode('jwk round-trip test');
2940+
const length = algorithm === 'KMAC128' ? 256 : 512;
2941+
2942+
const sig1 = await subtle.sign(
2943+
{ name: algorithm, length },
2944+
key as CryptoKey,
2945+
data,
2946+
);
2947+
const sig2 = await subtle.sign({ name: algorithm, length }, imported, data);
2948+
expect(ab2str(sig1, 'hex')).to.equal(ab2str(sig2, 'hex'));
2949+
});
2950+
}

0 commit comments

Comments
 (0)