File: src\runtime\src\libraries\Common\src\System\Net\Security\SslKeyLogger.cs
Web Access
Project: src\runtime\src\libraries\System.Net.Security\src\System.Net.Security.csproj (System.Net.Security)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.IO;
using System.Net;

internal static class SslKeyLogger
{
    private static readonly string? s_keyLogFile = Environment.GetEnvironmentVariable("SSLKEYLOGFILE");
    private static readonly FileStream? s_fileStream;

#pragma warning disable CA1810 // Initialize all static fields when declared and remove cctor
    static SslKeyLogger()
    {
        s_fileStream = null;

        try
        {
            bool isEnabled = LocalAppContextSwitches.EnableSslKeyLogging;

            if (isEnabled && s_keyLogFile != null)
            {
                s_fileStream = File.Open(s_keyLogFile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
            }
        }
        catch (Exception ex)
        {
            if (NetEventSource.Log.IsEnabled())
            {
                NetEventSource.Error(null, $"Failed to open SSL key log file '{s_keyLogFile}': {ex}");
            }
        }
    }
#pragma warning restore CA1810

    public static bool IsEnabled => s_fileStream != null;

    public static void WriteLineRaw(ReadOnlySpan<byte> data)
    {
        Debug.Assert(s_fileStream != null);
        if (s_fileStream == null)
        {
            return;
        }

        if (data.Length > 0)
        {
            lock (s_fileStream)
            {
                s_fileStream.Write(data);
                s_fileStream.WriteByte((byte)'\n');
                s_fileStream.Flush();
            }
        }
    }

    public static unsafe void WriteSecrets(
        ReadOnlySpan<byte> clientRandom,
        ReadOnlySpan<byte> clientHandshakeTrafficSecret,
        ReadOnlySpan<byte> serverHandshakeTrafficSecret,
        ReadOnlySpan<byte> clientTrafficSecret0,
        ReadOnlySpan<byte> serverTrafficSecret0,
        ReadOnlySpan<byte> clientEarlyTrafficSecret)
    {
        Debug.Assert(s_fileStream != null);
        Debug.Assert(!clientRandom.IsEmpty);

        if (s_fileStream == null ||
            clientRandom.IsEmpty ||

            // return early if there is nothing to log
            (clientHandshakeTrafficSecret.IsEmpty &&
            serverHandshakeTrafficSecret.IsEmpty &&
            clientTrafficSecret0.IsEmpty &&
            serverTrafficSecret0.IsEmpty &&
            clientEarlyTrafficSecret.IsEmpty))
        {
            return;
        }

        Span<byte> clientRandomUtf8 = clientRandom.Length <= 1024 ? stackalloc byte[clientRandom.Length * 2] : new byte[clientRandom.Length * 2];
        HexEncode(clientRandom, clientRandomUtf8);

        lock (s_fileStream)
        {
            WriteSecretCore("CLIENT_HANDSHAKE_TRAFFIC_SECRET"u8, clientRandomUtf8, clientHandshakeTrafficSecret);
            WriteSecretCore("SERVER_HANDSHAKE_TRAFFIC_SECRET"u8, clientRandomUtf8, serverHandshakeTrafficSecret);
            WriteSecretCore("CLIENT_TRAFFIC_SECRET_0"u8, clientRandomUtf8, clientTrafficSecret0);
            WriteSecretCore("SERVER_TRAFFIC_SECRET_0"u8, clientRandomUtf8, serverTrafficSecret0);
            WriteSecretCore("CLIENT_EARLY_TRAFFIC_SECRET"u8, clientRandomUtf8, clientEarlyTrafficSecret);

            s_fileStream.Flush();
        }
    }

    private static unsafe void WriteSecretCore(ReadOnlySpan<byte> labelUtf8, ReadOnlySpan<byte> clientRandomUtf8, ReadOnlySpan<byte> secret)
    {
        if (secret.Length == 0)
        {
            return;
        }

        // write the secret line in the format {label} {client_random (hex)} {secret (hex)} e.g.
        // SERVER_HANDSHAKE_TRAFFIC_SECRET bae582227f0f46ca663cb8c3d62e68cec38c2b947e7c4a9ec6f4e262b5ed5354 48f6bd5b0c8447d97129c6dad080f34c7f9f11ade8eeabb011f33811543411d7ab1013b1374bcd81bfface6a2deef539
        int totalLength = checked(labelUtf8.Length + 1 + clientRandomUtf8.Length + 1 + 2 * secret.Length + 1);
        Span<byte> line = (uint)totalLength <= 1024 ? stackalloc byte[totalLength] : new byte[totalLength];

        labelUtf8.CopyTo(line);
        line[labelUtf8.Length] = (byte)' ';

        clientRandomUtf8.CopyTo(line.Slice(labelUtf8.Length + 1));
        line[labelUtf8.Length + 1 + clientRandomUtf8.Length] = (byte)' ';

        HexEncode(secret, line.Slice(labelUtf8.Length + 1 + clientRandomUtf8.Length + 1));
        line[^1] = (byte)'\n';

        s_fileStream!.Write(line);
    }

    private static void HexEncode(ReadOnlySpan<byte> source, Span<byte> destination)
    {
        for (int i = 0; i < source.Length; i++)
        {
            HexConverter.ToBytesBuffer(source[i], destination.Slice(i * 2));
        }
    }

}