Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to decrypt Quic traffic in debug builds #83001

Merged
merged 9 commits into from
Jul 5, 2023
1 change: 1 addition & 0 deletions src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<Compile Include="$(CommonPath)System\Net\IPAddressParserStatics.cs" Link="Common\System\Net\IPAddressParserStatics.cs" />
<Compile Include="$(CommonPath)System\Net\Internals\IPEndPointExtensions.cs" Link="Common\System\Net\Internals\IPEndPointExtensions.cs" />
<Compile Include="$(CommonPath)System\Net\Security\TlsAlertMessage.cs" Link="Common\System\Net\Security\TlsAlertMessage.cs" />
<Compile Include="$(CommonPath)System\HexConverter.cs" Link="Common\System\HexConverter.cs" />
<Compile Include="$(CommonPath)System\Net\Security\TargetHostNameHelper.cs" Link="Common\System\Net\Security\TargetHostNameHelper.cs" />
<!-- IP parser -->
<Compile Include="$(CommonPath)System\Net\IPv4AddressHelper.Common.cs" Link="System\Net\IPv4AddressHelper.Common.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ internal sealed class MsQuicContextSafeHandle : MsQuicSafeHandle
/// </summary>
private readonly MsQuicSafeHandle? _parent;

#if DEBUG
/// <summary>
/// Native memory to hold TLS secrets. It needs to live same cycle as the underlying connection.
/// </summary>
private unsafe QUIC_TLS_SECRETS* _tlsSecrets;
wfurt marked this conversation as resolved.
Show resolved Hide resolved

public unsafe QUIC_TLS_SECRETS* GetSecretsBuffer()
{
if (_tlsSecrets == null)
wfurt marked this conversation as resolved.
Show resolved Hide resolved
{
_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)
{
Expand All @@ -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)
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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
wfurt marked this conversation as resolved.
Show resolved Hide resolved
{
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;
rzikm marked this conversation as resolved.
Show resolved Hide resolved

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<byte>(_tlsSecrets->ClientRandom, 32));
}

if (_tlsSecrets->IsSet.ClientHandshakeTrafficSecret != 0)
{
stream.Write(Encoding.ASCII.GetBytes($"CLIENT_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan<byte>(_tlsSecrets->ClientHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n"));
wfurt marked this conversation as resolved.
Show resolved Hide resolved
}

if (_tlsSecrets->IsSet.ServerHandshakeTrafficSecret != 0)
{
stream.Write(Encoding.ASCII.GetBytes($"SERVER_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan<byte>(_tlsSecrets->ServerHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n"));
}

if (_tlsSecrets->IsSet.ClientTrafficSecret0 != 0)
{
stream.Write(Encoding.ASCII.GetBytes($"CLIENT_TRAFFIC_SECRET_0 {clientRandom} {HexConverter.ToString(new ReadOnlySpan<byte>(_tlsSecrets->ClientTrafficSecret0, _tlsSecrets->SecretLength))}\n"));
}

if (_tlsSecrets->IsSet.ServerTrafficSecret0 != 0)
{
stream.Write(Encoding.ASCII.GetBytes($"SERVER_TRAFFIC_SECRET_0 {clientRandom} {HexConverter.ToString(new ReadOnlySpan<byte>(_tlsSecrets->ServerTrafficSecret0, _tlsSecrets->SecretLength))}\n"));
}

if (_tlsSecrets->IsSet.ClientEarlyTrafficSecret != 0)
{
stream.Write(Encoding.ASCII.GetBytes($"CLIENT_EARLY_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan<byte>(_tlsSecrets->ClientEarlyTrafficSecret, _tlsSecrets->SecretLength))}\n"));
}

stream.Flush();
}
}
}

public unsafe void Dispose()
{
if (_tlsSecrets != null)
{
NativeMemory.Clear(_tlsSecrets, (nuint)sizeof(QUIC_TLS_SECRETS));
wfurt marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ namespace System.Net.Quic;
/// </remarks>
public sealed partial class QuicConnection : IAsyncDisposable
{
#if DEBUG
/// <summary>
/// The actual secret structure wrapper passed to MsQuic.
/// </summary>
private MsQuicTlsSecret? _tlsSecret;
#endif

/// <summary>
/// Returns <c>true</c> if QUIC is supported on the current machine and can be used; otherwise, <c>false</c>.
/// </summary>
Expand Down Expand Up @@ -145,7 +152,6 @@ static async ValueTask<QuicConnection> StartConnectAsync(QuicClientConnectionOpt
/// Set when CONNECTED is received.
/// </summary>
private SslApplicationProtocol _negotiatedApplicationProtocol;

/// <summary>
/// The remote endpoint used for this connection.
/// </summary>
Expand Down Expand Up @@ -204,6 +210,10 @@ private unsafe QuicConnection()
context.Free();
throw;
}

#if DEBUG
_tlsSecret = MsQuicTlsSecret.Create(_handle);
#endif
}

/// <summary>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
{
Expand Down