Skip to content

Commit 1e88bfd

Browse files
committed
Change to strongly typed TLS connection options
1 parent bc50aa6 commit 1e88bfd

File tree

10 files changed

+168
-79
lines changed

10 files changed

+168
-79
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext
3+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.CancellationToken.get -> System.Threading.CancellationToken
4+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.CancellationToken.set -> void
5+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.ClientHelloInfo.get -> System.Net.Security.SslClientHelloInfo
6+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.ClientHelloInfo.set -> void
7+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.Connection.get -> Microsoft.AspNetCore.Connections.ConnectionContext!
8+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.Connection.set -> void
9+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.State.get -> object?
10+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.State.set -> void
11+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.TlsConnectionCallbackContext() -> void
12+
Microsoft.AspNetCore.Connections.TlsConnectionOptions
13+
Microsoft.AspNetCore.Connections.TlsConnectionOptions.ApplicationProtocols.get -> System.Collections.Generic.List<System.Net.Security.SslApplicationProtocol>!
14+
Microsoft.AspNetCore.Connections.TlsConnectionOptions.ApplicationProtocols.set -> void
15+
Microsoft.AspNetCore.Connections.TlsConnectionOptions.OnConnection.get -> System.Func<Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext!, System.Threading.Tasks.ValueTask<System.Net.Security.SslServerAuthenticationOptions!>>!
16+
Microsoft.AspNetCore.Connections.TlsConnectionOptions.OnConnection.set -> void
17+
Microsoft.AspNetCore.Connections.TlsConnectionOptions.OnConnectionState.get -> object?
18+
Microsoft.AspNetCore.Connections.TlsConnectionOptions.OnConnectionState.set -> void
19+
Microsoft.AspNetCore.Connections.TlsConnectionOptions.TlsConnectionOptions() -> void
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if NET7_0_OR_GREATER
5+
using System.Net.Security;
6+
using System.Threading;
7+
using Microsoft.AspNetCore.Connections;
8+
using Microsoft.AspNetCore.Http.Features;
9+
10+
namespace Microsoft.AspNetCore.Connections;
11+
12+
/// <summary>
13+
/// Per connection state used to determine the TLS options.
14+
/// </summary>
15+
public class TlsConnectionCallbackContext
16+
{
17+
/// <summary>
18+
/// Information from the Client Hello message.
19+
/// </summary>
20+
public SslClientHelloInfo ClientHelloInfo { get; set; }
21+
22+
/// <summary>
23+
/// The information that was passed when registering the callback.
24+
/// </summary>
25+
public object? State { get; set; }
26+
27+
/// <summary>
28+
/// The token to monitor for cancellation requests.
29+
/// </summary>
30+
public CancellationToken CancellationToken { get; set; }
31+
32+
/// <summary>
33+
/// Information about an individual connection.
34+
/// </summary>
35+
public ConnectionContext Connection { get; set; } = default!;
36+
}
37+
#endif
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if NET7_0_OR_GREATER
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Net.Security;
8+
using System.Threading.Tasks;
9+
10+
namespace Microsoft.AspNetCore.Connections;
11+
12+
/// <summary>
13+
/// Options used to configure a per connection callback for TLS configuration.
14+
/// </summary>
15+
public class TlsConnectionOptions
16+
{
17+
/// <summary>
18+
/// The callback to invoke per connection. This property is required.
19+
/// </summary>
20+
public Func<TlsConnectionCallbackContext, ValueTask<SslServerAuthenticationOptions>> OnConnection { get; set; } = default!;
21+
22+
/// <summary>
23+
/// Optional application state to flow to the <see cref="OnConnection"/> callback.
24+
/// </summary>
25+
public object? OnConnectionState { get; set; }
26+
27+
public List<SslApplicationProtocol> ApplicationProtocols { get; set; } = default!;
28+
}
29+
#endif

src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Net.Security;
99
using Microsoft.AspNetCore.Connections;
1010
using Microsoft.AspNetCore.Http.Features;
11+
using Microsoft.AspNetCore.Server.Kestrel.Https;
1112
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
1213

