diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj index c30e10521a02f..53b39492c39a4 100644 --- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj +++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj @@ -32,6 +32,7 @@ + diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs index 3adc922745423..8e87654f2e321 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs @@ -92,6 +92,23 @@ internal sealed class MsQuicContextSafeHandle : MsQuicSafeHandle /// private readonly MsQuicSafeHandle? _parent; +#if DEBUG + /// + /// Native memory to hold TLS secrets. It needs to live same cycle as the underlying connection. + /// + private unsafe QUIC_TLS_SECRETS* _tlsSecrets; + + public unsafe QUIC_TLS_SECRETS* GetSecretsBuffer() + { + if (_tlsSecrets == null) + { + _tlsSecrets = (QUIC_TLS_SECRETS*)NativeMemory.Alloc((nuint)sizeof(QUIC_TLS_SECRETS)); + } + + return _tlsSecrets; + } +#endif + public unsafe MsQuicContextSafeHandle(QUIC_HANDLE* handle, GCHandle context, SafeHandleType safeHandleType, MsQuicSafeHandle? parent = null) : base(handle, safeHandleType) { @@ -108,7 +125,7 @@ public unsafe MsQuicContextSafeHandle(QUIC_HANDLE* handle, GCHandle context, Saf } } - protected override bool ReleaseHandle() + protected override unsafe bool ReleaseHandle() { base.ReleaseHandle(); if (_context.IsAllocated) @@ -123,6 +140,13 @@ protected override bool ReleaseHandle() NetEventSource.Info(this, $"{this} {_parent} ref count decremented"); } } +#if DEBUG + if (_tlsSecrets != null) + { + NativeMemory.Clear(_tlsSecrets, (nuint)sizeof(QUIC_TLS_SECRETS)); + NativeMemory.Free(_tlsSecrets); + } +#endif return true; } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicTlsSecret.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicTlsSecret.cs new file mode 100644 index 0000000000000..3a19656f63acb --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicTlsSecret.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if DEBUG +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using Microsoft.Quic; +using static Microsoft.Quic.MsQuic; + +namespace System.Net.Quic; + +internal sealed class MsQuicTlsSecret : IDisposable +{ + private static readonly string? s_keyLogFile = Environment.GetEnvironmentVariable("SSLKEYLOGFILE"); + private static readonly FileStream? s_fileStream = s_keyLogFile != null ? File.Open(s_keyLogFile, FileMode.Append, FileAccess.Write) : null; + + private unsafe QUIC_TLS_SECRETS* _tlsSecrets; + + public static unsafe MsQuicTlsSecret? Create(MsQuicContextSafeHandle handle) + { + if (s_fileStream != null) + { + try + { + QUIC_TLS_SECRETS* ptr = handle.GetSecretsBuffer(); + if (ptr != null) + { + int status = MsQuicApi.Api.SetParam(handle, QUIC_PARAM_CONN_TLS_SECRETS, (uint)sizeof(QUIC_TLS_SECRETS), ptr); + + if (StatusSucceeded(status)) + { + return new MsQuicTlsSecret(ptr); + } + else + { + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Error(handle, "Failed to set native memory for TLS secret."); + } + } + } + } + catch { }; + } + + return null; + } + + private unsafe MsQuicTlsSecret(QUIC_TLS_SECRETS* memory) + { + _tlsSecrets = memory; + } + + public void WriteSecret() => WriteSecret(s_fileStream); + public unsafe void WriteSecret(FileStream? stream) + { + if (stream != null && _tlsSecrets != null) + { + lock (stream) + { + string clientRandom = string.Empty; + + if (_tlsSecrets->IsSet.ClientRandom != 0) + { + clientRandom = HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientRandom, 32)); + } + + if (_tlsSecrets->IsSet.ClientHandshakeTrafficSecret != 0) + { + stream.Write(Encoding.ASCII.GetBytes($"CLIENT_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n")); + } + + if (_tlsSecrets->IsSet.ServerHandshakeTrafficSecret != 0) + { + stream.Write(Encoding.ASCII.GetBytes($"SERVER_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ServerHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n")); + } + + if (_tlsSecrets->IsSet.ClientTrafficSecret0 != 0) + { + stream.Write(Encoding.ASCII.GetBytes($"CLIENT_TRAFFIC_SECRET_0 {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientTrafficSecret0, _tlsSecrets->SecretLength))}\n")); + } + + if (_tlsSecrets->IsSet.ServerTrafficSecret0 != 0) + { + stream.Write(Encoding.ASCII.GetBytes($"SERVER_TRAFFIC_SECRET_0 {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ServerTrafficSecret0, _tlsSecrets->SecretLength))}\n")); + } + + if (_tlsSecrets->IsSet.ClientEarlyTrafficSecret != 0) + { + stream.Write(Encoding.ASCII.GetBytes($"CLIENT_EARLY_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientEarlyTrafficSecret, _tlsSecrets->SecretLength))}\n")); + } + + stream.Flush(); + } + } + } + + public unsafe void Dispose() + { + if (_tlsSecrets != null) + { + NativeMemory.Clear(_tlsSecrets, (nuint)sizeof(QUIC_TLS_SECRETS)); + } + } +} +#endif diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index 4422277733425..b3ac83567cee4 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -38,6 +38,13 @@ namespace System.Net.Quic; /// public sealed partial class QuicConnection : IAsyncDisposable { +#if DEBUG + /// + /// The actual secret structure wrapper passed to MsQuic. + /// + private MsQuicTlsSecret? _tlsSecret; +#endif + /// /// Returns true if QUIC is supported on the current machine and can be used; otherwise, false. /// @@ -145,7 +152,6 @@ static async ValueTask StartConnectAsync(QuicClientConnectionOpt /// Set when CONNECTED is received. /// private SslApplicationProtocol _negotiatedApplicationProtocol; - /// /// The remote endpoint used for this connection. /// @@ -204,6 +210,10 @@ private unsafe QuicConnection() context.Free(); throw; } + +#if DEBUG + _tlsSecret = MsQuicTlsSecret.Create(_handle); +#endif } /// @@ -231,6 +241,9 @@ internal unsafe QuicConnection(QUIC_HANDLE* handle, QUIC_NEW_CONNECTION_INFO* in _remoteEndPoint = info->RemoteAddress->ToIPEndPoint(); _localEndPoint = info->LocalAddress->ToIPEndPoint(); +#if DEBUG + _tlsSecret = MsQuicTlsSecret.Create(_handle); +#endif } private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, CancellationToken cancellationToken = default) @@ -600,6 +613,9 @@ public async ValueTask DisposeAsync() return; } +#if DEBUG + _tlsSecret?.Dispose(); +#endif // Check if the connection has been shut down and if not, shut it down. if (_shutdownTcs.TryInitialize(out ValueTask valueTask, this)) {