File: Connection\DirectTlsConnection.FeatureCollection.cs
Web Access
Project: src\aspnetcore\src\Servers\Kestrel\Transport.DirectTls\src\Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls.csproj (Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls.Connection;
 
/// <summary>
/// Feature-collection surface for a DirectTls connection, implemented directly on the connection object.
/// </summary>
internal sealed partial class DirectTlsConnection : ITlsConnectionFeature, ITlsHandshakeFeature, ITlsApplicationProtocolFeature, IConnectionSocketFeature
{
    private Socket? _socket;
    private readonly object _socketLock = new();
 
    // The ALPN protocol negotiated during the handshake (empty when none was negotiated). Set in the
    // constructor and again at CompleteHandshake once the handshake has actually completed.
    private SslApplicationProtocol _negotiatedApplicationProtocol;
 
    // The TLS session backing this connection. DirectTls terminates TLS for every connection.
    private TlsSocketSession TlsSession => _connectionState.Session;
 
    // ── ITlsConnectionFeature ────────────────────────────────────────────────
    // Present on the connection so the UseHttps middleware no-ops (it does not wrap the already-encrypted
    // transport in a second SslStream) and Kestrel resolves the request scheme as https.
 
    /// <summary>
    /// The client certificate presented during the (mutual-TLS) handshake, or <see langword="null"/> when the
    /// endpoint did not request one or the peer did not present one. It is captured and validated at handshake
    /// completion by the TLS event pump; it is not re-queried from the native session on read.
    /// </summary>
    public X509Certificate2? ClientCertificate { get; set; }
 
    /// <inheritdoc />
    public Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken)
        => Task.FromResult(ClientCertificate);
 
    // ── ITlsHandshakeFeature ─────────────────────────────────────────────────
 
    /// <inheritdoc />
    public SslProtocols Protocol => TlsSession.NegotiatedProtocol;
 
    /// <inheritdoc />
    public TlsCipherSuite? NegotiatedCipherSuite => TlsSession.NegotiatedCipherSuite;
 
    /// <inheritdoc />
    public string HostName => TlsSession.TargetHostName ?? string.Empty;
 
    // The legacy Cipher/Hash/KeyExchange algorithm triples are obsolete and report their neutral values on
    // modern TLS (1.2/1.3) - exactly as SslStream does. Operators should read NegotiatedCipherSuite instead.
#pragma warning disable SYSLIB0058 // Obsolete TLS cipher algorithm enums
    /// <inheritdoc />
    public CipherAlgorithmType CipherAlgorithm => CipherAlgorithmType.None;
 
    /// <inheritdoc />
    public int CipherStrength => 0;
 
    /// <inheritdoc />
    public HashAlgorithmType HashAlgorithm => HashAlgorithmType.None;
 
    /// <inheritdoc />
    public int HashStrength => 0;
 
    /// <inheritdoc />
    public ExchangeAlgorithmType KeyExchangeAlgorithm => ExchangeAlgorithmType.None;
 
    /// <inheritdoc />
    public int KeyExchangeStrength => 0;
#pragma warning restore SYSLIB0058
 
    // ── ITlsApplicationProtocolFeature ───────────────────────────────────────
    // Published directly (this transport references Kestrel.Core), so HttpConnection.SelectProtocol can
    // negotiate HTTP/2 without the UseHttps middleware bridging a raw SslApplicationProtocol feature.
 
    /// <inheritdoc />
    public ReadOnlyMemory<byte> ApplicationProtocol => _negotiatedApplicationProtocol.Protocol;
 
    // ── IConnectionSocketFeature ─────────────────────────────────────────────
 
    /// <summary>
    /// Exposes a managed <see cref="Socket"/> for the connection.
    ///
    /// NOT IDEAL - this is a sharp edge (see the sample app README). DirectTls does not retain the managed
    /// <see cref="Socket"/> returned by accept: ownership of its fd is transferred to the
    /// <see cref="TlsSocketSession"/>'s <see cref="SafeSocketHandle"/> and driven by the TLS event pump. We
    /// reconstruct a <see cref="Socket"/> from the raw file descriptor on demand, wrapping it in a
    /// <b>non-owning</b> <see cref="SafeSocketHandle"/> so that disposing the returned socket does not close the
    /// descriptor the TLS session still uses. The returned socket is only safe for reading metadata (endpoints,
    /// socket options); performing raw send/receive on it would corrupt the TLS record stream. It exists purely
    /// for feature parity with the standard sockets transport, which surfaces the real accepted socket here.
    /// </summary>
    public Socket Socket
    {
        get
        {
            lock (_socketLock)
            {
                if (_socket is { } existing)
                {
                    return existing;
                }
 
                ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
 
                _socket = new Socket(new SafeSocketHandle((IntPtr)_connectionState.Fd, ownsHandle: false));
                return _socket;
            }
        }
    }
}