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

Enable HTTP/2 client cert authentication in WinHttpHandler #33158

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
68fd1af
ENABLE_HTTP2_PLUS_CLIENT_CERT option is set for HTTP/2 requests
alnikola Feb 21, 2020
2eb56a6
UseClientCertOnHttp2_ClientCertValid_Success test
alnikola Feb 26, 2020
923f05d
HTTP2_PLUS_CLIENT_CERT option is set before openning a connection
alnikola Mar 4, 2020
5f3bc1e
Fix Windows_NT net472 build
alnikola Mar 4, 2020
72005dc
All existing WinHttpHandler tests are enabled for net472
alnikola Mar 6, 2020
1ba5d40
Manual_CertificateOnlySentWhenValid_Success works on all frameworks
alnikola Mar 6, 2020
7ca1af2
Fix build
alnikola Mar 9, 2020
2a01fc6
Fix UseClientCertOnHttp2_OSSupportsItButCertNotSet_SuccessWithOneWayA…
alnikola Mar 9, 2020
ed354d9
All HTTP 1.1 tests enabled for WinHttpHandler on all platforms
alnikola Mar 12, 2020
90051de
Tests fixed
alnikola Mar 13, 2020
2f7792f
Unsuporrted HTTP/2 tests are disabled on Win 7 and 8.1
alnikola Mar 16, 2020
ba477cd
ConditionalFact replaced with ConditionalClass
alnikola Mar 16, 2020
03d72f7
More unsupported tests skipped
alnikola Mar 16, 2020
27f2817
Suggested changes applied
alnikola Mar 16, 2020
4d1be6c
Conditional compilation replaced with extension methods
alnikola Mar 17, 2020
8b90785
Unsupported test disabled on Win7 and Win8.1
alnikola Mar 17, 2020
4b09bdd
Enable PlatformHandler_ResponseStream_Http2_Test for all frameworks
alnikola Mar 17, 2020
6d43f8c
Skipped tests marked as Conditional
alnikola Mar 17, 2020
95bb001
Lang version bumped to 8.0
alnikola Mar 17, 2020
ffe648a
Unsupported outerloop tests disabled
alnikola Mar 18, 2020
1b306d9
Tests using RemoteExecutor fixed
alnikola Mar 18, 2020
aeca683
Unsupported tests disabled
alnikola Mar 18, 2020
35ff8e5
Comments addressed
alnikola Mar 19, 2020
378234a
ConditionalTheory and ConditionalFact are set on skippable tests for …
alnikola Mar 19, 2020
885b74e
Protocol version checks fixed
alnikola Mar 19, 2020
1cf8812
PostAsync_Cancel_CancellationTokenPassedToContent marked as Condtional
alnikola Mar 19, 2020
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 @@ -148,9 +148,11 @@ internal partial class WinHttp

public const uint WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS = 111;

public const uint WINHTTP_OPTION_ENABLE_HTTP2_PLUS_CLIENT_CERT = 161;
public const uint WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL = 133;
public const uint WINHTTP_OPTION_HTTP_PROTOCOL_USED = 134;
public const uint WINHTTP_PROTOCOL_FLAG_HTTP2 = 0x1;
public const uint WINHTTP_HTTP2_PLUS_CLIENT_CERT_FLAG = 0x1;

