Skip to content

Commit a74df6c

Browse files
committed
feat: add raw-secret format, fix JWK alg names, and update coverage
Add raw-secret format support to importKey/exportKey as a Node.js- compatible alias for raw. Fix getAlgorithmName to produce correct JWK alg field values (e.g. A256GCM instead of AES-GCM256). Add AES-OCB JWK and raw-secret import/export tests. Fix existing raw-secret tests to actually use raw-secret format. Update coverage docs to mark all symmetric key import/export as implemented.
1 parent b215ae9 commit a74df6c

5 files changed

Lines changed: 97 additions & 34 deletions

File tree

.docs/implementation-coverage.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
286286
| `AES-CTR` ||
287287
| `AES-CBC` ||
288288
| `AES-GCM` ||
289+
| `AES-OCB` ||
289290
| `ChaCha20-Poly1305` ||
290291

291292
## `subtle.deriveBits`
@@ -325,7 +326,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
325326
| `AES-CTR` ||
326327
| `AES-CBC` ||
327328
| `AES-GCM` ||
328-
| `AES-OCB` | |
329+
| `AES-OCB` | |
329330
| `ChaCha20-Poly1305` ||
330331
| `RSA-OAEP` ||
331332

@@ -336,7 +337,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
336337
| `AES-CTR` | | |||| | |
337338
| `AES-GCM` | | |||| | |
338339
| `AES-KW` | | |||| | |
339-
| `AES-OCB` | | | | | | | |
340+
| `AES-OCB` | | | | | | | |
340341
| `ChaCha20-Poly1305` | | || || | |
341342
| `ECDH` ||||| || |
342343
| `ECDSA` ||||| || |
@@ -385,7 +386,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
385386
| `AES-CBC` ||
386387
| `AES-GCM` ||
387388
| `AES-KW` ||
388-
| `AES-OCB` | |
389+
| `AES-OCB` | |
389390
| `ChaCha20-Poly1305` ||
390391
| `HMAC` ||
391392

@@ -396,7 +397,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
396397
| `AES-CTR` | | |||| | |
397398
| `AES-GCM` | | |||| | |
398399
| `AES-KW` | | |||| | |
399-
| `AES-OCB` | | | | | | | |
400+
| `AES-OCB` | | | | | | | |
400401
| `ChaCha20-Poly1305` | | || || | |
401402
| `ECDH` ||||| || |
402403
| `ECDSA` ||||| || |
@@ -439,7 +440,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
439440
| `AES-CTR` ||
440441
| `AES-GCM` ||
441442
| `AES-KW` ||
442-
| `AES-OCB` | |
443+
| `AES-OCB` | |
443444
| `ChaCha20-Poly1305` ||
444445
| `RSA-OAEP` ||
445446

@@ -450,7 +451,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
450451
| `AES-CTR` ||
451452
| `AES-GCM` ||
452453
| `AES-KW` ||
453-
| `AES-OCB` | |
454+
| `AES-OCB` | |
454455
| `ChaCha20-Poly1305` ||
455456
| `ECDH` ||
456457
| `ECDSA` ||
@@ -491,6 +492,6 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
491492
| `AES-CTR` ||
492493
| `AES-GCM` ||
493494
| `AES-KW` ||
494-
| `AES-OCB` | |
495+
| `AES-OCB` | |
495496
| `ChaCha20-Poly1305` ||
496497
| `RSA-OAEP` ||

