← Knowledge base

.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:

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.

References

Run the check on your .NET service →