public const uint WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET = 114;
public const uint WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT = 115;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ protected override async Task SerializeToStreamAsync(Stream stream, TransportCon
for (int i = 0; i < _length; i++)
{
buffer[0] = (byte)i;
#if !NETFRAMEWORK
await stream.WriteAsync(buffer);
#else
await stream.WriteAsync(buffer, 0, buffer.Length);
#endif
alnikola marked this conversation as resolved.
Show resolved Hide resolved
await stream.FlushAsync();
await Task.Delay(_millisecondDelayBetweenBytes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,12 @@ private async Task ProcessRequests()

// Send a response in the JSON format that the client expects
string username = context.User.Identity.Name;
await context.Response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes($"{{\"authenticated\": \"true\", \"user\": \"{username}\" }}"));
byte[] bytes = System.Text.Encoding.UTF8.GetBytes($"{{\"authenticated\": \"true\", \"user\": \"{username}\" }}");
#if !NETFRAMEWORK
await context.Response.OutputStream.WriteAsync(bytes);
#else
await context.Response.OutputStream.WriteAsync(bytes, 0, bytes.Length);
#endif

context.Response.Close();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public class GenericLoopbackOptions
public IPAddress Address { get; set; } = IPAddress.Loopback;
public bool UseSsl { get; set; } = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback();
public SslProtocols SslProtocols { get; set; } =
#if !NETSTANDARD2_0
#if !NETSTANDARD2_0 && !NETFRAMEWORK
SslProtocols.Tls13 |
#endif
SslProtocols.Tls12;
Expand Down
9 changes: 9 additions & 0 deletions src/libraries/Common/tests/System/Net/Http/Http2Frames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -548,10 +548,19 @@ public override void WriteTo(Span<byte> buffer)
BinaryPrimitives.WriteUInt16BigEndian(buffer, checked((ushort)Origin.Length));
buffer = buffer.Slice(2);

#if !NETFRAMEWORK
Encoding.ASCII.GetBytes(Origin, buffer);
buffer = buffer.Slice(Origin.Length);

Encoding.ASCII.GetBytes(AltSvc, buffer);
#else
alnikola marked this conversation as resolved.
Show resolved Hide resolved
var tmpBuffer = Encoding.ASCII.GetBytes(Origin);
tmpBuffer.CopyTo(buffer);
buffer = buffer.Slice(Origin.Length);

tmpBuffer = Encoding.ASCII.GetBytes(AltSvc);
tmpBuffer.CopyTo(buffer);
#endif
}

public override string ToString() => $"{base.ToString()}\n{nameof(Origin)}: {Origin}\n{nameof(AltSvc)}: {AltSvc}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http.Functional.Tests;
using System.Net.Security;
using System.Net.Sockets;
Expand All @@ -28,6 +28,7 @@ public class Http2LoopbackConnection : GenericLoopbackConnection
private readonly byte[] _prefix;
public string PrefixString => Encoding.UTF8.GetString(_prefix, 0, _prefix.Length);
public bool IsInvalid => _connectionSocket == null;
public Stream Stream => _connectionStream;

public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)
{
Expand All @@ -40,6 +41,7 @@ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)

using (var cert = Configuration.Certificates.GetServerCertificate())
{
#if !NETFRAMEWORK
SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();

options.EnabledSslProtocols = httpOptions.SslProtocols;
Expand All @@ -51,9 +53,12 @@ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)

options.ServerCertificate = cert;

options.ClientCertificateRequired = false;
options.ClientCertificateRequired = httpOptions.ClientCertificateRequired;

sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).Wait();
#else
sslStream.AuthenticateAsServerAsync(cert, httpOptions.ClientCertificateRequired, httpOptions.SslProtocols, false).Wait();
scalablecory marked this conversation as resolved.
Show resolved Hide resolved
#endif
}

_connectionStream = sslStream;
Expand All @@ -64,6 +69,10 @@ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)
{
throw new Exception("Connection stream closed while attempting to read connection preface.");
}
else if (Text.Encoding.ASCII.GetString(_prefix).Contains("HTTP/1.1"))
{
throw new Exception("HTTP 1.1 request received.");
}
}

public async Task SendConnectionPrefaceAsync()
Expand All @@ -90,6 +99,7 @@ public async Task WriteFrameAsync(Frame frame)

// Read until the buffer is full
// Return false on EOF, throw on partial read
#if !NETFRAMEWORK
private async Task<bool> FillBufferAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
{
int readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
alnikola marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -112,11 +122,37 @@ public async Task WriteFrameAsync(Frame frame)

return true;
}
#else
private async Task<bool> FillBufferAsync(byte[] buffer, CancellationToken cancellationToken = default(CancellationToken))
{
int readBytes = await _connectionStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
if (readBytes == 0)
{
return false;
}

buffer = buffer.Skip(readBytes).ToArray();
while (buffer.Length > 0)
{
readBytes = await _connectionStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
if (readBytes == 0)
{
throw new Exception("Connection closed when expecting more data.");
}

buffer = buffer.Skip(readBytes).ToArray();
}

return true;
}
#endif

