@@ -306,6 +306,60 @@ pub(crate) fn mlkem_decapsulate(
306306 Ok ( shared_secret)
307307}
308308
309+ // ============================================================================
310+ // ML-KEM Public Key Import
311+ // ============================================================================
312+
313+ /// Import an ML-KEM public key from raw bytes.
314+ ///
315+ /// Constructs a `SECKEYPublicKey` with `keyType = kyberKey` and the
316+ /// appropriate `KyberParams`, then uses `SECKEY_CopyPublicKey` to create
317+ /// a properly arena-backed copy, and `PK11_ImportPublicKey` to register
318+ /// it in PKCS#11.
319+ pub fn import_mlkem_public_key (
320+ raw_key : & [ u8 ] ,
321+ params : MlKemParameterSet ,
322+ ) -> Res < PublicKey > {
323+ init ( ) ?;
324+
325+ let expected_size = params. public_key_bytes ( ) ;
326+ if raw_key. len ( ) != expected_size {
327+ return Err ( Error :: InvalidInput ) ;
328+ }
329+
330+ let kyber_params = match params {
331+ MlKemParameterSet :: MlKem768 => p11:: KyberParams_params_ml_kem768 ,
332+ MlKemParameterSet :: MlKem1024 => p11:: KyberParams_params_ml_kem1024 ,
333+ } ;
334+
335+ unsafe {
336+ let mut temp_key: p11:: SECKEYPublicKey = std:: mem:: zeroed ( ) ;
337+ temp_key. keyType = p11:: KeyType_kyberKey ;
338+ temp_key. u . kyber . as_mut ( ) . params = kyber_params;
339+ temp_key. u . kyber . as_mut ( ) . publicValue = SECItem {
340+ type_ : crate :: SECItemType :: siBuffer,
341+ data : raw_key. as_ptr ( ) as * mut u8 ,
342+ len : raw_key. len ( ) as u32 ,
343+ } ;
344+
345+ // SECKEY_CopyPublicKey allocates a new arena and deep-copies
346+ let copied: PublicKey = p11:: SECKEY_CopyPublicKey ( & temp_key) . into_result ( ) ?;
347+
348+ // Register in PKCS#11
349+ let slot = Slot :: internal ( ) ?;
350+ let handle = p11:: PK11_ImportPublicKey (
351+ * slot,
352+ * copied,
353+ crate :: PR_FALSE ,
354+ ) ;
355+ if handle == pkcs11_bindings:: CK_INVALID_HANDLE {
356+ return Err ( Error :: InvalidInput ) ;
357+ }
358+
359+ Ok ( copied)
360+ }
361+ }
362+
309363// ============================================================================
310364// Public API
311365// ============================================================================
@@ -616,6 +670,69 @@ mod tests {
616670 // Legacy API tests (for backwards compatibility)
617671 // ========================================================================
618672
673+ // ========================================================================
674+ // Import tests
675+ // ========================================================================
676+
677+ #[ test]
678+ fn test_mlkem768_import_public_key_roundtrip ( ) {
679+ use crate :: err:: secstatus_to_res;
680+ use crate :: util:: SECItemMut ;
681+
682+ fixture_init ( ) ;
683+
684+ // Generate a keypair
685+ let keypair = generate_keypair ( KemParameterSet :: MlKem768 ) . unwrap ( ) ;
686+ let KemKeypair :: MlKem768 {
687+ ref public,
688+ ref private,
689+ } = keypair
690+ else {
691+ panic ! ( "Expected MlKem768 keypair" ) ;
692+ } ;
693+
694+ // Export public key bytes via PK11_ReadRawAttribute(CKA_VALUE)
695+ let mut key_item = SECItemMut :: make_empty ( ) ;
696+ secstatus_to_res ( unsafe {
697+ p11:: PK11_ReadRawAttribute (
698+ p11:: PK11ObjectType :: PK11_TypePubKey ,
699+ ( * * public) . cast ( ) ,
700+ pkcs11_bindings:: CKA_VALUE ,
701+ key_item. as_mut ( ) ,
702+ )
703+ } )
704+ . unwrap ( ) ;
705+ let pk_bytes = key_item. as_slice ( ) . to_owned ( ) ;
706+ assert_eq ! ( pk_bytes. len( ) , MlKemParameterSet :: MlKem768 . public_key_bytes( ) ) ;
707+
708+ // Import from raw bytes
709+ let imported_pk =
710+ import_mlkem_public_key ( & pk_bytes, MlKemParameterSet :: MlKem768 ) . unwrap ( ) ;
711+
712+ // Encapsulate with imported public key
713+ let target = p11:: CKM_HKDF_DERIVE . into ( ) ;
714+ let ( ss_key, ciphertext) = mlkem_encapsulate ( & imported_pk, target) . unwrap ( ) ;
715+ let shared_secret = ss_key. key_data ( ) . unwrap ( ) . to_vec ( ) ;
716+
717+ // Decapsulate with original private key
718+ let dec_key = mlkem_decapsulate ( private, & ciphertext, target) . unwrap ( ) ;
719+ let decap_ss = dec_key. key_data ( ) . unwrap ( ) . to_vec ( ) ;
720+
721+ assert_eq ! ( shared_secret, decap_ss) ;
722+ }
723+
724+ #[ test]
725+ fn test_mlkem_import_invalid_size ( ) {
726+ fixture_init ( ) ;
727+
728+ let result = import_mlkem_public_key ( & [ 0u8 ; 100 ] , MlKemParameterSet :: MlKem768 ) ;
729+ assert ! ( result. is_err( ) ) ;
730+ }
731+
732+ // ========================================================================
733+ // Legacy API tests (for backwards compatibility)
734+ // ========================================================================
735+
619736 #[ test]
620737 fn test_mlkem_parameter_set_sizes ( ) {
621738 // ML-KEM-768 sizes
0 commit comments