1314
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -58,22 +59,31 @@ public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDe
5859
// The transport will check if the feature is missing.
5960
if (listenOptions.HttpsOptions != null)
6061
{
61-
features.Set(HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions));
62+
var sslServerAuthenticationOptions = HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions);
63+
features.Set(new TlsConnectionOptions
64+
{
65+
ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols ?? new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
66+
OnConnection = context => ValueTask.FromResult(sslServerAuthenticationOptions),
67+
OnConnectionState = null,
68+
});
6269
}
6370
if (listenOptions.HttpsCallbackOptions != null)
6471
{
65-
Func<SslClientHelloInfo, CancellationToken, ValueTask<SslServerAuthenticationOptions>> callback = (helloInfo, cancellationToken) =>
72+
features.Set(new TlsConnectionOptions
6673
{
67-
return listenOptions.HttpsCallbackOptions.OnConnection(new Https.TlsHandshakeCallbackContext
74+
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
75+
OnConnection = context =>
6876
{
69-
ClientHelloInfo = helloInfo,
70-
CancellationToken = cancellationToken,
71-
State = listenOptions.HttpsCallbackOptions.OnConnectionState
72-
});
73-
};
74-
75-
features.Set(callback);
76-
features.Set<IList<SslApplicationProtocol>>(new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 });
77+
return listenOptions.HttpsCallbackOptions.OnConnection(new TlsHandshakeCallbackContext
78+
{
79+
ClientHelloInfo = context.ClientHelloInfo,
80+
CancellationToken = context.CancellationToken,
81+
State = context.State,
82+
Connection = context.Connection,
83+
});
84+
},
85+
OnConnectionState = listenOptions.HttpsCallbackOptions.OnConnectionState,
86+
});
7787
}
7888

7989
var transport = await _multiplexedTransportFactory.BindAsync(endPoint, features, cancellationToken).ConfigureAwait(false);

