dotnet / dotnet/runtime

[API Proposal]: Make SslStream's NegotiatedApplicationProtocol and all overloads of AuthenticateAs* virtual to allow for pluggable TLS implementations

Open
#132,848 4 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-System.Net.Security
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

Performing mTLS with HSMs, smart cards, or cloud key stores (e.g., Azure Key Vault) where private keys are non-extractable is currently difficult and platform-dependent in .NET:

- Windows (Schannel): Schannel does not coordinate TLS cipher suite negotiation with hardware key capabilities in many cases.

- Linux (OpenSSL): While an OpenSSL provider can technically be loaded via P/Invoke to use a custom provider, it is non-portable (OpenSSL is only used by the runtime on Linux), very complex, and managing OSSL_LIB_CTX teardowns safely in .NET is nearly impossible.

Consequently, using third-party TLS engines (e.g., BouncyCastle, BoringSSL, Rustls, or GnuTLS) is a necessity for these scenarios.

Since https://github.com/dotnet/runtime/issues/42455 was resolved, it has become possible to subclass SslStream to plug in custom TLS implementations to use in http requests using SocketsHttpHandler and HttpClient when using HTTP 1.1. It is only possible in HTTP 1.1 because NegotiatedApplicationProtocol is not virtual, and will throw if read, such as happens in HttpConnectionPool.Http2. Additionally, the overloads of AuthenticateAs* that take SslClientAuthenticationOptions are also not virtual, which is not a problem for the HttpClient use case, but prevent a full custom implementation of SslStream.

Other AuthenticateAs* methods are already virtual, and call this non-virtual, but public overload.

### API Proposal

```csharp
namespace System.Net.Security;

public partial class SslStream
{
public virtual SslApplicationProtocol NegotiatedApplicationProtocol { /* ... */ }
public virtual void AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions) { /* ... */ }
public virtual Task AuthenticateAsClientAsync(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken = default) { /* ... */ }
public virtual void AuthenticateAsServer(SslServerAuthenticationOptions sslServerAuthenticationOptions) { /* ... */ }
public virtual Task AuthenticateAsServerAsync(SslServerAuthenticationOptions sslServerAuthenticationOptions, CancellationToken cancellationToken = default) { /* ... */ }
}
```

### API Usage

```csharp
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, cancellationToken) =>
{
var rawStream = await ConnectSocketAsync(context.DnsEndPoint, cancellationToken);
var customStream = new CustomHsmTlsStream(rawStream);
await customStream.AuthenticateAsClientAsync(/* ... */, cancellationToken);

return customStream;
}
};
```

### Alternative Designs

A perhaps better, but more difficult approach would be to extract the relevant portions of SslStream for SocketsHttpHandler use to an interface that SslStream would implement, and change the SslStream casts in HttpConnectionPool to use this new interface instead. This would help custom TLS implementations to not instantiate heavy SslStream internal state, which will be unused, and to not need to implement irrelevant methods, such as AuthenticateAsServer when used for a client.

### Risks

Binary/Source Compatibility: 100% compatible. Converting non-virtual members to virtual in C# is a non-breaking change.

Execution Risk: Minimal. Existing SslStream callers experience zero changes; custom streams gain the ability to participate in HTTP/2 negotiation and options-based handshakes.

Contributor guide

Open the contributing guide

Research direction

Start by locating SslStream's existing virtual AuthenticateAs* methods and the members referenced by HttpConnectionPool, then review how HTTP/2 reads NegotiatedApplicationProtocol. Done means the proposed options-based overloads and property are virtual, allowing a custom SslStream to participate in SocketsHttpHandler HTTP/2 connections without the current exception.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend-api-design, networking, security
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.