Merge pull request #10171 from dgarske/hpke_csharp

Add HPKE (RFC 9180) C# wrapper
This commit is contained in:
Sean Parkinson
2026-04-14 08:27:03 +10:00
committed by GitHub
5 changed files with 845 additions and 0 deletions
+25
View File
@@ -40,6 +40,8 @@ apt-get install mono-complete
### Build wolfSSL and install
#### System-wide install
```
./autogen.sh
cp wrapper/CSharp/user_settings.h .
@@ -49,6 +51,16 @@ make check
sudo make install
```
#### Local-only install (no sudo required)
```
./autogen.sh
cp wrapper/CSharp/user_settings.h .
./configure --enable-usersettings --prefix=$(pwd)/install
make
make install
```
### Build and run the wolfCrypt test wrapper
From the `wrapper/CSharp` directory (`cd wrapper/CSharp`):
@@ -57,9 +69,22 @@ Compile wolfCrypt test:
```
mcs wolfCrypt-Test/wolfCrypt-Test.cs wolfSSL_CSharp/wolfCrypt.cs wolfSSL_CSharp/wolfSSL.cs wolfSSL_CSharp/X509.cs -OUT:wolfcrypttest.exe
```
Run with system-wide install:
```
mono wolfcrypttest.exe
```
Run with local-only install. The compile step above produced
`wolfcrypttest.exe` inside `wrapper/CSharp/`; this run command is invoked
from the wolfSSL project root so the relative paths line up:
```
LD_LIBRARY_PATH=./install/lib mono wrapper/CSharp/wolfcrypttest.exe
```
### Build and run the wolfSSL client/server test
From the `wrapper/CSharp` directory (`cd wrapper/CSharp`):
+1
View File
@@ -70,6 +70,7 @@
#define WOLFSSL_SHA512
#define HAVE_HKDF
#define HAVE_HPKE
#undef NO_DH
#define HAVE_PUBLIC_FFDHE
@@ -880,6 +880,202 @@ public class wolfCrypt_Test_CSharp
}
} /* END hash_test */
private static void hpke_test(wolfcrypt.HpkeKem kem,
wolfcrypt.HpkeKdf kdf, wolfcrypt.HpkeAead aead)
{
IntPtr hpke = IntPtr.Zero;
IntPtr receiverKey = IntPtr.Zero;
IntPtr deserializedKey = IntPtr.Zero;
IntPtr ephemeralKey = IntPtr.Zero;
try
{
Console.WriteLine("\nStarting HPKE Base mode test...");
/* Initialize HPKE context */
Console.WriteLine("Initializing HPKE context...");
hpke = wolfcrypt.HpkeInit(kem, kdf, aead);
if (hpke == IntPtr.Zero)
{
throw new Exception("HpkeInit failed");
}
Console.WriteLine("HPKE context initialization passed.");
/* Generate receiver keypair */
Console.WriteLine("Generating receiver keypair...");
receiverKey = wolfcrypt.HpkeGenerateKeyPair(hpke);
if (receiverKey == IntPtr.Zero)
{
throw new Exception("HpkeGenerateKeyPair (receiver) failed");
}
Console.WriteLine("Receiver keypair generation passed.");
/* Serialize and deserialize public key (round-trip) */
Console.WriteLine("Testing public key serialize/deserialize round-trip...");
byte[] pubKeyBytes = wolfcrypt.HpkeSerializePublicKey(hpke, receiverKey);
if (pubKeyBytes == null)
{
throw new Exception("HpkeSerializePublicKey failed");
}
Console.WriteLine($"Serialized public key length: {pubKeyBytes.Length}");
deserializedKey = wolfcrypt.HpkeDeserializePublicKey(hpke, pubKeyBytes);
if (deserializedKey == IntPtr.Zero)
{
throw new Exception("HpkeDeserializePublicKey failed");
}
/* Verify round-trip by re-serializing */
byte[] pubKeyBytes2 = wolfcrypt.HpkeSerializePublicKey(hpke, deserializedKey);
if (pubKeyBytes2 == null || !wolfcrypt.ByteArrayVerify(pubKeyBytes, pubKeyBytes2))
{
throw new Exception("Public key round-trip verification failed");
}
Console.WriteLine("Public key round-trip test passed.");
/* Generate ephemeral keypair for sender */
Console.WriteLine("Generating ephemeral keypair...");
ephemeralKey = wolfcrypt.HpkeGenerateKeyPair(hpke);
if (ephemeralKey == IntPtr.Zero)
{
throw new Exception("HpkeGenerateKeyPair (ephemeral) failed");
}
Console.WriteLine("Ephemeral keypair generation passed.");
/* Define test data */
byte[] info = Encoding.UTF8.GetBytes("HPKE .NET Test");
byte[] aad = Encoding.UTF8.GetBytes("additional data");
byte[] plaintext = Encoding.UTF8.GetBytes("Hello HPKE from wolfCrypt .NET!");
/* Seal (encrypt) */
Console.WriteLine("Testing HpkeSealBase...");
byte[] encCiphertext = wolfcrypt.HpkeSealBase(hpke, ephemeralKey,
receiverKey, info, aad, plaintext);
if (encCiphertext == null)
{
throw new Exception("HpkeSealBase failed");
}
Console.WriteLine($"HpkeSealBase passed. Output length: {encCiphertext.Length}");
/* Open (decrypt) */
Console.WriteLine("Testing HpkeOpenBase...");
byte[] decrypted = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
encCiphertext, info, aad, plaintext.Length);
if (decrypted == null)
{
throw new Exception("HpkeOpenBase failed");
}
Console.WriteLine("HpkeOpenBase passed.");
/* Compare plaintext and decrypted */
if (!wolfcrypt.ByteArrayVerify(plaintext, decrypted))
{
throw new Exception("Decrypted text does not match original plaintext");
}
Console.WriteLine("HPKE Base mode test PASSED.");
/* Test convenience overload (no ephemeral key) */
Console.WriteLine("Testing HpkeSealBase convenience overload...");
byte[] encCiphertext2 = wolfcrypt.HpkeSealBase(hpke, receiverKey,
info, aad, plaintext, kem);
if (encCiphertext2 == null)
{
throw new Exception("HpkeSealBase (convenience) failed");
}
Console.WriteLine($"HpkeSealBase convenience passed. Output length: {encCiphertext2.Length}");
byte[] decrypted2 = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
encCiphertext2, info, aad, kem);
if (decrypted2 == null)
{
throw new Exception("HpkeOpenBase (convenience) failed");
}
if (!wolfcrypt.ByteArrayVerify(plaintext, decrypted2))
{
throw new Exception("Convenience seal/open: decrypted text does not match");
}
Console.WriteLine("HPKE convenience overload test PASSED.");
/* Empty plaintext round-trip - native API accepts ptSz == 0 */
Console.WriteLine("Testing HpkeSealBase/OpenBase with empty plaintext...");
byte[] emptyPt = new byte[0];
byte[] encEmpty = wolfcrypt.HpkeSealBase(hpke, receiverKey,
info, aad, emptyPt, kem);
if (encEmpty == null)
{
throw new Exception("HpkeSealBase with empty plaintext failed");
}
byte[] decEmpty = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
encEmpty, info, aad, kem);
if (decEmpty == null || decEmpty.Length != 0)
{
throw new Exception("HpkeOpenBase with empty plaintext: round-trip failed");
}
Console.WriteLine("Empty plaintext round-trip test PASSED.");
/* Negative test: tampered ciphertext should fail */
Console.WriteLine("Testing HpkeOpenBase with tampered ciphertext...");
byte[] tampered = (byte[])encCiphertext.Clone();
tampered[tampered.Length - 1] ^= 0xFF; /* flip last byte (in tag) */
byte[] badResult = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
tampered, info, aad, plaintext.Length);
if (badResult != null)
{
throw new Exception("HpkeOpenBase should fail with tampered ciphertext");
}
Console.WriteLine("Tampered ciphertext test PASSED (correctly rejected).");
/* Negative test: mismatched AAD should fail */
Console.WriteLine("Testing HpkeOpenBase with mismatched AAD...");
byte[] wrongAad = Encoding.UTF8.GetBytes("wrong aad");
badResult = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
encCiphertext, info, wrongAad, plaintext.Length);
if (badResult != null)
{
throw new Exception("HpkeOpenBase should fail with mismatched AAD");
}
Console.WriteLine("Mismatched AAD test PASSED (correctly rejected).");
/* Null info/aad round-trip - exercises the null-marshaling path through P/Invoke */
Console.WriteLine("Testing HpkeSealBase/OpenBase with null info and aad...");
byte[] encNull = wolfcrypt.HpkeSealBase(hpke, receiverKey,
null, null, plaintext, kem);
if (encNull == null)
{
throw new Exception("HpkeSealBase with null info/aad failed");
}
byte[] decNull = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
encNull, null, null, kem);
if (decNull == null || !wolfcrypt.ByteArrayVerify(plaintext, decNull))
{
throw new Exception("HpkeOpenBase with null info/aad: round-trip failed");
}
Console.WriteLine("Null info/aad round-trip test PASSED.");
/* Negative test: invalid ptLen should fail */
Console.WriteLine("Testing HpkeOpenBase with invalid ptLen...");
badResult = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
encCiphertext, info, aad, plaintext.Length + 1);
if (badResult != null)
{
throw new Exception("HpkeOpenBase should fail with incorrect ptLen");
}
Console.WriteLine("Invalid ptLen test PASSED (correctly rejected).");
}
finally
{
/* Cleanup */
if (ephemeralKey != IntPtr.Zero)
wolfcrypt.HpkeFreeKey(hpke, ephemeralKey, kem);
if (deserializedKey != IntPtr.Zero)
wolfcrypt.HpkeFreeKey(hpke, deserializedKey, kem);
if (receiverKey != IntPtr.Zero)
wolfcrypt.HpkeFreeKey(hpke, receiverKey, kem);
if (hpke != IntPtr.Zero)
wolfcrypt.HpkeFree(hpke);
}
} /* END hpke_test */
public static void standard_log(int lvl, StringBuilder msg)
{
Console.WriteLine(msg);
@@ -941,6 +1137,15 @@ public class wolfCrypt_Test_CSharp
hash_test((uint)wolfcrypt.hashType.WC_HASH_TYPE_SHA512); /* SHA-512 HASH test */
hash_test((uint)wolfcrypt.hashType.WC_HASH_TYPE_SHA3_256); /* SHA3_256 HASH test */
Console.WriteLine("\nStarting HPKE tests");
hpke_test(wolfcrypt.HpkeKem.DHKEM_P256_HKDF_SHA256,
wolfcrypt.HpkeKdf.HKDF_SHA256,
wolfcrypt.HpkeAead.AES_128_GCM);
hpke_test(wolfcrypt.HpkeKem.DHKEM_X25519_HKDF_SHA256,
wolfcrypt.HpkeKdf.HKDF_SHA256,
wolfcrypt.HpkeAead.AES_128_GCM);
wolfcrypt.Cleanup();
Console.WriteLine("\nAll tests completed successfully");
+612
View File
@@ -20,6 +20,10 @@
*/
using System;
using System.Collections.Generic;
#if !WindowsCE
using System.Collections.Concurrent;
#endif
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
@@ -361,6 +365,10 @@ namespace wolfSSL.CSharp
private extern static int wc_curve25519_make_key(IntPtr rng, int keysize, IntPtr key);
[DllImport(wolfssl_dll)]
private extern static int wc_curve25519_shared_secret(IntPtr privateKey, IntPtr publicKey, byte[] outSharedSecret, ref int outlen);
/* Only available when wolfSSL is built with WOLFSSL_CURVE25519_BLINDING.
* Calls are wrapped in try/catch to tolerate builds without it. */
[DllImport(wolfssl_dll)]
private extern static int wc_curve25519_set_rng(IntPtr key, IntPtr rng);
/* ASN.1 DER format */
[DllImport(wolfssl_dll)]
@@ -400,6 +408,10 @@ namespace wolfSSL.CSharp
private extern static int wc_curve25519_make_key(IntPtr rng, int keysize, IntPtr key);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_curve25519_shared_secret(IntPtr privateKey, IntPtr publicKey, byte[] outSharedSecret, ref int outlen);
/* Only available when wolfSSL is built with WOLFSSL_CURVE25519_BLINDING.
* Calls are wrapped in try/catch to tolerate builds without it. */
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_curve25519_set_rng(IntPtr key, IntPtr rng);
/* ASN.1 DER format */
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
@@ -469,6 +481,43 @@ namespace wolfSSL.CSharp
#endif
/********************************
* HPKE
* Requires: HAVE_HPKE, HAVE_ECC (or HAVE_CURVE25519), HAVE_AESGCM
*/
#if WindowsCE
[DllImport(wolfssl_dll)]
private extern static int wc_HpkeInit(IntPtr hpke, int kem, int kdf, int aead, IntPtr heap);
[DllImport(wolfssl_dll)]
private extern static int wc_HpkeGenerateKeyPair(IntPtr hpke, ref IntPtr keypair, IntPtr rng);
[DllImport(wolfssl_dll)]
private extern static int wc_HpkeSerializePublicKey(IntPtr hpke, IntPtr key, byte[] outBuf, ref ushort outSz);
[DllImport(wolfssl_dll)]
private extern static int wc_HpkeDeserializePublicKey(IntPtr hpke, ref IntPtr key, byte[] inBuf, ushort inSz);
[DllImport(wolfssl_dll)]
private extern static void wc_HpkeFreeKey(IntPtr hpke, ushort kem, IntPtr keypair, IntPtr heap);
[DllImport(wolfssl_dll)]
private extern static int wc_HpkeSealBase(IntPtr hpke, IntPtr ephemeralKey, IntPtr receiverKey, byte[] info, uint infoSz, byte[] aad, uint aadSz, byte[] plaintext, uint ptSz, byte[] ciphertext);
[DllImport(wolfssl_dll)]
private extern static int wc_HpkeOpenBase(IntPtr hpke, IntPtr receiverKey, byte[] pubKey, ushort pubKeySz, byte[] info, uint infoSz, byte[] aad, uint aadSz, byte[] ciphertext, uint ctSz, byte[] plaintext);
#else
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_HpkeInit(IntPtr hpke, int kem, int kdf, int aead, IntPtr heap);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_HpkeGenerateKeyPair(IntPtr hpke, ref IntPtr keypair, IntPtr rng);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_HpkeSerializePublicKey(IntPtr hpke, IntPtr key, byte[] outBuf, ref ushort outSz);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_HpkeDeserializePublicKey(IntPtr hpke, ref IntPtr key, byte[] inBuf, ushort inSz);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static void wc_HpkeFreeKey(IntPtr hpke, ushort kem, IntPtr keypair, IntPtr heap);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_HpkeSealBase(IntPtr hpke, IntPtr ephemeralKey, IntPtr receiverKey, byte[] info, uint infoSz, byte[] aad, uint aadSz, byte[] plaintext, uint ptSz, byte[] ciphertext);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static int wc_HpkeOpenBase(IntPtr hpke, IntPtr receiverKey, byte[] pubKey, ushort pubKeySz, byte[] info, uint infoSz, byte[] aad, uint aadSz, byte[] ciphertext, uint ctSz, byte[] plaintext);
#endif
/********************************
* HASH
*/
@@ -3192,6 +3241,569 @@ namespace wolfSSL.CSharp
/* END HASH */
/***********************************************************************
* HPKE (RFC 9180) - Base mode SingleShot
* Requires: HAVE_HPKE, HAVE_ECC (or HAVE_CURVE25519), HAVE_AESGCM
**********************************************************************/
/* BEGIN HPKE */
/* HPKE KEM IDs */
public enum HpkeKem : ushort {
DHKEM_P256_HKDF_SHA256 = 0x0010,
DHKEM_P384_HKDF_SHA384 = 0x0011,
DHKEM_P521_HKDF_SHA512 = 0x0012,
DHKEM_X25519_HKDF_SHA256 = 0x0020,
DHKEM_X448_HKDF_SHA512 = 0x0021,
}
/* HPKE KDF IDs */
public enum HpkeKdf : ushort {
HKDF_SHA256 = 0x0001,
HKDF_SHA384 = 0x0002,
HKDF_SHA512 = 0x0003,
}
/* HPKE AEAD IDs */
public enum HpkeAead : ushort {
AES_128_GCM = 0x0001,
AES_256_GCM = 0x0002,
}
/* HPKE Nt (GCM tag length) */
private const int HPKE_Nt = 16;
/* HPKE max encoded public-key length (matches HPKE_Npk_MAX in hpke.h) */
private const int HPKE_Npk_MAX = 133;
/* Hpke struct is ~80 bytes on 64-bit (see hpke.h). Allocate 512 bytes
* (6x headroom) to accommodate platform alignment and future growth.
* If the native struct ever exceeds this, wc_HpkeInit will write OOB —
* keep in sync with hpke.h if the struct grows significantly. */
private const int HPKE_STRUCT_SZ = 512;
/* Per-Hpke-context state owned by the C# wrapper.
* The RNG must outlive any keypair created with this context: when
* wolfSSL is built with WOLFSSL_CURVE25519_BLINDING, wc_curve25519_make_key
* stores the rng pointer inside the keypair (via wc_curve25519_set_rng)
* and reuses it for blinding during shared-secret operations. If the
* wrapper freed the rng after key generation, that pointer would dangle
* and the next seal/open would fail with RNG_FAILURE_E (-199). */
private struct HpkeContextState
{
public IntPtr rng;
public HpkeKem kem;
}
#if WindowsCE
/* .NET Compact Framework / Windows CE does not provide
* System.Collections.Concurrent, so fall back to a plain Dictionary
* guarded by an explicit lock. */
private static readonly Dictionary<IntPtr, HpkeContextState> hpkeContexts =
new Dictionary<IntPtr, HpkeContextState>();
private static readonly object hpkeContextsLock = new object();
private static void HpkeContextStore(IntPtr hpke, HpkeContextState state)
{
lock (hpkeContextsLock) { hpkeContexts[hpke] = state; }
}
private static bool HpkeContextTryGet(IntPtr hpke, out HpkeContextState state)
{
lock (hpkeContextsLock) { return hpkeContexts.TryGetValue(hpke, out state); }
}
private static bool HpkeContextTryRemove(IntPtr hpke, out HpkeContextState state)
{
lock (hpkeContextsLock)
{
if (hpkeContexts.TryGetValue(hpke, out state))
{
hpkeContexts.Remove(hpke);
return true;
}
return false;
}
}
#else
private static readonly ConcurrentDictionary<IntPtr, HpkeContextState> hpkeContexts =
new ConcurrentDictionary<IntPtr, HpkeContextState>();
private static void HpkeContextStore(IntPtr hpke, HpkeContextState state)
{
hpkeContexts[hpke] = state;
}
private static bool HpkeContextTryGet(IntPtr hpke, out HpkeContextState state)
{
return hpkeContexts.TryGetValue(hpke, out state);
}
private static bool HpkeContextTryRemove(IntPtr hpke, out HpkeContextState state)
{
return hpkeContexts.TryRemove(hpke, out state);
}
#endif
/// <summary>
/// Get the enc (encapsulated key) length for a given KEM
/// </summary>
/// <param name="kem">KEM identifier</param>
/// <returns>Length in bytes</returns>
private static ushort HpkeEncLen(HpkeKem kem)
{
/* Values must match DHKEM_*_ENC_LEN macros in wolfssl/wolfcrypt/hpke.h.
* Not P/Invoked because wc_HpkeKemGetEncLen is currently WOLFSSL_LOCAL. */
switch (kem)
{
case HpkeKem.DHKEM_P256_HKDF_SHA256: return 65; /* DHKEM_P256_ENC_LEN */
case HpkeKem.DHKEM_P384_HKDF_SHA384: return 97; /* DHKEM_P384_ENC_LEN */
case HpkeKem.DHKEM_P521_HKDF_SHA512: return 133; /* DHKEM_P521_ENC_LEN */
case HpkeKem.DHKEM_X25519_HKDF_SHA256: return 32; /* DHKEM_X25519_ENC_LEN */
case HpkeKem.DHKEM_X448_HKDF_SHA512: return 56; /* DHKEM_X448_ENC_LEN */
default: return 0;
}
}
/// <summary>
/// Allocate and initialize an HPKE context
/// </summary>
/// <param name="kem">KEM algorithm identifier</param>
/// <param name="kdf">KDF algorithm identifier</param>
/// <param name="aead">AEAD algorithm identifier</param>
/// <returns>Pointer to allocated Hpke context or IntPtr.Zero on failure</returns>
public static IntPtr HpkeInit(HpkeKem kem, HpkeKdf kdf, HpkeAead aead)
{
IntPtr hpke = IntPtr.Zero;
IntPtr rng = IntPtr.Zero;
try
{
hpke = Marshal.AllocHGlobal(HPKE_STRUCT_SZ);
if (hpke == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE alloc failed");
return IntPtr.Zero;
}
/* Zero the memory */
Marshal.Copy(new byte[HPKE_STRUCT_SZ], 0, hpke, HPKE_STRUCT_SZ);
int ret = wc_HpkeInit(hpke, (int)kem, (int)kdf, (int)aead, IntPtr.Zero);
if (ret != 0)
{
log(ERROR_LOG, "HPKE init failed " + ret + ": " + GetError(ret));
Marshal.FreeHGlobal(hpke);
return IntPtr.Zero;
}
/* Allocate a persistent RNG that lives as long as this context.
* Required so curve25519 keypairs (with blinding) retain a valid
* rng pointer for shared-secret operations. */
rng = RandomNew();
if (rng == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE init: RNG allocation failed");
Marshal.FreeHGlobal(hpke);
return IntPtr.Zero;
}
HpkeContextStore(hpke, new HpkeContextState { rng = rng, kem = kem });
}
catch (Exception e)
{
log(ERROR_LOG, "HPKE init exception " + e.ToString());
if (rng != IntPtr.Zero)
{
RandomFree(rng);
}
if (hpke != IntPtr.Zero)
{
Marshal.FreeHGlobal(hpke);
}
return IntPtr.Zero;
}
return hpke;
}
/// <summary>
/// Generate a new HPKE keypair
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <returns>Pointer to keypair or IntPtr.Zero on failure</returns>
public static IntPtr HpkeGenerateKeyPair(IntPtr hpke)
{
IntPtr keypair = IntPtr.Zero;
try
{
if (hpke == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE generate keypair: invalid context");
return IntPtr.Zero;
}
HpkeContextState state;
if (!HpkeContextTryGet(hpke, out state) || state.rng == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE generate keypair: no RNG associated with context");
return IntPtr.Zero;
}
int ret = wc_HpkeGenerateKeyPair(hpke, ref keypair, state.rng);
if (ret != 0)
{
log(ERROR_LOG, "HPKE generate keypair failed " + ret + ": " + GetError(ret));
return IntPtr.Zero;
}
/* For X25519, explicitly bind the persistent rng to the keypair.
* wc_curve25519_make_key already does this internally when wolfSSL
* is built with WOLFSSL_CURVE25519_BLINDING, but the explicit call
* here documents the lifetime requirement and is defensive against
* future changes. The function only exists when blinding is built
* in, so swallow EntryPointNotFoundException for builds without it. */
if (state.kem == HpkeKem.DHKEM_X25519_HKDF_SHA256 && keypair != IntPtr.Zero)
{
try
{
wc_curve25519_set_rng(keypair, state.rng);
}
catch (EntryPointNotFoundException)
{
/* wolfSSL built without WOLFSSL_CURVE25519_BLINDING; nothing to do */
}
}
}
catch (Exception e)
{
log(ERROR_LOG, "HPKE generate keypair exception " + e.ToString());
keypair = IntPtr.Zero;
}
return keypair;
}
/// <summary>
/// Serialize the public key to bytes
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <param name="keypair">Keypair from HpkeGenerateKeyPair()</param>
/// <returns>Serialized public key bytes or null on failure</returns>
public static byte[] HpkeSerializePublicKey(IntPtr hpke, IntPtr keypair)
{
try
{
if (hpke == IntPtr.Zero || keypair == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE serialize public key: invalid parameter");
return null;
}
ushort outSz = (ushort)HPKE_Npk_MAX;
byte[] outBuf = new byte[outSz];
int ret = wc_HpkeSerializePublicKey(hpke, keypair, outBuf, ref outSz);
if (ret != 0)
{
log(ERROR_LOG, "HPKE serialize public key failed " + ret + ": " + GetError(ret));
return null;
}
/* Trim to actual size */
byte[] result = new byte[outSz];
Array.Copy(outBuf, 0, result, 0, outSz);
return result;
}
catch (Exception e)
{
log(ERROR_LOG, "HPKE serialize public key exception " + e.ToString());
return null;
}
}
/// <summary>
/// Deserialize a public key from bytes
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <param name="pubKeyBytes">Serialized public key bytes</param>
/// <returns>Pointer to keypair or IntPtr.Zero on failure</returns>
public static IntPtr HpkeDeserializePublicKey(IntPtr hpke, byte[] pubKeyBytes)
{
IntPtr key = IntPtr.Zero;
try
{
if (hpke == IntPtr.Zero || pubKeyBytes == null || pubKeyBytes.Length == 0)
{
log(ERROR_LOG, "HPKE deserialize public key: invalid parameter");
return IntPtr.Zero;
}
int ret = wc_HpkeDeserializePublicKey(hpke, ref key, pubKeyBytes, (ushort)pubKeyBytes.Length);
if (ret != 0)
{
log(ERROR_LOG, "HPKE deserialize public key failed " + ret + ": " + GetError(ret));
return IntPtr.Zero;
}
}
catch (Exception e)
{
log(ERROR_LOG, "HPKE deserialize public key exception " + e.ToString());
return IntPtr.Zero;
}
return key;
}
/// <summary>
/// Free a keypair created by HpkeGenerateKeyPair or HpkeDeserializePublicKey
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <param name="keypair">Keypair to free</param>
/// <param name="kem">KEM used when the keypair was created</param>
public static void HpkeFreeKey(IntPtr hpke, IntPtr keypair, HpkeKem kem)
{
if (hpke != IntPtr.Zero && keypair != IntPtr.Zero)
{
wc_HpkeFreeKey(hpke, (ushort)kem, keypair, IntPtr.Zero);
}
}
/// <summary>
/// Free an HPKE context allocated by HpkeInit
/// </summary>
/// <param name="hpke">HPKE context to free</param>
public static void HpkeFree(IntPtr hpke)
{
if (hpke != IntPtr.Zero)
{
HpkeContextState state;
if (HpkeContextTryRemove(hpke, out state) && state.rng != IntPtr.Zero)
{
RandomFree(state.rng);
}
Marshal.FreeHGlobal(hpke);
}
}
/// <summary>
/// SingleShot seal (encrypt) using HPKE Base mode.
/// Returns enc||ciphertext as a single byte array.
/// The enc length is determined by the KEM (e.g. 65 bytes for P-256).
/// Ciphertext length = plaintext length + Nt (16-byte GCM tag).
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <param name="ephemeralKey">Ephemeral keypair for sender</param>
/// <param name="receiverKey">Receiver public key</param>
/// <param name="info">Info context bytes (can be null)</param>
/// <param name="aad">Additional authenticated data (can be null)</param>
/// <param name="plaintext">Plaintext to encrypt</param>
/// <returns>enc||ciphertext byte array or null on failure</returns>
public static byte[] HpkeSealBase(IntPtr hpke, IntPtr ephemeralKey, IntPtr receiverKey,
byte[] info, byte[] aad, byte[] plaintext)
{
try
{
if (hpke == IntPtr.Zero || ephemeralKey == IntPtr.Zero || receiverKey == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE seal base: invalid parameter");
return null;
}
/* Native wc_HpkeSealBase only requires plaintext to be non-NULL;
* ptSz == 0 is valid (output is just the AEAD tag). */
if (plaintext == null)
{
log(ERROR_LOG, "HPKE seal base: invalid plaintext");
return null;
}
/* Serialize the ephemeral public key (enc) */
byte[] enc = HpkeSerializePublicKey(hpke, ephemeralKey);
if (enc == null)
{
log(ERROR_LOG, "HPKE seal base: failed to serialize ephemeral key");
return null;
}
uint infoSz = (info != null) ? (uint)info.Length : 0;
uint aadSz = (aad != null) ? (uint)aad.Length : 0;
uint ptSz = (uint)plaintext.Length;
/* wc_HpkeSealBase outputs ptSz + Nt (GCM tag) bytes */
int sealLen = (int)ptSz + HPKE_Nt;
byte[] sealOut = new byte[sealLen];
int ret = wc_HpkeSealBase(hpke, ephemeralKey, receiverKey,
info, infoSz, aad, aadSz, plaintext, ptSz, sealOut);
if (ret != 0)
{
log(ERROR_LOG, "HPKE seal base failed " + ret + ": " + GetError(ret));
return null;
}
/* Return enc || sealOut */
byte[] result = new byte[enc.Length + sealLen];
Array.Copy(enc, 0, result, 0, enc.Length);
Array.Copy(sealOut, 0, result, enc.Length, sealLen);
return result;
}
catch (Exception e)
{
log(ERROR_LOG, "HPKE seal base exception " + e.ToString());
return null;
}
}
/// <summary>
/// Convenience SingleShot seal (encrypt) using HPKE Base mode.
/// Generates an ephemeral keypair internally so the caller does not
/// need to manage one.
/// Returns enc||ciphertext as a single byte array.
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <param name="receiverKey">Receiver public key</param>
/// <param name="info">Info context bytes (can be null)</param>
/// <param name="aad">Additional authenticated data (can be null)</param>
/// <param name="plaintext">Plaintext to encrypt</param>
/// <param name="kem">KEM used (needed to free the ephemeral key)</param>
/// <returns>enc||ciphertext byte array or null on failure</returns>
public static byte[] HpkeSealBase(IntPtr hpke, IntPtr receiverKey,
byte[] info, byte[] aad, byte[] plaintext, HpkeKem kem)
{
IntPtr ephemeralKey = IntPtr.Zero;
try
{
ephemeralKey = HpkeGenerateKeyPair(hpke);
if (ephemeralKey == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE seal base: ephemeral keygen failed");
return null;
}
return HpkeSealBase(hpke, ephemeralKey, receiverKey,
info, aad, plaintext);
}
catch (Exception e)
{
log(ERROR_LOG, "HPKE seal base exception " + e.ToString());
return null;
}
finally
{
if (ephemeralKey != IntPtr.Zero)
HpkeFreeKey(hpke, ephemeralKey, kem);
}
}
/// <summary>
/// SingleShot open (decrypt) using HPKE Base mode.
/// Takes the full enc||ciphertext blob returned by HpkeSealBase.
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <param name="receiverKey">Receiver private keypair</param>
/// <param name="encCiphertext">enc||ciphertext blob from HpkeSealBase()</param>
/// <param name="info">Info context bytes (can be null)</param>
/// <param name="aad">Additional authenticated data (can be null)</param>
/// <param name="ptLen">Expected plaintext length</param>
/// <returns>Decrypted plaintext byte array or null on failure</returns>
public static byte[] HpkeOpenBase(IntPtr hpke, IntPtr receiverKey,
byte[] encCiphertext, byte[] info, byte[] aad, int ptLen)
{
try
{
if (hpke == IntPtr.Zero || receiverKey == IntPtr.Zero)
{
log(ERROR_LOG, "HPKE open base: invalid parameter");
return null;
}
if (encCiphertext == null || encCiphertext.Length == 0)
{
log(ERROR_LOG, "HPKE open base: invalid ciphertext");
return null;
}
/* encCiphertext = enc || ciphertext || GCM tag
* where ciphertext is ptLen bytes, tag is Nt bytes */
if (ptLen < 0 || ptLen > int.MaxValue - HPKE_Nt)
{
log(ERROR_LOG, "HPKE open base: invalid ptLen");
return null;
}
int sealLen = ptLen + HPKE_Nt;
if (encCiphertext.Length < sealLen)
{
log(ERROR_LOG, "HPKE open base: encCiphertext too short for given ptLen");
return null;
}
int pubKeySzInt = encCiphertext.Length - sealLen;
if (pubKeySzInt < 0 || pubKeySzInt > ushort.MaxValue)
{
log(ERROR_LOG, "HPKE open base: invalid encapsulated public key size");
return null;
}
ushort pubKeySz = (ushort)pubKeySzInt;
/* Split enc and sealed data (ciphertext || tag) */
byte[] pubKey = new byte[pubKeySz];
byte[] ct = new byte[sealLen];
Array.Copy(encCiphertext, 0, pubKey, 0, pubKeySz);
Array.Copy(encCiphertext, pubKeySz, ct, 0, sealLen);
uint infoSz = (info != null) ? (uint)info.Length : 0;
uint aadSz = (aad != null) ? (uint)aad.Length : 0;
byte[] plaintext = new byte[ptLen];
/* ctSz is just the ciphertext length (without tag);
* wc_HpkeOpenBase reads the tag from ct + ctSz */
int ret = wc_HpkeOpenBase(hpke, receiverKey, pubKey, pubKeySz,
info, infoSz, aad, aadSz, ct, (uint)ptLen, plaintext);
if (ret != 0)
{
log(ERROR_LOG, "HPKE open base failed " + ret + ": " + GetError(ret));
return null;
}
return plaintext;
}
catch (Exception e)
{
log(ERROR_LOG, "HPKE open base exception " + e.ToString());
return null;
}
}
/// <summary>
/// Convenience SingleShot open (decrypt) using HPKE Base mode.
/// Derives the plaintext length from the KEM enc length, so the caller
/// does not need to know ptLen.
/// </summary>
/// <param name="hpke">HPKE context from HpkeInit()</param>
/// <param name="receiverKey">Receiver private keypair</param>
/// <param name="encCiphertext">enc||ciphertext blob from HpkeSealBase()</param>
/// <param name="info">Info context bytes (can be null)</param>
/// <param name="aad">Additional authenticated data (can be null)</param>
/// <param name="kem">KEM used (to derive enc length)</param>
/// <returns>Decrypted plaintext byte array or null on failure</returns>
public static byte[] HpkeOpenBase(IntPtr hpke, IntPtr receiverKey,
byte[] encCiphertext, byte[] info, byte[] aad, HpkeKem kem)
{
ushort encLen = HpkeEncLen(kem);
if (encLen == 0)
{
log(ERROR_LOG, "HPKE open base: unsupported KEM");
return null;
}
if (encCiphertext == null || encCiphertext.Length < encLen + HPKE_Nt)
{
log(ERROR_LOG, "HPKE open base: encCiphertext too short");
return null;
}
int ptLen = encCiphertext.Length - encLen - HPKE_Nt;
return HpkeOpenBase(hpke, receiverKey, encCiphertext, info, aad, ptLen);
}
/* END HPKE */
/***********************************************************************
* Logging / Other
**********************************************************************/
+2
View File
@@ -317,6 +317,7 @@
<ClCompile Include="..\..\wolfcrypt\src\ge_low_mem.c" />
<ClCompile Include="..\..\wolfcrypt\src\ge_operations.c" />
<ClCompile Include="..\..\wolfcrypt\src\hash.c" />
<ClCompile Include="..\..\wolfcrypt\src\hpke.c" />
<ClCompile Include="..\..\wolfcrypt\src\hmac.c" />
<ClCompile Include="..\..\wolfcrypt\src\integer.c" />
<ClCompile Include="..\..\wolfcrypt\src\kdf.c" />
@@ -332,6 +333,7 @@
<ClCompile Include="..\..\wolfcrypt\src\poly1305.c" />
<ClCompile Include="..\..\wolfcrypt\src\pwdbased.c" />
<ClCompile Include="..\..\wolfcrypt\src\random.c" />
<ClCompile Include="..\..\wolfcrypt\src\rng_bank.c" />
<ClCompile Include="..\..\wolfcrypt\src\rc2.c" />
<ClCompile Include="..\..\wolfcrypt\src\ripemd.c" />
<ClCompile Include="..\..\wolfcrypt\src\rsa.c" />