Skip to content

Commit 9176185

Browse files
authored
Merge pull request #10171 from dgarske/hpke_csharp
Add HPKE (RFC 9180) C# wrapper
2 parents 649a32f + a82583d commit 9176185

5 files changed

Lines changed: 845 additions & 0 deletions

File tree

wrapper/CSharp/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ apt-get install mono-complete
4040

4141
### Build wolfSSL and install
4242

43+
#### System-wide install
44+
4345
```
4446
./autogen.sh
4547
cp wrapper/CSharp/user_settings.h .
@@ -49,6 +51,16 @@ make check
4951
sudo make install
5052
```
5153

54+
#### Local-only install (no sudo required)
55+
56+
```
57+
./autogen.sh
58+
cp wrapper/CSharp/user_settings.h .
59+
./configure --enable-usersettings --prefix=$(pwd)/install
60+
make
61+
make install
62+
```
63+
5264
### Build and run the wolfCrypt test wrapper
5365

5466
From the `wrapper/CSharp` directory (`cd wrapper/CSharp`):
@@ -57,9 +69,22 @@ Compile wolfCrypt test:
5769

5870
```
5971
mcs wolfCrypt-Test/wolfCrypt-Test.cs wolfSSL_CSharp/wolfCrypt.cs wolfSSL_CSharp/wolfSSL.cs wolfSSL_CSharp/X509.cs -OUT:wolfcrypttest.exe
72+
```
73+
74+
Run with system-wide install:
75+
76+
```
6077
mono wolfcrypttest.exe
6178
```
6279

80+
Run with local-only install. The compile step above produced
81+
`wolfcrypttest.exe` inside `wrapper/CSharp/`; this run command is invoked
82+
from the wolfSSL project root so the relative paths line up:
83+
84+
```
85+
LD_LIBRARY_PATH=./install/lib mono wrapper/CSharp/wolfcrypttest.exe
86+
```
87+
6388
### Build and run the wolfSSL client/server test
6489

6590
From the `wrapper/CSharp` directory (`cd wrapper/CSharp`):

wrapper/CSharp/user_settings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#define WOLFSSL_SHA512
7171

7272
#define HAVE_HKDF
73+
#define HAVE_HPKE
7374

7475
#undef NO_DH
7576
#define HAVE_PUBLIC_FFDHE

wrapper/CSharp/wolfCrypt-Test/wolfCrypt-Test.cs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,202 @@ private static void hash_test(uint hashType)
880880
}
881881
} /* END hash_test */
882882

