Skip to content

Commit

Permalink
Configure ALPN for callback scenarios #31303 (#34242)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher authored Jul 18, 2021
1 parent 8e9af3c commit 19f1321
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, TlsHandsh
listenOptions.IsTls = true;
listenOptions.Use(next =>
{
// Set the list of protocols from listen options
callbackOptions.HttpProtocols = listenOptions.Protocols;
var middleware = new HttpsConnectionMiddleware(next, callbackOptions, loggerFactory);
return middleware.OnConnectionAsync;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ internal class HttpsConnectionMiddleware
// The following fields are only set by TlsHandshakeCallbackOptions ctor.
private readonly Func<TlsHandshakeCallbackContext, ValueTask<SslServerAuthenticationOptions>>? _tlsCallbackOptions;
private readonly object? _tlsCallbackOptionsState;
private readonly HttpProtocols _httpProtocols;

// Pool for cancellation tokens that cancel the handshake
private readonly CancellationTokenSourcePool _ctsPool = new();
Expand Down Expand Up @@ -127,6 +128,7 @@ internal HttpsConnectionMiddleware(

_tlsCallbackOptions = tlsCallbackOptions.OnConnection;
_tlsCallbackOptionsState = tlsCallbackOptions.OnConnectionState;
_httpProtocols = ValidateAndNormalizeHttpProtocols(tlsCallbackOptions.HttpProtocols, _logger);
_sslStreamFactory = s => new SslStream(s);
}

Expand Down Expand Up @@ -434,6 +436,11 @@ private static async ValueTask<SslServerAuthenticationOptions> ServerOptionsCall
var sslOptions = await middleware._tlsCallbackOptions!(callbackContext);
feature.AllowDelayedClientCertificateNegotation = callbackContext.AllowDelayedClientCertificateNegotation;

// The callback didn't set ALPN so we will.
if (sslOptions.ApplicationProtocols == null)
{
ConfigureAlpn(sslOptions, middleware._httpProtocols);
}
KestrelEventSource.Log.TlsHandshakeStart(context, sslOptions);

return sslOptions;
Expand Down
3 changes: 3 additions & 0 deletions src/Servers/Kestrel/Core/src/TlsHandshakeCallbackOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,8 @@ public TimeSpan HandshakeTimeout
_handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
}
}

// Copied from the ListenOptions to enable ALPN
internal HttpProtocols HttpProtocols { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,98 @@ void ConfigureListenOptions(ListenOptions listenOptions)
await AssertConnectionResult(stream, true, expectedBody);
}

[ConditionalFact]
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "ALPN not supported")]
public async Task ServerOptionsSelectionCallback_SetsALPN()
{
static void ConfigureListenOptions(ListenOptions listenOptions)
{
listenOptions.UseHttps((_, _, _, _) =>
ValueTask.FromResult(new SslServerAuthenticationOptions()
{
ServerCertificate = _x509Certificate2,
}), state: null);
}

await using var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory), ConfigureListenOptions);

using var connection = server.CreateConnection();
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
{
// Use a random host name to avoid the TLS session resumption cache.
TargetHost = Guid.NewGuid().ToString(),
ApplicationProtocols = new() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11, },
});
Assert.Equal(SslApplicationProtocol.Http2, stream.NegotiatedApplicationProtocol);
}

[ConditionalFact]
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "ALPN not supported")]
public async Task TlsHandshakeCallbackOptionsOverload_SetsALPN()
{
static void ConfigureListenOptions(ListenOptions listenOptions)
{
listenOptions.UseHttps(new TlsHandshakeCallbackOptions()
{
OnConnection = context =>
{
return ValueTask.FromResult(new SslServerAuthenticationOptions()
{
ServerCertificate = _x509Certificate2,
});
}
});
}

await using var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory), ConfigureListenOptions);

using var connection = server.CreateConnection();
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
{
// Use a random host name to avoid the TLS session resumption cache.
TargetHost = Guid.NewGuid().ToString(),
ApplicationProtocols = new() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11, },
});
Assert.Equal(SslApplicationProtocol.Http2, stream.NegotiatedApplicationProtocol);
}

[ConditionalFact]
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "ALPN not supported")]
public async Task TlsHandshakeCallbackOptionsOverload_EmptyAlpnList_DisablesAlpn()
{
static void ConfigureListenOptions(ListenOptions listenOptions)
{
listenOptions.UseHttps(new TlsHandshakeCallbackOptions()
{
OnConnection = context =>
{
return ValueTask.FromResult(new SslServerAuthenticationOptions()
{
ServerCertificate = _x509Certificate2,
ApplicationProtocols = new(),
});
}
});
}

await using var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory), ConfigureListenOptions);

using var connection = server.CreateConnection();
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
{
// Use a random host name to avoid the TLS session resumption cache.
TargetHost = Guid.NewGuid().ToString(),
ApplicationProtocols = new() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11, },
});
Assert.Equal(default, stream.NegotiatedApplicationProtocol);
}

[ConditionalFact]
[OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Not supported yet.")]
public async Task CanRenegotiateForClientCertificateOnPostIfDrained()
Expand Down

0 comments on commit 19f1321

Please sign in to comment.