Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure ALPN for callback scenarios #34242

Merged
merged 2 commits into from
Jul 18, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,65 @@ 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,
Copy link
Member

@halter73 halter73 Jul 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test case confirming that adding

Suggested change
ServerCertificate = _x509Certificate2,
ServerCertificate = _x509Certificate2,
ApplicationProtocols = new(),

disables ALPN? Assuming it does ofc.

Copy link
Member Author

@Tratcher Tratcher Jul 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It worked on Windows but fails on Ubuntu 😢. I'll file a runtime bug for that. dotnet/runtime#55447

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in rc1 (main), dotnet/runtime#55772. Waiting for a runtime update.

}), 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 | OperatingSystems.Linux, SkipReason = "Not supported yet.")]
public async Task CanRenegotiateForClientCertificateOnPostIfDrained()
Expand Down