public async Task<Frame> ReadFrameAsync(TimeSpan timeout)
{
using CancellationTokenSource timeoutCts = new CancellationTokenSource(timeout);
return await ReadFrameAsync(timeoutCts.Token).ConfigureAwait(false);
using (CancellationTokenSource timeoutCts = new CancellationTokenSource(timeout))
{
return await ReadFrameAsync(timeoutCts.Token).ConfigureAwait(false);
}
alnikola marked this conversation as resolved.
Show resolved Hide resolved
}

private async Task<Frame> ReadFrameAsync(CancellationToken cancellationToken)
Expand Down Expand Up @@ -331,7 +367,11 @@ private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan<byte>
}
else
{
#if !NETFRAMEWORK
string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength));
#else
string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength).ToArray());
alnikola marked this conversation as resolved.
Show resolved Hide resolved
#endif
return (bytesConsumed + stringLength, value);
}
}
Expand Down
22 changes: 19 additions & 3 deletions src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,14 @@ public override async Task AcceptConnectionAsync(Func<GenericLoopbackConnection,
}
}

public static async Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<Http2LoopbackServer, Task> serverFunc, int timeout = 60_000)
public static Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<Http2LoopbackServer, Task> serverFunc, int timeout = 60_000)
{
using (var server = Http2LoopbackServer.CreateServer())
return CreateClientAndServerAsync(clientFunc, serverFunc, null, timeout);
}

public static async Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<Http2LoopbackServer, Task> serverFunc, Http2Options http2Options, int timeout = 60_000)
{
using (var server = Http2LoopbackServer.CreateServer(http2Options ?? new Http2Options()))
{
Task clientTask = clientFunc(server.Address);
Task serverTask = serverFunc(server);
Expand All @@ -197,6 +202,8 @@ public class Http2Options : GenericLoopbackOptions
{
public int ListenBacklog { get; set; } = 1;

public bool ClientCertificateRequired { get; set; }

public Http2Options()
{
UseSsl = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback();
Expand Down Expand Up @@ -237,7 +244,7 @@ public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Ta
}
}

public override Version Version => HttpVersion.Version20;
public override Version Version => HttpVersion20.Value;
}

public enum ProtocolErrors
Expand All @@ -257,4 +264,13 @@ public enum ProtocolErrors
INADEQUATE_SECURITY = 0xc,
HTTP_1_1_REQUIRED = 0xd
}

public static class HttpVersion20
{
#if !NETFRAMEWORK
public static readonly Version Value = HttpVersion.Version20;
alnikola marked this conversation as resolved.
Show resolved Hide resolved
#else
public static readonly Version Value = new Version(2, 0);
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ public void SingletonReturnsTrue()
[InlineData(SslProtocols.Tls, true)]
[InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)]
[InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)]
#if !NETFRAMEWORK
[InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)]
[InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)]
#endif
[InlineData(SslProtocols.None, false)]
[InlineData(SslProtocols.None, true)]
public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
Expand All @@ -64,7 +66,11 @@ public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol,
// restrictions on minimum TLS/SSL version
// We currently know that some platforms like Debian 10 OpenSSL
// will by default block < TLS 1.2
#if !NETFRAMEWORK
handler.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
#else
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
#endif
}