src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
1616
internal sealed class QuicConnectionListener : IMultiplexedConnectionListener, IAsyncDisposable
1717
{
1818
private readonly ILogger _log;
19-
private readonly List<SslApplicationProtocol> _protocols;
20-
private bool _disposed;
19+
private readonly TlsConnectionOptions _tlsConnectionOptions;
2120
private readonly QuicTransportContext _context;
22-
private QuicListener? _listener;
2321
private readonly QuicListenerOptions _quicListenerOptions;
22+
private bool _disposed;
23+
private QuicListener? _listener;
2424

2525
public QuicConnectionListener(
2626
QuicTransportOptions options,
2727
ILogger log,
2828
EndPoint endpoint,
29-
List<SslApplicationProtocol> protocols,
30-
Func<SslClientHelloInfo, CancellationToken, ValueTask<SslServerAuthenticationOptions>> sslServerAuthenticationOptionsCallback)
29+
TlsConnectionOptions tlsConnectionOptions)
3130
{
3231
if (!QuicListener.IsSupported)
3332
{
@@ -39,27 +38,33 @@ public QuicConnectionListener(
3938
throw new InvalidOperationException($"QUIC doesn't support listening on the configured endpoint type. Expected {nameof(IPEndPoint)} but got {endpoint.GetType().Name}.");
4039
}
4140

42-
if (protocols.Count == 0)
41+
if (tlsConnectionOptions.ApplicationProtocols.Count == 0)
4342
{
4443
throw new InvalidOperationException("No application protocols specified.");
4544
}
4645

4746
_log = log;
48-
_protocols = protocols;
47+
_tlsConnectionOptions = tlsConnectionOptions;
4948
_context = new QuicTransportContext(_log, options);
5049
_quicListenerOptions = new QuicListenerOptions
5150
{
52-
ApplicationProtocols = _protocols,
51+
ApplicationProtocols = _tlsConnectionOptions.ApplicationProtocols,
5352
ListenEndPoint = listenEndPoint,
5453
ListenBacklog = options.Backlog,
5554
ConnectionOptionsCallback = async (connection, helloInfo, cancellationToken) =>
5655
{
57-
var serverAuthenticationOptions = await sslServerAuthenticationOptionsCallback(helloInfo, cancellationToken);
56+
var serverAuthenticationOptions = await _tlsConnectionOptions.OnConnection(new TlsConnectionCallbackContext
57+
{
58+
CancellationToken = cancellationToken,
59+
ClientHelloInfo = helloInfo,
60+
State = _tlsConnectionOptions.OnConnectionState,
61+
Connection = null!,
62+
});
5863

5964
// If the callback didn't set protocols then use the listener's list of protocols.
6065
if (serverAuthenticationOptions.ApplicationProtocols == null)
6166
{
62-
serverAuthenticationOptions.ApplicationProtocols = _protocols;
67+
serverAuthenticationOptions.ApplicationProtocols = _tlsConnectionOptions.ApplicationProtocols;
6368
}
6469

6570
// If the SslServerAuthenticationOptions doesn't have a cert or protocols then the

src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,33 +51,18 @@ public async ValueTask<IMultiplexedConnectionListener> BindAsync(EndPoint endpoi
5151
throw new ArgumentNullException(nameof(endpoint));
5252
}
5353

54-
var sslServerAuthenticationOptionsCallback = features?.Get<Func<SslClientHelloInfo, CancellationToken, ValueTask<SslServerAuthenticationOptions>>>();
55-
var applicationProtocols = features?.Get<IList<SslApplicationProtocol>>();
54+
var tlsConnectionOptions = features?.Get<TlsConnectionOptions>();
5655

57-
// As a fallback, check if SslServerAuthenticationOptions is specified and use it instead.
58-
if (sslServerAuthenticationOptionsCallback == null)
59-
{
60-
var sslServerAuthenticationOptions = features?.Get<SslServerAuthenticationOptions>();
61-
if (sslServerAuthenticationOptions != null)
62-
{
63-
sslServerAuthenticationOptionsCallback = (helloInfo, cancellationToken) =>
64-
{
65-
return ValueTask.FromResult(sslServerAuthenticationOptions);
66-
};
67-
applicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols;
68-
}
69-
}
70-
71-
if (sslServerAuthenticationOptionsCallback == null)
56+
if (tlsConnectionOptions == null)
7257
{
7358
throw new InvalidOperationException("Couldn't find HTTPS configuration for QUIC transport.");
7459
}
75-
if (applicationProtocols == null || applicationProtocols.Count == 0)
60+
if (tlsConnectionOptions.ApplicationProtocols == null || tlsConnectionOptions.ApplicationProtocols.Count == 0)
7661
{
7762
throw new InvalidOperationException("No application protocols specified for QUIC transport.");
7863
}
7964

80-
var transport = new QuicConnectionListener(_options, _log, endpoint, applicationProtocols.ToList(), sslServerAuthenticationOptionsCallback);
65+
var transport = new QuicConnectionListener(_options, _log, endpoint, tlsConnectionOptions);
8166
await transport.CreateListenerAsync();
8267

8368
return transport;

src/Servers/Kestrel/Transport.Quic/test/QuicConnectionListenerTests.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Security.Cryptography.X509Certificates;
99
using System.Text;
1010
using System.Threading.Tasks;
11+
using Microsoft.AspNetCore.Connections;
1112
using Microsoft.AspNetCore.Http.Features;
1213
using Microsoft.AspNetCore.Testing;
1314
using Xunit;
@@ -115,12 +116,15 @@ public async Task AcceptAsync_NoCertificateOrApplicationProtocol_Log()
115116
{
116117
// Arrange
117118
await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(
118-
applicationProtocols: new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
119-
sslServerAuthenticationOptionsCallback: (helloInfo, cancellationToken) =>
119+
new TlsConnectionOptions
120120
{
121-
var options = new SslServerAuthenticationOptions();
122-
options.ApplicationProtocols = new List<SslApplicationProtocol>();
123-
return ValueTask.FromResult(options);
121+
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
122+
OnConnection = context =>
123+
{
124+
var options = new SslServerAuthenticationOptions();
125+
options.ApplicationProtocols = new List<SslApplicationProtocol>();
126+
return ValueTask.FromResult(options);
127+
}
124128
},
125129
LoggerFactory);
126130

@@ -142,13 +146,15 @@ public async Task AcceptAsync_NoApplicationProtocolsInCallback_DefaultToConnecti
142146
{
143147
// Arrange
144148
await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(
145-
applicationProtocols: new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
146-
sslServerAuthenticationOptionsCallback: (helloInfo, cancellationToken) =>
149+
new TlsConnectionOptions
147150
{
148-
var options = new SslServerAuthenticationOptions();
149-
options.ServerCertificate = TestResources.GetTestCertificate();
150-
151-
return ValueTask.FromResult(options);
151+
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
152+
OnConnection = context =>
153+
{
154+
var options = new SslServerAuthenticationOptions();
155+
options.ServerCertificate = TestResources.GetTestCertificate();
156+
return ValueTask.FromResult(options);
157+
}
152158
},
153159
LoggerFactory);
154160

src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,15 @@ public static async Task<QuicConnectionListener> CreateConnectionListenerFactory
5252
return (QuicConnectionListener)await transportFactory.BindAsync(endpoint, features, cancellationToken: CancellationToken.None);
5353
}
5454

55-
public static async Task<QuicConnectionListener> CreateConnectionListenerFactory(List<SslApplicationProtocol> applicationProtocols, Func<SslClientHelloInfo, CancellationToken, ValueTask<SslServerAuthenticationOptions>> sslServerAuthenticationOptionsCallback, ILoggerFactory loggerFactory = null, ISystemClock systemClock = null)
55+
public static async Task<QuicConnectionListener> CreateConnectionListenerFactory(TlsConnectionOptions tlsConnectionOptions, ILoggerFactory loggerFactory = null, ISystemClock systemClock = null)
5656
{
5757
var transportFactory = CreateTransportFactory(loggerFactory, systemClock);
5858

5959
// Use ephemeral port 0. OS will assign unused port.
6060
var endpoint = new IPEndPoint(IPAddress.Loopback, 0);
6161

6262
var features = new FeatureCollection();
63-
features.Set(sslServerAuthenticationOptionsCallback);
64-
features.Set<IList<SslApplicationProtocol>>(applicationProtocols);
63+
features.Set(tlsConnectionOptions);
6564
return (QuicConnectionListener)await transportFactory.BindAsync(endpoint, features, cancellationToken: CancellationToken.None);
6665
}
6766

@@ -76,7 +75,11 @@ public static FeatureCollection CreateBindAsyncFeatures(bool clientCertificateRe
7675
sslServerAuthenticationOptions.ClientCertificateRequired = clientCertificateRequired;
7776

7877
var features = new FeatureCollection();
79-
features.Set(sslServerAuthenticationOptions);
78+
features.Set(new TlsConnectionOptions
79+
{
80+
ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols,
81+
OnConnection = context => ValueTask.FromResult(sslServerAuthenticationOptions)
82+
});
8083

8184
return features;
8285
}

src/Servers/Kestrel/Transport.Quic/test/QuicTransportFactoryTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Security.Cryptography.X509Certificates;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Microsoft.AspNetCore.Connections;
1011
using Microsoft.AspNetCore.Http.Features;
1112
using Microsoft.AspNetCore.Server.Kestrel.Https;
1213
using Microsoft.AspNetCore.Testing;
@@ -41,7 +42,7 @@ public async Task BindAsync_NoApplicationProtocols_Error()
4142
var quicTransportOptions = new QuicTransportOptions();
4243
var quicTransportFactory = new QuicTransportFactory(NullLoggerFactory.Instance, Options.Create(quicTransportOptions));
4344
var features = new FeatureCollection();
44-
features.Set(new SslServerAuthenticationOptions());
45+
features.Set(new TlsConnectionOptions());
4546

4647
// Act
4748
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => quicTransportFactory.BindAsync(new IPEndPoint(0, 0), features: features, cancellationToken: CancellationToken.None).AsTask()).DefaultTimeout();
@@ -58,7 +59,7 @@ public async Task BindAsync_SslServerAuthenticationOptions_Success()
5859
var quicTransportOptions = new QuicTransportOptions();
5960
var quicTransportFactory = new QuicTransportFactory(NullLoggerFactory.Instance, Options.Create(quicTransportOptions));
6061
var features = new FeatureCollection();
61-
features.Set(new SslServerAuthenticationOptions
62+
features.Set(new TlsConnectionOptions
6263
{
6364
ApplicationProtocols = new List<SslApplicationProtocol>
6465
{

0 commit comments

Comments
 (0)