docs/data/coverage.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -320,16 +320,12 @@ export const COVERAGE_DATA: CoverageCategory[] = [
320320
{
321321
name: 'crypto.subtle.exportKey',
322322
subItems: [
323-
{ name: 'AES-CBC', status: 'partial', note: 'jwk, raw' },
324-
{ name: 'AES-CTR', status: 'partial', note: 'jwk, raw, raw-secret' },
325-
{ name: 'AES-GCM', status: 'partial', note: 'jwk, raw, raw-secret' },
326-
{ name: 'AES-KW', status: 'partial', note: 'jwk, raw, raw-secret' },
327-
{ name: 'AES-OCB', status: 'partial', note: 'jwk, raw, raw-secret' },
328-
{
329-
name: 'ChaCha20-Poly1305',
330-
status: 'partial',
331-
note: 'jwk, raw',
332-
},
323+
{ name: 'AES-CBC', status: 'implemented' },
324+
{ name: 'AES-CTR', status: 'implemented' },
325+
{ name: 'AES-GCM', status: 'implemented' },
326+
{ name: 'AES-KW', status: 'implemented' },
327+
{ name: 'AES-OCB', status: 'implemented' },
328+
{ name: 'ChaCha20-Poly1305', status: 'implemented' },
333329
{
334330
name: 'ECDH',
335331
status: 'partial',
@@ -342,7 +338,7 @@ export const COVERAGE_DATA: CoverageCategory[] = [
342338
},
343339
{ name: 'Ed25519', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
344340
{ name: 'Ed448', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
345-
{ name: 'HMAC', status: 'partial', note: 'jwk, raw, raw-secret' },
341+
{ name: 'HMAC', status: 'implemented' },
346342
{
347343
name: 'ML-DSA-44',
348344
status: 'partial',
@@ -400,12 +396,12 @@ export const COVERAGE_DATA: CoverageCategory[] = [
400396
{
401397
name: 'crypto.subtle.importKey',
402398
subItems: [
403-
{ name: 'AES-CBC', status: 'partial', note: 'jwk, raw, raw-secret' },
404-
{ name: 'AES-CTR', status: 'partial', note: 'jwk, raw, raw-secret' },
405-
{ name: 'AES-GCM', status: 'partial', note: 'jwk, raw, raw-secret' },
406-
{ name: 'AES-KW', status: 'partial', note: 'jwk, raw, raw-secret' },
407-
{ name: 'AES-OCB', status: 'partial', note: 'jwk, raw, raw-secret' },
408-
{ name: 'ChaCha20-Poly1305', status: 'partial', note: 'jwk, raw' },
399+
{ name: 'AES-CBC', status: 'implemented' },
400+
{ name: 'AES-CTR', status: 'implemented' },
401+
{ name: 'AES-GCM', status: 'implemented' },
402+
{ name: 'AES-KW', status: 'implemented' },
403+
{ name: 'AES-OCB', status: 'implemented' },
404+
{ name: 'ChaCha20-Poly1305', status: 'implemented' },
409405
{
410406
name: 'ECDH',
411407
status: 'partial',
@@ -418,8 +414,8 @@ export const COVERAGE_DATA: CoverageCategory[] = [
418414
},
419415
{ name: 'Ed25519', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
420416
{ name: 'Ed448', status: 'partial', note: 'spki, pkcs8, raw, jwk' },
421-
{ name: 'HKDF', status: 'partial', note: 'raw' },
422-
{ name: 'HMAC', status: 'partial', note: 'jwk, raw, raw-secret' },
417+
{ name: 'HKDF', status: 'implemented' },
418+
{ name: 'HMAC', status: 'implemented' },
423419
{
424420
name: 'ML-DSA-44',
425421
status: 'partial',
@@ -438,7 +434,7 @@ export const COVERAGE_DATA: CoverageCategory[] = [
438434
{ name: 'ML-KEM-512', status: 'missing' },
439435
{ name: 'ML-KEM-768', status: 'missing' },
440436
{ name: 'ML-KEM-1024', status: 'missing' },
441-
{ name: 'PBKDF2', status: 'partial', note: 'raw, raw-secret' },
437+
{ name: 'PBKDF2', status: 'implemented' },
442438
{ name: 'RSA-OAEP', status: 'partial', note: 'spki, pkcs8, jwk' },
443439
{ name: 'RSA-PSS', status: 'partial', note: 'spki, pkcs8, jwk' },
444440
{

example/src/tests/subtle/import_export.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,14 +1795,14 @@ sizes.forEach(size => {
17951795
test(SUITE, 'AES import/export raw-secret format', async () => {
17961796
const keyData = getRandomValues(new Uint8Array(32));
17971797
const key = await subtle.importKey(
1798-
'raw',
1798+
'raw-secret',
17991799
keyData,
18001800
{ name: 'AES-GCM', length: 256 },
18011801
true,
18021802
['encrypt', 'decrypt'],
18031803
);
18041804

1805-
const exported = await subtle.exportKey('raw', key);
1805+
const exported = await subtle.exportKey('raw-secret', key);
18061806
expect(Buffer.from(exported as ArrayBuffer).toString('hex')).to.equal(
18071807
Buffer.from(keyData as Uint8Array).toString('hex'),
18081808
);
@@ -1811,14 +1811,14 @@ test(SUITE, 'AES import/export raw-secret format', async () => {
18111811
test(SUITE, 'HMAC import/export raw-secret format', async () => {
18121812
const keyData = getRandomValues(new Uint8Array(32));
18131813
const key = await subtle.importKey(
1814-
'raw',
1814+
'raw-secret',
18151815
keyData,
18161816
{ name: 'HMAC', hash: 'SHA-256' },
18171817
true,
18181818
['sign', 'verify'],
18191819
);
18201820

1821-
const exported = await subtle.exportKey('raw', key);
1821+
const exported = await subtle.exportKey('raw-secret', key);
18221822
expect(Buffer.from(exported as ArrayBuffer).toString('hex')).to.equal(
18231823
Buffer.from(keyData as Uint8Array).toString('hex'),
18241824
);
@@ -1827,7 +1827,7 @@ test(SUITE, 'HMAC import/export raw-secret format', async () => {
18271827
test(SUITE, 'PBKDF2 import raw-secret format', async () => {
18281828
const keyData = getRandomValues(new Uint8Array(32));
18291829
const key = await subtle.importKey(
1830-
'raw',
1830+
'raw-secret',
18311831
keyData,
18321832
{ name: 'PBKDF2' },
18331833
false,
@@ -2281,3 +2281,51 @@ for (const { name: curveName, rawSize } of edCurves) {
22812281
expect(isValid).to.equal(true);
22822282
});
22832283
}
2284+
2285+
// AES-OCB JWK export/import roundtrip
2286+
test(SUITE, 'AES-OCB export/import jwk', async () => {
2287+
const key = await subtle.generateKey({ name: 'AES-OCB', length: 256 }, true, [
2288+
'encrypt',
2289+
'decrypt',
2290+
]);
2291+
2292+
const jwk = (await subtle.exportKey('jwk', key as CryptoKey)) as JWK;
2293+
2294+
expect(jwk.kty).to.equal('oct');
2295+
expect(jwk.alg).to.equal('A256OCB');
2296+
expect(jwk.ext).to.equal(true);
2297+
expect(jwk.key_ops).to.have.members(['encrypt', 'decrypt']);
2298+
expect(jwk.k).to.be.a('string');
2299+
2300+
const imported = await subtle.importKey(
2301+
'jwk',
2302+
jwk,
2303+
{ name: 'AES-OCB' },
2304+
true,
2305+
['encrypt', 'decrypt'],
2306+
);
2307+
2308+
const exportedRaw1 = await subtle.exportKey('raw', key as CryptoKey);
2309+
const exportedRaw2 = await subtle.exportKey('raw', imported as CryptoKey);
2310+
expect(Buffer.from(exportedRaw1 as ArrayBuffer).toString('hex')).to.equal(
2311+
Buffer.from(exportedRaw2 as ArrayBuffer).toString('hex'),
2312+
);
2313+
});
2314+
2315+
// AES-OCB raw-secret import/export
2316+
test(SUITE, 'AES-OCB import/export raw-secret', async () => {
2317+
const keyData = getRandomValues(new Uint8Array(32));
2318+
2319+
const key = await subtle.importKey(
2320+
'raw-secret',
2321+
keyData,
2322+
{ name: 'AES-OCB' },
2323+
true,
2324+
['encrypt', 'decrypt'],
2325+
);
2326+
2327+
const exported = await subtle.exportKey('raw-secret', key as CryptoKey);
2328+
expect(Buffer.from(exported as ArrayBuffer).toString('hex')).to.equal(
2329+
Buffer.from(keyData as Uint8Array).toString('hex'),
2330+
);
2331+
});

packages/react-native-quick-crypto/src/subtle.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,22 @@ function normalizeAlgorithm(
9191
}
9292

9393
function getAlgorithmName(name: string, length: number): string {
94-
return `${name}${length}`;
94+
switch (name) {
95+
case 'AES-CBC':
96+
return `A${length}CBC`;
97+
case 'AES-CTR':
98+
return `A${length}CTR`;
99+
case 'AES-GCM':
100+
return `A${length}GCM`;
101+
case 'AES-KW':
102+
return `A${length}KW`;
103+
case 'AES-OCB':
104+
return `A${length}OCB`;
105+
case 'ChaCha20-Poly1305':
106+
return 'C20P';
107+
default:
108+
return `${name}${length}`;
109+
}
95110
}
96111

97112
// Placeholder implementations for missing functions
@@ -1733,6 +1748,8 @@ export class Subtle {
17331748
): Promise<ArrayBuffer | JWK> {
17341749
if (!key.extractable) throw new Error('key is not extractable');
17351750

1751+
if (format === 'raw-secret') format = 'raw';
1752+
17361753
switch (format) {
17371754
case 'spki':
17381755
return (await exportKeySpki(key)) as ArrayBuffer;
@@ -1967,6 +1984,7 @@ export class Subtle {
19671984
extractable: boolean,
19681985
keyUsages: KeyUsage[],
19691986
): Promise<CryptoKey> {
1987+
if (format === 'raw-secret') format = 'raw';
19701988
const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey');
19711989
let result: CryptoKey;
19721990
switch (normalizedAlgorithm.name) {

packages/react-native-quick-crypto/src/utils/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ export type DiffieHellmanCallback = (
462462
// from @paulmillr/noble-curves
463463
export type Hex = string | Uint8Array;
464464

465-
export type ImportFormat = 'raw' | 'pkcs8' | 'spki' | 'jwk';
465+
export type ImportFormat = 'raw' | 'raw-secret' | 'pkcs8' | 'spki' | 'jwk';
466466

467467
export type Operation =
468468
| 'encrypt'

0 commit comments

Comments
 (0)