Merge pull request #10191 from dgarske/csharp_pqc

C# Wrapper: ML-KEM and ML-DSA (Dilithium) Support
This commit is contained in:
Sean Parkinson
2026-04-15 09:05:25 +10:00
committed by GitHub
4 changed files with 1438 additions and 0 deletions
+11
View File
@@ -87,6 +87,17 @@
#define ECC_TIMING_RESISTANT
#define HAVE_COMP_KEY
/* Enable ML-KEM, ML-DSA */
#define HAVE_MLKEM
#define WOLFSSL_WC_MLKEM
#define WOLFSSL_HAVE_MLKEM
/* Required for PQC with DTLS 1.3 (auto-enabled in settings.h, explicit for clarity) */
#define WOLFSSL_DTLS_CH_FRAG
#define HAVE_DILITHIUM
#define WOLFSSL_WC_DILITHIUM
#define WOLFSSL_SHAKE128
#define WOLFSSL_SHAKE256
/* Disable features */
#define NO_PSK
@@ -674,6 +674,311 @@ public class wolfCrypt_Test_CSharp
if (publicKeyB != IntPtr.Zero) wolfcrypt.Curve25519FreeKey(publicKeyB);
} /* END curve25519_test */
private static void mlkem_test(wolfcrypt.MlKemTypes type)
{
int ret = 0;
IntPtr keyA = IntPtr.Zero;
IntPtr keyB = IntPtr.Zero;
IntPtr heap = IntPtr.Zero;
int devId = wolfcrypt.INVALID_DEVID;
byte[] pubA = null;
byte[] privA = null;
byte[] cipherText = null;
byte[] sharedSecretA = null;
byte[] sharedSecretB = null;
try
{
Console.WriteLine("\nStarting " + type + " shared secret test ...");
/* Generate Key Pair */
Console.WriteLine("Testing ML-KEM Key Generation...");
Console.WriteLine("Generate Key Pair A...");
keyA = wolfcrypt.MlKemMakeKey(type, heap, devId);
if (keyA == IntPtr.Zero)
{
ret = -1;
Console.Error.WriteLine("Failed to generate key pair A.");
}
if (ret == 0)
{
Console.WriteLine("Initialize Key B for decode...");
keyB = wolfcrypt.MlKemNew(type, heap, devId);
if (keyB == IntPtr.Zero)
{
ret = -1;
Console.Error.WriteLine("Failed to initialize key B for decode.");
}
}
if (ret == 0)
{
Console.WriteLine("ML-KEM Key Generation test passed.");
}
/* Encode */
if (ret == 0)
{
Console.WriteLine("Testing ML-KEM Key Encode...");
ret = wolfcrypt.MlKemEncodePublicKey(keyA, out pubA);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to encode public key of A. Error code: {ret}");
}
}
if (ret == 0)
{
ret = wolfcrypt.MlKemEncodePrivateKey(keyA, out privA);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to encode private key of A. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-KEM Key Encode test passed.");
}
/* Encapsulate */
if (ret == 0)
{
Console.WriteLine("Testing ML-KEM Encapsulation...");
ret = wolfcrypt.MlKemEncapsulate(keyA, out cipherText, out sharedSecretA);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to encapsulate. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-KEM Encapsulation test passed.");
}
/* Decode */
if (ret == 0)
{
Console.WriteLine("Testing ML-KEM Decode...");
ret = wolfcrypt.MlKemDecodePrivateKey(keyB, privA);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to decode private key of A. Error code: {ret}");
}
}
if (ret == 0)
{
ret = wolfcrypt.MlKemDecodePublicKey(keyB, pubA);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to decode public key of A. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-KEM Decode test passed.");
}
/* Decapsulate */
if (ret == 0)
{
Console.WriteLine("Testing ML-KEM Decapsulation...");
ret = wolfcrypt.MlKemDecapsulate(keyB, cipherText, out sharedSecretB);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to decapsulate. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-KEM Decapsulation test passed.");
}
/* Check */
if (ret == 0)
{
Console.WriteLine("Comparing Shared Secrets...");
if (!wolfcrypt.ByteArrayVerify(sharedSecretA, sharedSecretB))
{
ret = -1;
Console.Error.WriteLine($"Shared secrets do not match. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-KEM shared secret match.");
}
if (ret != 0)
{
throw new Exception("ML-KEM test failed.");
}
}
catch (Exception ex)
{
Console.WriteLine($"ML-KEM test failed: {ex.Message}");
throw;
}
finally
{
/* Cleanup */
if (keyA != IntPtr.Zero)
{
ret = wolfcrypt.MlKemFreeKey(ref keyA);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to free MlKem key A. Error code: {ret}");
}
}
if (keyB != IntPtr.Zero)
{
ret = wolfcrypt.MlKemFreeKey(ref keyB);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to free MlKem key B. Error code: {ret}");
}
}
}
} /* END mlkem_test */
private static void mldsa_test(wolfcrypt.MlDsaLevels level)
{
int ret = 0;
IntPtr key = IntPtr.Zero;
IntPtr importKey = IntPtr.Zero;
IntPtr heap = IntPtr.Zero;
int devId = wolfcrypt.INVALID_DEVID;
byte[] privateKey = null;
byte[] publicKey = null;
byte[] message = Encoding.UTF8.GetBytes("This is some data to sign with ML-DSA");
byte[] signature = null;
try
{
Console.WriteLine("\nStarting " + level + " key generation and signature test ...");
/* Generate Key */
Console.WriteLine("Testing ML-DSA Key Generation...");
key = wolfcrypt.MlDsaMakeKey(heap, devId, level);
if (key == IntPtr.Zero)
{
ret = -1;
Console.Error.WriteLine("Failed to generate keypair.");
}
if (ret == 0)
{
Console.WriteLine("ML-DSA Key Generation test passed.");
}
/* Export */
if (ret == 0)
{
Console.WriteLine("Testing ML-DSA Key Export...");
ret = wolfcrypt.MlDsaExportPrivateKey(key, out privateKey);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to export private key. Error code: {ret}");
}
}
if (ret == 0)
{
ret = wolfcrypt.MlDsaExportPublicKey(key, out publicKey);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to export public key. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-DSA Key Export test passed.");
}
/* Import into a fresh key to test the full import workflow */
if (ret == 0)
{
Console.WriteLine("Testing ML-DSA Key Import...");
/* Free the keygen key and create a fresh one for import */
wolfcrypt.MlDsaFreeKey(ref key);
importKey = wolfcrypt.MlDsaNew(heap, devId, level);
if (importKey == IntPtr.Zero)
{
ret = -1;
Console.Error.WriteLine("Failed to allocate key for import.");
}
}
if (ret == 0)
{
ret = wolfcrypt.MlDsaImportPrivateKey(privateKey, importKey);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to import private key. Error code: {ret}");
}
}
if (ret == 0)
{
ret = wolfcrypt.MlDsaImportPublicKey(publicKey, importKey);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to import public key. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-DSA Key Import test passed.");
}
/* Sign with imported key */
if (ret == 0)
{
Console.WriteLine("Testing ML-DSA Signature Creation...");
ret = wolfcrypt.MlDsaSignMsg(importKey, message, out signature);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to sign. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine($"ML-DSA Signature Creation test passed. Signature Length: {signature.Length}");
}
/* Verify with imported key */
if (ret == 0)
{
Console.WriteLine("Testing ML-DSA Signature Verification...");
ret = wolfcrypt.MlDsaVerifyMsg(importKey, message, signature);
if (ret != 0)
{
Console.Error.WriteLine($"Failed to verify message. Error code: {ret}");
}
}
if (ret == 0)
{
Console.WriteLine("ML-DSA Signature Verification test passed.");
}
if (ret != 0)
{
throw new Exception("ML-DSA test failed.");
}
}
catch (Exception ex)
{
Console.WriteLine($"ML-DSA test failed: {ex.Message}");
throw;
}
finally
{
if (key != IntPtr.Zero)
{
wolfcrypt.MlDsaFreeKey(ref key);
}
if (importKey != IntPtr.Zero)
{
wolfcrypt.MlDsaFreeKey(ref importKey);
}
}
} /* END mldsa_test */
private static void aes_gcm_test()
{
IntPtr aes = IntPtr.Zero;
@@ -1126,6 +1431,18 @@ public class wolfCrypt_Test_CSharp
curve25519_test(); /* curve25519 shared secret test */
Console.WriteLine("\nStarting ML-KEM test");
mlkem_test(wolfcrypt.MlKemTypes.ML_KEM_512); /* ML-KEM test */
mlkem_test(wolfcrypt.MlKemTypes.ML_KEM_768); /* ML-KEM test */
mlkem_test(wolfcrypt.MlKemTypes.ML_KEM_1024); /* ML-KEM test */
Console.WriteLine("\nStarting ML-DSA test");
mldsa_test(wolfcrypt.MlDsaLevels.ML_DSA_44); /* ML-DSA test */
mldsa_test(wolfcrypt.MlDsaLevels.ML_DSA_65); /* ML-DSA test */
mldsa_test(wolfcrypt.MlDsaLevels.ML_DSA_87); /* ML-DSA test */
Console.WriteLine("\nStarting AES-GCM test");
aes_gcm_test(); /* AES_GCM test */
File diff suppressed because it is too large Load Diff
+118
View File
@@ -509,6 +509,8 @@ namespace wolfSSL.CSharp
private extern static int wolfSSL_shutdown(IntPtr ssl);
[DllImport(wolfssl_dll)]
private extern static void wolfSSL_free(IntPtr ssl);
[DllImport(wolfssl_dll)]
private static extern int wolfSSL_UseKeyShare(IntPtr ssl, ushort group);
#else
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static IntPtr wolfSSL_new(IntPtr ctx);
@@ -524,6 +526,8 @@ namespace wolfSSL.CSharp
private extern static int wolfSSL_shutdown(IntPtr ssl);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private extern static void wolfSSL_free(IntPtr ssl);
[DllImport(wolfssl_dll, CallingConvention = CallingConvention.Cdecl)]
private static extern int wolfSSL_UseKeyShare(IntPtr ssl, ushort group);
#endif
/********************************
@@ -709,6 +713,90 @@ namespace wolfSSL.CSharp
public static readonly int WOLFSSL_LOAD_FLAG_IGNORE_ZEROFILE = 0x00000010;
public static readonly int WOLFSSL_LOAD_VERIFY_DEFAULT_FLAGS = WOLFSSL_LOAD_FLAG_NONE;
/* from ssl.h */
public enum NamedGroup
{
WOLFSSL_NAMED_GROUP_INVALID = 0,
WOLFSSL_ECC_SECP160K1 = 15,
WOLFSSL_ECC_SECP160R1 = 16,
WOLFSSL_ECC_SECP160R2 = 17,
WOLFSSL_ECC_SECP192K1 = 18,
WOLFSSL_ECC_SECP192R1 = 19,
WOLFSSL_ECC_SECP224K1 = 20,
WOLFSSL_ECC_SECP224R1 = 21,
WOLFSSL_ECC_SECP256K1 = 22,
WOLFSSL_ECC_SECP256R1 = 23,
WOLFSSL_ECC_SECP384R1 = 24,
WOLFSSL_ECC_SECP521R1 = 25,
WOLFSSL_ECC_BRAINPOOLP256R1 = 26,
WOLFSSL_ECC_BRAINPOOLP384R1 = 27,
WOLFSSL_ECC_BRAINPOOLP512R1 = 28,
WOLFSSL_ECC_X25519 = 29,
WOLFSSL_ECC_X448 = 30,
WOLFSSL_ECC_BRAINPOOLP256R1TLS13 = 31,
WOLFSSL_ECC_BRAINPOOLP384R1TLS13 = 32,
WOLFSSL_ECC_BRAINPOOLP512R1TLS13 = 33,
WOLFSSL_ECC_SM2P256V1 = 41,
WOLFSSL_ECC_MAX = 41,
WOLFSSL_ECC_MAX_AVAIL = 46,
/* Update use of disabled curves when adding value greater than 46. */
WOLFSSL_FFDHE_START = 256,
WOLFSSL_FFDHE_2048 = 256,
WOLFSSL_FFDHE_3072 = 257,
WOLFSSL_FFDHE_4096 = 258,
WOLFSSL_FFDHE_6144 = 259,
WOLFSSL_FFDHE_8192 = 260,
WOLFSSL_FFDHE_END = 511,
/* Old code points to keep compatibility with MlKem Round 3.
* Taken from OQS's openssl provider, see:
* https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/
* oqs-kem-info.md
*/
WOLFSSL_KYBER_LEVEL1 = 570, /* KYBER_512 */
WOLFSSL_KYBER_LEVEL3 = 572, /* KYBER_768 */
WOLFSSL_KYBER_LEVEL5 = 573, /* KYBER_1024 */
WOLFSSL_P256_KYBER_LEVEL1 = 12090,
WOLFSSL_P384_KYBER_LEVEL3 = 12092,
WOLFSSL_P521_KYBER_LEVEL5 = 12093,
WOLFSSL_X25519_KYBER_LEVEL1 = 12089,
WOLFSSL_X448_KYBER_LEVEL3 = 12176,
WOLFSSL_X25519_KYBER_LEVEL3 = 25497,
WOLFSSL_P256_KYBER_LEVEL3 = 25498,
/* Taken from draft-ietf-tls-mlkem, see:
* https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/
*/
WOLFSSL_ML_KEM_512 = 512,
WOLFSSL_ML_KEM_768 = 513,
WOLFSSL_ML_KEM_1024 = 514,
/* Taken from draft-ietf-tls-ecdhe-mlkem, see:
* https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/
*/
WOLFSSL_SECP256R1MLKEM768 = 4587,
WOLFSSL_X25519MLKEM768 = 4588,
WOLFSSL_SECP384R1MLKEM1024 = 4589,
/* Taken from OQS's openssl provider, see:
* https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/
* oqs-kem-info.md
*/
WOLFSSL_P256_ML_KEM_512_OLD = 12103,
WOLFSSL_P384_ML_KEM_768_OLD = 12104,
WOLFSSL_P521_ML_KEM_1024_OLD = 12105,
WOLFSSL_SECP256R1MLKEM512 = 12107,
WOLFSSL_SECP384R1MLKEM768 = 12108,
WOLFSSL_SECP521R1MLKEM1024 = 12109,
WOLFSSL_X25519MLKEM512 = 12214,
WOLFSSL_X448MLKEM768 = 12215,
}
private static IntPtr unwrap_ctx(IntPtr ctx)
{
@@ -1288,6 +1376,36 @@ namespace wolfSSL.CSharp
}
/// <summary>
/// Creates a key share entry for the specified group on the given SSL/TLS connection.
/// </summary>
/// <param name="ssl">Pointer to the SSL structure to use.</param>
/// <param name="group">The key exchange group identifier to use for key share (e.g., TLS supported group ID).</param>
/// <returns>1 on success</returns>
public static int UseKeyShare(IntPtr ssl, NamedGroup group)
{
if (ssl == IntPtr.Zero)
{
return FAILURE;
}
try
{
IntPtr sslCtx = unwrap_ssl(ssl);
if (sslCtx == IntPtr.Zero)
{
log(ERROR_LOG, "UseKeyShare ssl unwrap error");
return FAILURE;
}
return wolfSSL_UseKeyShare(sslCtx, (ushort)group);
}
catch (Exception e)
{
log(ERROR_LOG, "wolfSSL_UseKeyShare error " + e.ToString());
return FAILURE;
}
}
/// <summary>
/// Optional, can be used to set a custom receive function
/// </summary>