883+
private static void hpke_test(wolfcrypt.HpkeKem kem,
884+
wolfcrypt.HpkeKdf kdf, wolfcrypt.HpkeAead aead)
885+
{
886+
IntPtr hpke = IntPtr.Zero;
887+
IntPtr receiverKey = IntPtr.Zero;
888+
IntPtr deserializedKey = IntPtr.Zero;
889+
IntPtr ephemeralKey = IntPtr.Zero;
890+
891+
try
892+
{
893+
Console.WriteLine("\nStarting HPKE Base mode test...");
894+
895+
/* Initialize HPKE context */
896+
Console.WriteLine("Initializing HPKE context...");
897+
hpke = wolfcrypt.HpkeInit(kem, kdf, aead);
898+
if (hpke == IntPtr.Zero)
899+
{
900+
throw new Exception("HpkeInit failed");
901+
}
902+
Console.WriteLine("HPKE context initialization passed.");
903+
904+
/* Generate receiver keypair */
905+
Console.WriteLine("Generating receiver keypair...");
906+
receiverKey = wolfcrypt.HpkeGenerateKeyPair(hpke);
907+
if (receiverKey == IntPtr.Zero)
908+
{
909+
throw new Exception("HpkeGenerateKeyPair (receiver) failed");
910+
}
911+
Console.WriteLine("Receiver keypair generation passed.");
912+
913+
/* Serialize and deserialize public key (round-trip) */
914+
Console.WriteLine("Testing public key serialize/deserialize round-trip...");
915+
byte[] pubKeyBytes = wolfcrypt.HpkeSerializePublicKey(hpke, receiverKey);
916+
if (pubKeyBytes == null)
917+
{
918+
throw new Exception("HpkeSerializePublicKey failed");
919+
}
920+
Console.WriteLine($"Serialized public key length: {pubKeyBytes.Length}");
921+
922+
deserializedKey = wolfcrypt.HpkeDeserializePublicKey(hpke, pubKeyBytes);
923+
if (deserializedKey == IntPtr.Zero)
924+
{
925+
throw new Exception("HpkeDeserializePublicKey failed");
926+
}
927+
928+
/* Verify round-trip by re-serializing */
929+
byte[] pubKeyBytes2 = wolfcrypt.HpkeSerializePublicKey(hpke, deserializedKey);
930+
if (pubKeyBytes2 == null || !wolfcrypt.ByteArrayVerify(pubKeyBytes, pubKeyBytes2))
931+
{
932+
throw new Exception("Public key round-trip verification failed");
933+
}
934+
Console.WriteLine("Public key round-trip test passed.");
935+
936+
/* Generate ephemeral keypair for sender */
937+
Console.WriteLine("Generating ephemeral keypair...");
938+
ephemeralKey = wolfcrypt.HpkeGenerateKeyPair(hpke);
939+
if (ephemeralKey == IntPtr.Zero)
940+
{
941+
throw new Exception("HpkeGenerateKeyPair (ephemeral) failed");
942+
}
943+
Console.WriteLine("Ephemeral keypair generation passed.");
944+
945+
/* Define test data */
946+
byte[] info = Encoding.UTF8.GetBytes("HPKE .NET Test");
947+
byte[] aad = Encoding.UTF8.GetBytes("additional data");
948+
byte[] plaintext = Encoding.UTF8.GetBytes("Hello HPKE from wolfCrypt .NET!");
949+
950+
/* Seal (encrypt) */
951+
Console.WriteLine("Testing HpkeSealBase...");
952+
byte[] encCiphertext = wolfcrypt.HpkeSealBase(hpke, ephemeralKey,
953+
receiverKey, info, aad, plaintext);
954+
if (encCiphertext == null)
955+
{
956+
throw new Exception("HpkeSealBase failed");
957+
}
958+
Console.WriteLine($"HpkeSealBase passed. Output length: {encCiphertext.Length}");
959+
960+
/* Open (decrypt) */
961+
Console.WriteLine("Testing HpkeOpenBase...");
962+
byte[] decrypted = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
963+
encCiphertext, info, aad, plaintext.Length);
964+
if (decrypted == null)
965+
{
966+
throw new Exception("HpkeOpenBase failed");
967+
}
968+
Console.WriteLine("HpkeOpenBase passed.");
969+
970+
/* Compare plaintext and decrypted */
971+
if (!wolfcrypt.ByteArrayVerify(plaintext, decrypted))
972+
{
973+
throw new Exception("Decrypted text does not match original plaintext");
974+
}
975+
Console.WriteLine("HPKE Base mode test PASSED.");
976+
977+
/* Test convenience overload (no ephemeral key) */
978+
Console.WriteLine("Testing HpkeSealBase convenience overload...");
979+
byte[] encCiphertext2 = wolfcrypt.HpkeSealBase(hpke, receiverKey,
980+
info, aad, plaintext, kem);
981+
if (encCiphertext2 == null)
982+
{
983+
throw new Exception("HpkeSealBase (convenience) failed");
984+
}
985+
Console.WriteLine($"HpkeSealBase convenience passed. Output length: {encCiphertext2.Length}");
986+
987+
byte[] decrypted2 = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
988+
encCiphertext2, info, aad, kem);
989+
if (decrypted2 == null)
990+
{
991+
throw new Exception("HpkeOpenBase (convenience) failed");
992+
}
993+
if (!wolfcrypt.ByteArrayVerify(plaintext, decrypted2))
994+
{
995+
throw new Exception("Convenience seal/open: decrypted text does not match");
996+
}
997+
Console.WriteLine("HPKE convenience overload test PASSED.");
998+
999+
/* Empty plaintext round-trip - native API accepts ptSz == 0 */
1000+
Console.WriteLine("Testing HpkeSealBase/OpenBase with empty plaintext...");
1001+
byte[] emptyPt = new byte[0];
1002+
byte[] encEmpty = wolfcrypt.HpkeSealBase(hpke, receiverKey,
1003+
info, aad, emptyPt, kem);
1004+
if (encEmpty == null)
1005+
{
1006+
throw new Exception("HpkeSealBase with empty plaintext failed");
1007+
}
1008+
byte[] decEmpty = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
1009+
encEmpty, info, aad, kem);
1010+
if (decEmpty == null || decEmpty.Length != 0)
1011+
{
1012+
throw new Exception("HpkeOpenBase with empty plaintext: round-trip failed");
1013+
}
1014+
Console.WriteLine("Empty plaintext round-trip test PASSED.");
1015+
1016+
/* Negative test: tampered ciphertext should fail */
1017+
Console.WriteLine("Testing HpkeOpenBase with tampered ciphertext...");
1018+
byte[] tampered = (byte[])encCiphertext.Clone();
1019+
tampered[tampered.Length - 1] ^= 0xFF; /* flip last byte (in tag) */
1020+
byte[] badResult = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
1021+
tampered, info, aad, plaintext.Length);
1022+
if (badResult != null)
1023+
{
1024+
throw new Exception("HpkeOpenBase should fail with tampered ciphertext");
1025+
}
1026+
Console.WriteLine("Tampered ciphertext test PASSED (correctly rejected).");
1027+
1028+
/* Negative test: mismatched AAD should fail */
1029+
Console.WriteLine("Testing HpkeOpenBase with mismatched AAD...");
1030+
byte[] wrongAad = Encoding.UTF8.GetBytes("wrong aad");
1031+
badResult = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
1032+
encCiphertext, info, wrongAad, plaintext.Length);
1033+
if (badResult != null)
1034+
{
1035+
throw new Exception("HpkeOpenBase should fail with mismatched AAD");
1036+
}
1037+
Console.WriteLine("Mismatched AAD test PASSED (correctly rejected).");
1038+
1039+
/* Null info/aad round-trip - exercises the null-marshaling path through P/Invoke */
1040+
Console.WriteLine("Testing HpkeSealBase/OpenBase with null info and aad...");
1041+
byte[] encNull = wolfcrypt.HpkeSealBase(hpke, receiverKey,
1042+
null, null, plaintext, kem);
1043+
if (encNull == null)
1044+
{
1045+
throw new Exception("HpkeSealBase with null info/aad failed");
1046+
}
1047+
byte[] decNull = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
1048+
encNull, null, null, kem);
1049+
if (decNull == null || !wolfcrypt.ByteArrayVerify(plaintext, decNull))
1050+
{
1051+
throw new Exception("HpkeOpenBase with null info/aad: round-trip failed");
1052+
}
1053+
Console.WriteLine("Null info/aad round-trip test PASSED.");
1054+
1055+
/* Negative test: invalid ptLen should fail */
1056+
Console.WriteLine("Testing HpkeOpenBase with invalid ptLen...");
1057+
badResult = wolfcrypt.HpkeOpenBase(hpke, receiverKey,
1058+
encCiphertext, info, aad, plaintext.Length + 1);
1059+
if (badResult != null)
1060+
{
1061+
throw new Exception("HpkeOpenBase should fail with incorrect ptLen");
1062+
}
1063+
Console.WriteLine("Invalid ptLen test PASSED (correctly rejected).");
1064+
}
1065+
finally
1066+
{
1067+
/* Cleanup */
1068+
if (ephemeralKey != IntPtr.Zero)
1069+
wolfcrypt.HpkeFreeKey(hpke, ephemeralKey, kem);
1070+
if (deserializedKey != IntPtr.Zero)
1071+
wolfcrypt.HpkeFreeKey(hpke, deserializedKey, kem);
1072+
if (receiverKey != IntPtr.Zero)
1073+
wolfcrypt.HpkeFreeKey(hpke, receiverKey, kem);
1074+
if (hpke != IntPtr.Zero)
1075+
wolfcrypt.HpkeFree(hpke);
1076+
}
1077+
} /* END hpke_test */
1078+
8831079
public static void standard_log(int lvl, StringBuilder msg)
8841080
{
8851081
Console.WriteLine(msg);
@@ -941,6 +1137,15 @@ public static void Main(string[] args)
9411137
hash_test((uint)wolfcrypt.hashType.WC_HASH_TYPE_SHA512); /* SHA-512 HASH test */
9421138
hash_test((uint)wolfcrypt.hashType.WC_HASH_TYPE_SHA3_256); /* SHA3_256 HASH test */
9431139

1140+
Console.WriteLine("\nStarting HPKE tests");
1141+
1142+
hpke_test(wolfcrypt.HpkeKem.DHKEM_P256_HKDF_SHA256,
1143+
wolfcrypt.HpkeKdf.HKDF_SHA256,
1144+
wolfcrypt.HpkeAead.AES_128_GCM);
1145+
hpke_test(wolfcrypt.HpkeKem.DHKEM_X25519_HKDF_SHA256,
1146+
wolfcrypt.HpkeKdf.HKDF_SHA256,
1147+
wolfcrypt.HpkeAead.AES_128_GCM);
1148+
9441149
wolfcrypt.Cleanup();
9451150

9461151
Console.WriteLine("\nAll tests completed successfully");

0 commit comments

Comments
 (0)