.NET
Is PQC enabled? — quick check
macOS / Linux / Windows
# 1) No-dependency check — identify this machine first.
uname -a 2>/dev/null || true
# 2) Dependency check — prompt before installing anything.
if ! command -v dotnet >/dev/null 2>&1; then
echo '.NET SDK/runtime was not found.'
printf 'Install or enable .NET SDK/runtime now? [y/N] '
read answer
case "$answer" in
[Yy]*) echo 'Install .NET 9+ from your OS package manager or dotnet.microsoft.com, then rerun this snippet.' ;;
*) echo 'Skipping .NET SDK/runtime-based check.'; exit 1 ;;
esac
fi
# 3) .NET runtime info — offline.
dotnet --info | head -8
# .NET's SslStream cannot expose the negotiated TLS named group on any
# platform. The honest local answer is to pair .NET with the system openssl
# binary (Linux/macOS) or a winget-installed openssl (Windows):
# 1) No-dependency check — identify this machine first.
uname -a 2>/dev/null || true
# 2) Dependency check — prompt before installing anything.
if ! command -v openssl >/dev/null 2>&1; then
echo 'OpenSSL was not found. A local PQC proof needs OpenSSL 3.5+.'
printf 'Install OpenSSL now? [y/N] '
read answer
case "$answer" in
[Yy]*)
if command -v brew >/dev/null 2>&1; then brew install openssl@3
elif command -v apt-get >/dev/null 2>&1; then sudo apt-get update && sudo apt-get install -y openssl
elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y openssl
elif command -v yum >/dev/null 2>&1; then sudo yum install -y openssl
else echo 'No supported package manager found. Install OpenSSL 3.5+ and retry.'; exit 1
fi ;;
*) echo 'Install OpenSSL 3.5+ and retry for a local PQC proof.'; exit 1 ;;
esac
fi
OPENSSL=openssl
if command -v brew >/dev/null 2>&1; then
BREW_OPENSSL="$(brew --prefix openssl@3 2>/dev/null)/bin/openssl"
[ -x "$BREW_OPENSSL" ] && OPENSSL="$BREW_OPENSSL"
fi
$OPENSSL version
if ! $OPENSSL list -tls-groups 2>/dev/null | grep -qiE 'X25519MLKEM768|MLKEM|Kyber'; then
echo 'This OpenSSL does not advertise ML-KEM groups. Upgrade to OpenSSL 3.5+ or load oqsprovider, then retry.'
exit 1
fi
$OPENSSL s_client -connect example.com:443 -tls1_3 -groups X25519MLKEM768 </dev/null 2>&1 | grep -E 'Negotiated TLS1\.3 group|Server Temp Key|Cipher is|alert' Expected when PQC is ON
.NET SDK Version: 9.0.100
Negotiated TLS1.3 group: X25519MLKEM768
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 What you'll see when PQC is OFF
.NET SDK Version: 8.0.404
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
# (no group line — server has no overlap with offered PQ group) Track dotnet/runtime#107568 for native PQ support in SslStream. Until then SslStream is fine for transport, but cannot answer 'was PQ used?' from inside the process.
SslStream in .NET delegates TLS to the platform:
- Linux / macOS — uses the system OpenSSL via the
System.Security.Cryptography.Native.OpenSslshim. Install OpenSSL 3.5+ to getX25519MLKEM768. - Windows — uses Schannel. Microsoft has begun rolling out PQC TLS to Windows Insider builds; production Windows 11 23H2/24H2 does not yet negotiate hybrid PQ. Track SymCrypt for status.
Configure named groups (Linux/macOS)
.NET 9 added SslClientAuthenticationOptions.AllowTlsResume and exposes
cipher-suite policy through CipherSuitesPolicy, but not a managed
named-groups list. The way to influence groups is the underlying OpenSSL config:
OPENSSL_CONF=/etc/ssl/openssl-pqc.cnf dotnet run
With Groups = X25519MLKEM768:X25519:secp256r1 in that config file, every
HttpClient, SqlConnection, gRPC channel, and Kestrel listener
negotiates hybrid PQC.
Kestrel / ASP.NET Core
builder.WebHost.ConfigureKestrel(o => {
o.ConfigureHttpsDefaults(h => {
h.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12;
});
}); The OS / OpenSSL config below Kestrel decides which named groups go on the wire.