@@ -28,6 +28,7 @@ import {
2828 // createPublicKey, // TODO: for 'bad usages' test
2929 // createPrivateKey, // TODO: for 'bad usages' test
3030 getRandomValues ,
31+ PQC_SEEDLESS_PKCS8_LENGTHS ,
3132 subtle ,
3233} from 'react-native-quick-crypto' ;
3334import { MLKEM_VARIANTS } from './mlkem_constants' ;
@@ -2283,6 +2284,68 @@ test(SUITE, 'ML-DSA-44 importKey rejects jwk alg mismatch', async () => {
22832284 ) ;
22842285} ) ;
22852286
2287+ // --- ML-DSA seedless PKCS#8 import rejection (PR #997) ---
2288+
2289+ for ( const variant of MLDSA_VARIANTS ) {
2290+ test ( SUITE , `${ variant } pkcs8 import rejects seedless key` , async ( ) => {
2291+ const seedless = new Uint8Array ( PQC_SEEDLESS_PKCS8_LENGTHS [ variant ] ?? 0 ) ;
2292+ await assertThrowsAsync (
2293+ async ( ) =>
2294+ await subtle . importKey ( 'pkcs8' , seedless , { name : variant } , true , [
2295+ 'sign' ,
2296+ ] ) ,
2297+ 'NotSupportedError' ,
2298+ ) ;
2299+ } ) ;
2300+
2301+ test (
2302+ SUITE ,
2303+ `${ variant } pkcs8 export produces 54-byte seed-only encoding` ,
2304+ async ( ) => {
2305+ const keyPair = ( await subtle . generateKey ( { name : variant } , true , [
2306+ 'sign' ,
2307+ 'verify' ,
2308+ ] ) ) as CryptoKeyPair ;
2309+ const exported = ( await subtle . exportKey (
2310+ 'pkcs8' ,
2311+ keyPair . privateKey as CryptoKey ,
2312+ ) ) as ArrayBuffer ;
2313+ expect ( exported . byteLength ) . to . equal ( 54 ) ;
2314+ } ,
2315+ ) ;
2316+ }
2317+
2318+ test ( SUITE , 'ML-DSA-65 pkcs8 round-trip + sign/verify' , async ( ) => {
2319+ const keyPair = ( await subtle . generateKey ( { name : 'ML-DSA-65' } , true , [
2320+ 'sign' ,
2321+ 'verify' ,
2322+ ] ) ) as CryptoKeyPair ;
2323+
2324+ const exported = ( await subtle . exportKey (
2325+ 'pkcs8' ,
2326+ keyPair . privateKey as CryptoKey ,
2327+ ) ) as ArrayBuffer ;
2328+ expect ( exported . byteLength ) . to . equal ( 54 ) ;
2329+
2330+ const imported = await subtle . importKey (
2331+ 'pkcs8' ,
2332+ exported ,
2333+ { name : 'ML-DSA-65' } ,
2334+ true ,
2335+ [ 'sign' ] ,
2336+ ) ;
2337+
2338+ const message = new TextEncoder ( ) . encode ( 'round-trip test' ) ;
2339+ const signature = await subtle . sign ( { name : 'ML-DSA-65' } , imported , message ) ;
2340+ const verified = await subtle . verify (
2341+ { name : 'ML-DSA-65' } ,
2342+ keyPair . publicKey as CryptoKey ,
2343+ signature ,
2344+ message ,
2345+ ) ;
2346+ expect ( verified ) . to . equal ( true ) ;
2347+ } ) ;
2348+
22862349// --- ML-DSA raw-public and raw-seed export/import tests ---
22872350// 'raw-public' is normalized to 'raw' internally (public key bytes)
22882351// 'raw-seed' passes through directly (64-byte private seed)
@@ -2684,6 +2747,69 @@ test(SUITE, 'ML-KEM-768 importKey raw rejects bad usages', async () => {
26842747 ) ;
26852748} ) ;
26862749
2750+ // --- ML-KEM seedless PKCS#8 import rejection + export length (PR #997) ---
2751+
2752+ for ( const variant of MLKEM_VARIANTS ) {
2753+ test ( SUITE , `${ variant } pkcs8 import rejects seedless key` , async ( ) => {
2754+ const seedless = new Uint8Array ( PQC_SEEDLESS_PKCS8_LENGTHS [ variant ] ?? 0 ) ;
2755+ await assertThrowsAsync (
2756+ async ( ) =>
2757+ await subtle . importKey ( 'pkcs8' , seedless , { name : variant } , true , [
2758+ 'decapsulateBits' ,
2759+ ] ) ,
2760+ 'NotSupportedError' ,
2761+ ) ;
2762+ } ) ;
2763+
2764+ test (
2765+ SUITE ,
2766+ `${ variant } pkcs8 export produces 86-byte seed-only encoding` ,
2767+ async ( ) => {
2768+ const keyPair = ( await subtle . generateKey ( { name : variant } , true , [
2769+ 'encapsulateBits' ,
2770+ 'decapsulateBits' ,
2771+ ] ) ) as CryptoKeyPair ;
2772+ const exported = ( await subtle . exportKey (
2773+ 'pkcs8' ,
2774+ keyPair . privateKey as CryptoKey ,
2775+ ) ) as ArrayBuffer ;
2776+ expect ( exported . byteLength ) . to . equal ( 86 ) ;
2777+ } ,
2778+ ) ;
2779+ }
2780+
2781+ test ( SUITE , 'ML-KEM-768 pkcs8 round-trip + decapsulate' , async ( ) => {
2782+ const keyPair = ( await subtle . generateKey ( { name : 'ML-KEM-768' } , true , [
2783+ 'encapsulateBits' ,
2784+ 'decapsulateBits' ,
2785+ ] ) ) as CryptoKeyPair ;
2786+
2787+ const exported = ( await subtle . exportKey (
2788+ 'pkcs8' ,
2789+ keyPair . privateKey as CryptoKey ,
2790+ ) ) as ArrayBuffer ;
2791+ expect ( exported . byteLength ) . to . equal ( 86 ) ;
2792+
2793+ const imported = await subtle . importKey (
2794+ 'pkcs8' ,
2795+ exported ,
2796+ { name : 'ML-KEM-768' } ,
2797+ true ,
2798+ [ 'decapsulateBits' ] ,
2799+ ) ;
2800+
2801+ const { sharedKey, ciphertext } = await subtle . encapsulateBits (
2802+ { name : 'ML-KEM-768' } ,
2803+ keyPair . publicKey as CryptoKey ,
2804+ ) ;
2805+ const recovered = await subtle . decapsulateBits (
2806+ { name : 'ML-KEM-768' } ,
2807+ imported ,
2808+ ciphertext ,
2809+ ) ;
2810+ expect ( new Uint8Array ( recovered ) ) . to . deep . equal ( new Uint8Array ( sharedKey ) ) ;
2811+ } ) ;
2812+
26872813// --- Ed25519/Ed448 raw import/export Tests ---
26882814
26892815const edCurves = [
0 commit comments