var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,12 @@ public async Task Credentials_ServerUsesWindowsAuthentication_Success(string ser
[InlineData("Negotiate")]
public async Task Credentials_ServerChallengesWithWindowsAuth_ClientSendsWindowsAuthHeader(string authScheme)
{
#if WINHTTPHANDLER_TEST
if (UseVersion >= HttpVersion.Version11)
{
throw new SkipTestException($"Test doesn't support {UseVersion} protocol.");
}
#endif
await LoopbackServerFactory.CreateClientAndServerAsync(
async uri =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,28 @@ public abstract class HttpClientHandler_Cancellation_Test : HttpClientHandlerTes
{
public HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { }

#if WINHTTPHANDLER_TEST
[ConditionalTheory]
#else
[Theory]
#endif
alnikola marked this conversation as resolved.
Show resolved Hide resolved
[InlineData(false, CancellationMode.Token)]
[InlineData(true, CancellationMode.Token)]
public async Task PostAsync_CancelDuringRequestContentSend_TaskCanceledQuickly(bool chunkedTransfer, CancellationMode mode)
{
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && chunkedTransfer)
if (LoopbackServerFactory.Version >= HttpVersion20.Value && chunkedTransfer)
{
// There is no chunked encoding in HTTP/2 and later
return;
}

#if WINHTTPHANDLER_TEST
if (UseVersion >= HttpVersion20.Value && !PlatformDetection.IsWindows10Version1607OrGreater)
{
throw new SkipTestException($"Test doesn't support {UseVersion} protocol.");
}
#endif

var serverRelease = new TaskCompletionSource<bool>();
await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
{
Expand Down Expand Up @@ -80,7 +91,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
[MemberData(nameof(OneBoolAndCancellationMode))]
public async Task GetAsync_CancelDuringResponseHeadersReceived_TaskCanceledQuickly(bool connectionClose, CancellationMode mode)
{
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && connectionClose)
if (LoopbackServerFactory.Version >= HttpVersion20.Value && connectionClose)
{
// There is no Connection header in HTTP/2 and later
return;
Expand Down Expand Up @@ -130,7 +141,7 @@ await ValidateClientCancellationAsync(async () =>
[MemberData(nameof(TwoBoolsAndCancellationMode))]
public async Task GetAsync_CancelDuringResponseBodyReceived_Buffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, CancellationMode mode)
{
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && (chunkedTransfer || connectionClose))
if (LoopbackServerFactory.Version >= HttpVersion20.Value && (chunkedTransfer || connectionClose))
{
// There is no chunked encoding or connection header in HTTP/2 and later
return;
Expand Down Expand Up @@ -182,16 +193,27 @@ await ValidateClientCancellationAsync(async () =>
}
}

#if WINHTTPHANDLER_TEST
[ConditionalTheory]
#else
[Theory]
#endif
[MemberData(nameof(ThreeBools))]
public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, bool readOrCopyToAsync)
{
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && (chunkedTransfer || connectionClose))
if (LoopbackServerFactory.Version >= HttpVersion20.Value && (chunkedTransfer || connectionClose))
{
// There is no chunked encoding or connection header in HTTP/2 and later
return;
}

#if WINHTTPHANDLER_TEST
if (UseVersion >= HttpVersion20.Value && !PlatformDetection.IsWindows10Version1607OrGreater)
{
throw new SkipTestException($"Test doesn't support {UseVersion} protocol.");
}
#endif

using (HttpClient client = CreateHttpClient())
{
client.Timeout = Timeout.InfiniteTimeSpan;
Expand Down Expand Up @@ -237,14 +259,23 @@ await ValidateClientCancellationAsync(async () =>
});
}
}

#if WINHTTPHANDLER_TEST
[ConditionalTheory]
#else
[Theory]
#endif
[InlineData(CancellationMode.CancelPendingRequests, false)]
[InlineData(CancellationMode.DisposeHttpClient, false)]
[InlineData(CancellationMode.CancelPendingRequests, true)]
[InlineData(CancellationMode.DisposeHttpClient, true)]
public async Task GetAsync_CancelPendingRequests_DoesntCancelReadAsyncOnResponseStream(CancellationMode mode, bool copyToAsync)
{
#if WINHTTPHANDLER_TEST
if (UseVersion >= HttpVersion20.Value && !PlatformDetection.IsWindows10Version1607OrGreater)
{
throw new SkipTestException($"Test doesn't support {UseVersion} protocol.");
}
#endif
using (HttpClient client = CreateHttpClient())
{
client.Timeout = Timeout.InfiniteTimeSpan;
Expand Down Expand Up @@ -312,7 +343,7 @@ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
[ConditionalFact]
public async Task MaxConnectionsPerServer_WaitingConnectionsAreCancelable()
{
if (LoopbackServerFactory.Version >= HttpVersion.Version20)
if (LoopbackServerFactory.Version >= HttpVersion20.Value)
{
// HTTP/2 does not use connection limits.
throw new SkipTestException("Not supported on HTTP/2 and later");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ await TestHelper.WhenAllCompletedOrAnyFailed(
{
_output.WriteLine(
"Client cert: {0}",
((X509Certificate2)sslStream.RemoteCertificate).GetNameInfo(X509NameType.SimpleName, false));
new X509Certificate2(sslStream.RemoteCertificate.Export(X509ContentType.Cert)).GetNameInfo(X509NameType.SimpleName, false));
Assert.Equal(cert, sslStream.RemoteCertificate);
}
else
Expand Down
Loading