2020 */
2121
2222using System ;
23+ using System . Collections . Concurrent ;
2324using System . Runtime . InteropServices ;
2425using System . Security . Cryptography ;
2526using System . Text ;
@@ -361,6 +362,10 @@ public class wolfcrypt
361362 private extern static int wc_curve25519_make_key ( IntPtr rng , int keysize , IntPtr key ) ;
362363 [ DllImport ( wolfssl_dll ) ]
363364 private extern static int wc_curve25519_shared_secret ( IntPtr privateKey , IntPtr publicKey , byte [ ] outSharedSecret , ref int outlen ) ;
365+ /* Only available when wolfSSL is built with WOLFSSL_CURVE25519_BLINDING.
366+ * Calls are wrapped in try/catch to tolerate builds without it. */
367+ [ DllImport ( wolfssl_dll ) ]
368+ private extern static int wc_curve25519_set_rng ( IntPtr key , IntPtr rng ) ;
364369
365370 /* ASN.1 DER format */
366371 [ DllImport ( wolfssl_dll ) ]
@@ -400,6 +405,10 @@ public class wolfcrypt
400405 private extern static int wc_curve25519_make_key ( IntPtr rng , int keysize , IntPtr key ) ;
401406 [ DllImport ( wolfssl_dll , CallingConvention = CallingConvention . Cdecl ) ]
402407 private extern static int wc_curve25519_shared_secret ( IntPtr privateKey , IntPtr publicKey , byte [ ] outSharedSecret , ref int outlen ) ;
408+ /* Only available when wolfSSL is built with WOLFSSL_CURVE25519_BLINDING.
409+ * Calls are wrapped in try/catch to tolerate builds without it. */
410+ [ DllImport ( wolfssl_dll , CallingConvention = CallingConvention . Cdecl ) ]
411+ private extern static int wc_curve25519_set_rng ( IntPtr key , IntPtr rng ) ;
403412
404413 /* ASN.1 DER format */
405414 [ DllImport ( wolfssl_dll , CallingConvention = CallingConvention . Cdecl ) ]
@@ -3257,13 +3266,31 @@ public enum HpkeAead : ushort {
32573266 }
32583267
32593268 /* HPKE Nt (GCM tag length) */
3260- private static readonly int HPKE_Nt = 16 ;
3269+ private const int HPKE_Nt = 16 ;
3270+
3271+ /* HPKE max encoded public-key length (matches HPKE_Npk_MAX in hpke.h) */
3272+ private const int HPKE_Npk_MAX = 133 ;
32613273
32623274 /* Hpke struct is ~80 bytes on 64-bit (see hpke.h). Allocate 512 bytes
32633275 * (6x headroom) to accommodate platform alignment and future growth.
32643276 * If the native struct ever exceeds this, wc_HpkeInit will write OOB —
32653277 * keep in sync with hpke.h if the struct grows significantly. */
3266- private static readonly int HPKE_STRUCT_SZ = 512 ;
3278+ private const int HPKE_STRUCT_SZ = 512 ;
3279+
3280+ /* Per-Hpke-context state owned by the C# wrapper.
3281+ * The RNG must outlive any keypair created with this context: when
3282+ * wolfSSL is built with WOLFSSL_CURVE25519_BLINDING, wc_curve25519_make_key
3283+ * stores the rng pointer inside the keypair (via wc_curve25519_set_rng)
3284+ * and re-uses it for blinding during shared-secret operations. If the
3285+ * wrapper freed the rng after key generation, that pointer would dangle
3286+ * and the next seal/open would fail with RNG_FAILURE_E (-199). */
3287+ private struct HpkeContextState
3288+ {
3289+ public IntPtr rng ;
3290+ public HpkeKem kem ;
3291+ }
3292+ private static readonly ConcurrentDictionary < IntPtr , HpkeContextState > hpkeContexts =
3293+ new ConcurrentDictionary < IntPtr , HpkeContextState > ( ) ;
32673294
32683295 /// <summary>
32693296 /// Get the enc (encapsulated key) length for a given KEM
@@ -3272,13 +3299,15 @@ public enum HpkeAead : ushort {
32723299 /// <returns>Length in bytes</returns>
32733300 private static ushort HpkeEncLen ( HpkeKem kem )
32743301 {
3302+ /* Values must match DHKEM_*_ENC_LEN macros in wolfssl/wolfcrypt/hpke.h.
3303+ * Not P/Invoked because wc_HpkeKemGetEncLen is currently WOLFSSL_LOCAL. */
32753304 switch ( kem )
32763305 {
3277- case HpkeKem . DHKEM_P256_HKDF_SHA256 : return 65 ;
3278- case HpkeKem . DHKEM_P384_HKDF_SHA384 : return 97 ;
3279- case HpkeKem . DHKEM_P521_HKDF_SHA512 : return 133 ;
3280- case HpkeKem . DHKEM_X25519_HKDF_SHA256 : return 32 ;
3281- case HpkeKem . DHKEM_X448_HKDF_SHA512 : return 56 ;
3306+ case HpkeKem . DHKEM_P256_HKDF_SHA256 : return 65 ; /* DHKEM_P256_ENC_LEN */
3307+ case HpkeKem . DHKEM_P384_HKDF_SHA384 : return 97 ; /* DHKEM_P384_ENC_LEN */
3308+ case HpkeKem . DHKEM_P521_HKDF_SHA512 : return 133 ; /* DHKEM_P521_ENC_LEN */
3309+ case HpkeKem . DHKEM_X25519_HKDF_SHA256 : return 32 ; /* DHKEM_X25519_ENC_LEN */
3310+ case HpkeKem . DHKEM_X448_HKDF_SHA512 : return 56 ; /* DHKEM_X448_ENC_LEN */
32823311 default : return 0 ;
32833312 }
32843313 }
@@ -3293,6 +3322,7 @@ private static ushort HpkeEncLen(HpkeKem kem)
32933322 public static IntPtr HpkeInit ( HpkeKem kem , HpkeKdf kdf , HpkeAead aead )
32943323 {
32953324 IntPtr hpke = IntPtr . Zero ;
3325+ IntPtr rng = IntPtr . Zero ;
32963326
32973327 try
32983328 {
@@ -3313,10 +3343,27 @@ public static IntPtr HpkeInit(HpkeKem kem, HpkeKdf kdf, HpkeAead aead)
33133343 Marshal . FreeHGlobal ( hpke ) ;
33143344 return IntPtr . Zero ;
33153345 }
3346+
3347+ /* Allocate a persistent RNG that lives as long as this context.
3348+ * Required so curve25519 keypairs (with blinding) retain a valid
3349+ * rng pointer for shared-secret operations. */
3350+ rng = RandomNew ( ) ;
3351+ if ( rng == IntPtr . Zero )
3352+ {
3353+ log ( ERROR_LOG , "HPKE init: RNG allocation failed" ) ;
3354+ Marshal . FreeHGlobal ( hpke ) ;
3355+ return IntPtr . Zero ;
3356+ }
3357+
3358+ hpkeContexts [ hpke ] = new HpkeContextState { rng = rng , kem = kem } ;
33163359 }
33173360 catch ( Exception e )
33183361 {
33193362 log ( ERROR_LOG , "HPKE init exception " + e . ToString ( ) ) ;
3363+ if ( rng != IntPtr . Zero )
3364+ {
3365+ RandomFree ( rng ) ;
3366+ }
33203367 if ( hpke != IntPtr . Zero )
33213368 {
33223369 Marshal . FreeHGlobal ( hpke ) ;
@@ -3335,7 +3382,6 @@ public static IntPtr HpkeInit(HpkeKem kem, HpkeKdf kdf, HpkeAead aead)
33353382 public static IntPtr HpkeGenerateKeyPair ( IntPtr hpke )
33363383 {
33373384 IntPtr keypair = IntPtr . Zero ;
3338- IntPtr rng = IntPtr . Zero ;
33393385
33403386 try
33413387 {
@@ -3345,29 +3391,43 @@ public static IntPtr HpkeGenerateKeyPair(IntPtr hpke)
33453391 return IntPtr . Zero ;
33463392 }
33473393
3348- rng = RandomNew ( ) ;
3349- if ( rng == IntPtr . Zero )
3394+ HpkeContextState state ;
3395+ if ( ! hpkeContexts . TryGetValue ( hpke , out state ) || state . rng == IntPtr . Zero )
33503396 {
3351- log ( ERROR_LOG , "HPKE generate keypair: RNG init failed " ) ;
3397+ log ( ERROR_LOG , "HPKE generate keypair: no RNG associated with context " ) ;
33523398 return IntPtr . Zero ;
33533399 }
33543400
3355- int ret = wc_HpkeGenerateKeyPair ( hpke , ref keypair , rng ) ;
3401+ int ret = wc_HpkeGenerateKeyPair ( hpke , ref keypair , state . rng ) ;
33563402 if ( ret != 0 )
33573403 {
33583404 log ( ERROR_LOG , "HPKE generate keypair failed " + ret + ": " + GetError ( ret ) ) ;
3359- keypair = IntPtr . Zero ;
3405+ return IntPtr . Zero ;
3406+ }
3407+
3408+ /* For X25519, explicitly bind the persistent rng to the keypair.
3409+ * wc_curve25519_make_key already does this internally when wolfSSL
3410+ * is built with WOLFSSL_CURVE25519_BLINDING, but the explicit call
3411+ * here documents the lifetime requirement and is defensive against
3412+ * future changes. The function only exists when blinding is built
3413+ * in, so swallow EntryPointNotFoundException for builds without it. */
3414+ if ( state . kem == HpkeKem . DHKEM_X25519_HKDF_SHA256 && keypair != IntPtr . Zero )
3415+ {
3416+ try
3417+ {
3418+ wc_curve25519_set_rng ( keypair , state . rng ) ;
3419+ }
3420+ catch ( EntryPointNotFoundException )
3421+ {
3422+ /* wolfSSL built without WOLFSSL_CURVE25519_BLINDING; nothing to do */
3423+ }
33603424 }
33613425 }
33623426 catch ( Exception e )
33633427 {
33643428 log ( ERROR_LOG , "HPKE generate keypair exception " + e . ToString ( ) ) ;
33653429 keypair = IntPtr . Zero ;
33663430 }
3367- finally
3368- {
3369- if ( rng != IntPtr . Zero ) RandomFree ( rng ) ;
3370- }
33713431
33723432 return keypair ;
33733433 }
@@ -3388,7 +3448,7 @@ public static byte[] HpkeSerializePublicKey(IntPtr hpke, IntPtr keypair)
33883448 return null ;
33893449 }
33903450
3391- ushort outSz = 133 ; /* HPKE_Npk_MAX */
3451+ ushort outSz = ( ushort ) HPKE_Npk_MAX ;
33923452 byte [ ] outBuf = new byte [ outSz ] ;
33933453
33943454 int ret = wc_HpkeSerializePublicKey ( hpke , keypair , outBuf , ref outSz ) ;
@@ -3466,6 +3526,11 @@ public static void HpkeFree(IntPtr hpke)
34663526 {
34673527 if ( hpke != IntPtr . Zero )
34683528 {
3529+ HpkeContextState state ;
3530+ if ( hpkeContexts . TryRemove ( hpke , out state ) && state . rng != IntPtr . Zero )
3531+ {
3532+ RandomFree ( state . rng ) ;
3533+ }
34693534 Marshal . FreeHGlobal ( hpke ) ;
34703535 }
34713536 }
0 commit comments