From 42c68ead37b2868ffa74d7b3982ff0a35645a521 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 20 Apr 2026 08:42:35 +0200 Subject: [PATCH 1/2] Add tls-uses-pqc test to verify OpenSSL TLS does PQC key exchange. --- tls-uses-pqc/Program.cs | 73 ++++++++++++++++++++++++++++++++ tls-uses-pqc/test.json | 16 +++++++ tls-uses-pqc/test.sh | 6 +++ tls-uses-pqc/tls-uses-pqc.csproj | 10 +++++ 4 files changed, 105 insertions(+) create mode 100644 tls-uses-pqc/Program.cs create mode 100644 tls-uses-pqc/test.json create mode 100755 tls-uses-pqc/test.sh create mode 100644 tls-uses-pqc/tls-uses-pqc.csproj diff --git a/tls-uses-pqc/Program.cs b/tls-uses-pqc/Program.cs new file mode 100644 index 00000000..ac280aba --- /dev/null +++ b/tls-uses-pqc/Program.cs @@ -0,0 +1,73 @@ +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +// Create a self-signed certificate. +using var key = ECDsa.Create(); +var certReq = new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256); +using var cert = certReq.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddYears(1)); + +// Set up a loopback TCP connection. +var listener = new TcpListener(IPAddress.Loopback, 0); +listener.Start(); +int port = ((IPEndPoint)listener.LocalEndpoint).Port; + +// Server side: accept and authenticate as TLS server. +var serverTask = Task.Run(async () => +{ + using var serverClient = await listener.AcceptTcpClientAsync(); + await using var serverSsl = new SslStream(serverClient.GetStream(), false); + await serverSsl.AuthenticateAsServerAsync(cert); + return GetNegotiatedGroupName(serverSsl); +}); + +// Client side: connect and authenticate as TLS client. +using var client = new TcpClient(); +await client.ConnectAsync(IPAddress.Loopback, port); +await using var clientSsl = new SslStream(client.GetStream(), false, (sender, certificate, chain, errors) => true /* accept cert */); +await clientSsl.AuthenticateAsClientAsync("localhost"); + +string? clientGroup = GetNegotiatedGroupName(clientSsl); +string? serverGroup = await serverTask; +listener.Stop(); + +Console.WriteLine($"Client negotiated group: {clientGroup}"); +Console.WriteLine($"Server negotiated group: {serverGroup}"); + +// Verify PQC was used: the group name should contain "MLKEM". +bool clientPqc = clientGroup != null && clientGroup.Contains("MLKEM", StringComparison.OrdinalIgnoreCase); +bool serverPqc = serverGroup != null && serverGroup.Contains("MLKEM", StringComparison.OrdinalIgnoreCase); + +if (clientPqc && serverPqc) +{ + Console.WriteLine("PASS: PQC key exchange negotiated."); + return 0; +} + +Console.WriteLine($"FAIL: Expected PQC key exchange group containing 'MLKEM', got client: '{clientGroup}', server: '{serverGroup}'"); +return 1; + +// Use OpenSSL interop to get the negotiated key exchange group (SslStream doesn't expose this information). +static string? GetNegotiatedGroupName(SslStream sslStream) +{ + var secCtxField = typeof(SslStream).GetField("_securityContext", BindingFlags.NonPublic | BindingFlags.Instance)!; + var safeSslHandle = (SafeHandle)secCtxField.GetValue(sslStream)!; + bool added = false; + safeSslHandle.DangerousAddRef(ref added); + try + { + IntPtr namePtr = SSL_get0_group_name(safeSslHandle.DangerousGetHandle()); + return namePtr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(namePtr); + } + finally + { + if (added) safeSslHandle.DangerousRelease(); + } +} + +[DllImport("libssl.so.3")] +static extern IntPtr SSL_get0_group_name(IntPtr ssl); diff --git a/tls-uses-pqc/test.json b/tls-uses-pqc/test.json new file mode 100644 index 00000000..d19123eb --- /dev/null +++ b/tls-uses-pqc/test.json @@ -0,0 +1,16 @@ +{ + "name": "tls-uses-pqc", + "enabled": true, + "requiresSdk": true, + "version": "10.0", + "versionSpecific": false, + "type": "bash", + "cleanup": true, + "ignoredRIDs":[ + "rhel.8", // no PQC key exchange support + "rhel.9", // no PQC key exchange support + "centos.8", // no PQC key exchange support + "centos.9", // no PQC key exchange support + "fedora.42" // no PQC key exchange support + ] +} diff --git a/tls-uses-pqc/test.sh b/tls-uses-pqc/test.sh new file mode 100755 index 00000000..7369d686 --- /dev/null +++ b/tls-uses-pqc/test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +dotnet run diff --git a/tls-uses-pqc/tls-uses-pqc.csproj b/tls-uses-pqc/tls-uses-pqc.csproj new file mode 100644 index 00000000..305d8ad1 --- /dev/null +++ b/tls-uses-pqc/tls-uses-pqc.csproj @@ -0,0 +1,10 @@ + + + + Exe + $(TestTargetFramework) + enable + enable + + + From 9a5724cd8bff830d536a1231bf9ddef9d212c77f Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 22 Apr 2026 09:24:24 +0200 Subject: [PATCH 2/2] Describe what is being tested. --- tls-uses-pqc/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tls-uses-pqc/Program.cs b/tls-uses-pqc/Program.cs index ac280aba..5585079f 100644 --- a/tls-uses-pqc/Program.cs +++ b/tls-uses-pqc/Program.cs @@ -1,3 +1,6 @@ +// This test verifies that OpenSSL TLS uses PQC key exchange by default. +// It performs a standard TLS handshake and then checks that a PQC group was negotiated. + using System.Net; using System.Net.Security; using System.Net.Sockets;