Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 0 additions & 3 deletions src/libraries/Common/tests/System/Net/Configuration.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ public static partial class Http
public static string TLSv11RemoteServer => GetValue("DOTNET_TEST_HTTPHOST_TLS11", "https://www.ssllabs.com:10302/");
public static string TLSv12RemoteServer => GetValue("DOTNET_TEST_HTTPHOST_TLS12", "https://www.ssllabs.com:10303/");

public static string ExpiredCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_EXPIREDCERT", "https://expired.badssl.com/");
public static string WrongHostNameCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_WRONGHOSTNAME", "https://wrong.host.badssl.com/");
public static string SelfSignedCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_SELFSIGNEDCERT", "https://self-signed.badssl.com/");
public static string RevokedCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_REVOKEDCERT", "https://revoked.badssl.com/");

public static string EchoClientCertificateRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_ECHOCLIENTCERT", "https://corefx-net-tls.azurewebsites.net/EchoClientCertificate.ashx");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,19 @@
using System.Net.Security;
using System.Net.Test.Common;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.DotNet.XUnitExtensions;
using Xunit;
using Xunit.Abstractions;

namespace System.Net.Http.Functional.Tests
{
using Configuration = System.Net.Test.Common.Configuration;

#if WINHTTPHANDLER_TEST
using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
#endif

public abstract class HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandlerTestBase
{
private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater);

public HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { }

#if !WINHTTPHANDLER_TEST
Expand Down Expand Up @@ -86,22 +82,30 @@ await TestHelper.WhenAllCompletedOrAnyFailed(
}
}

public static readonly object[][] InvalidCertificateServers =
[Theory]
[InlineData(true, false)]
[InlineData(false, false)]
[InlineData(false, true)]
Comment thread
rzikm marked this conversation as resolved.
[InlineData(true, true)]
public async Task InvalidCertificates_CertificateValidationDisabled_Succeeds(bool expired, bool wrongHostName)
{
new object[] { Configuration.Http.ExpiredCertRemoteServer },
new object[] { Configuration.Http.SelfSignedCertRemoteServer },
new object[] { Configuration.Http.WrongHostNameCertRemoteServer },
};
using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(
name: wrongHostName ? "not-localhost" : "localhost", expired: expired);

[OuterLoop]
[ConditionalTheory(typeof(HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test), nameof(ClientSupportsDHECipherSuites))]
[MemberData(nameof(InvalidCertificateServers))]
public async Task InvalidCertificateServers_CertificateValidationDisabled_Succeeds(string url)
{
using (HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: true))
using (HttpClient client = CreateHttpClient(handler))
{
(await client.GetAsync(url)).Dispose();
await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
{
using HttpResponseMessage response = await client.GetAsync(new UriBuilder(uri) { Host = "localhost" }.Uri);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}, server => server.HandleRequestAsync(),
options: new GenericLoopbackOptions
{
UseSsl = true,
Certificate = certificate,
SslProtocols = SslProtocols.None,
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
#if WINHTTPHANDLER_TEST
using System.ComponentModel;
#endif
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Net.Test.Common;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
Expand All @@ -26,7 +31,9 @@ namespace System.Net.Http.Functional.Tests

public abstract partial class HttpClientHandler_ServerCertificates_Test : HttpClientHandlerTestBase
{
#if WINHTTPHANDLER_TEST
private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater);
#endif

public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { }

Expand Down Expand Up @@ -211,25 +218,86 @@ public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseE
}
}

public static readonly object[][] CertificateValidationServers =
[Theory]
[InlineData(true, false)]
[InlineData(false, false)]
[InlineData(false, true)]
public async Task NoCallback_BadCertificate_ThrowsException(bool expired, bool wrongHostName)
{
new object[] { Configuration.Http.ExpiredCertRemoteServer },
new object[] { Configuration.Http.SelfSignedCertRemoteServer },
new object[] { Configuration.Http.WrongHostNameCertRemoteServer },
};
using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(
name: wrongHostName ? "not-localhost" : "localhost", expired: expired);

[OuterLoop("Uses external servers")]
[ConditionalTheory(typeof(HttpClientHandler_ServerCertificates_Test), nameof(ClientSupportsDHECipherSuites))]
[MemberData(nameof(CertificateValidationServers))]
public async Task NoCallback_BadCertificate_ThrowsException(string url)
{
using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false))
using (HttpClient client = CreateHttpClient(handler))
{
await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName);

await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
HttpRequestException exception = await Assert.ThrowsAsync<HttpRequestException>(
() => client.GetAsync(new UriBuilder(uri) { Host = "localhost" }.Uri));
#if WINHTTPHANDLER_TEST
const int ErrorWinHttpSecureFailure = 12175;
Assert.Equal(ErrorWinHttpSecureFailure, Assert.IsAssignableFrom<Win32Exception>(exception.InnerException).NativeErrorCode);
#else
if (customChainPolicy)
{
Assert.Equal(HttpRequestError.SecureConnectionError, exception.HttpRequestError);
Assert.IsType<AuthenticationException>(exception.InnerException);
}
#endif
}, async server =>
{
try
{
if (customChainPolicy || IsWinHttpHandler)
{
await server.AcceptConnectionAsync(_ => Task.CompletedTask);
}
else
{
// Native handlers may report generic errors, so acceptance must produce a successful response.
await server.HandleRequestAsync();
}
}
catch (Exception e) when (e is AuthenticationException or IOException)
{
_output.WriteLine($"Expected certificate validation failure: {e}");
}
},
options: new LoopbackServer.Options
{
UseSsl = true,
Certificate = certificate,
SslProtocols = SslProtocols.None,
});
}
}

[Fact]
public async Task LoopbackServer_AuthenticationFailure_DisposesStream()
{
bool streamDisposed = false;
var expectedException = new IOException("Simulated TLS transport failure.");
using var stream = new DelegateStream(
canReadFunc: () => true,
canWriteFunc: () => true,
readAsyncFunc: (_, _, _, _) => Task.FromException<int>(expectedException),
writeAsyncFunc: (_, _, _, _) => Task.CompletedTask,
disposeFunc: disposing => streamDisposed |= disposing);
using var socket = new SocketWrapper(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp));
using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate();
var options = new LoopbackServer.Options { UseSsl = true, Certificate = certificate };
#if !NETFRAMEWORK
options.CertificateContext = SslStreamCertificateContext.Create(certificate, null);
#endif

IOException exception = await Assert.ThrowsAsync<IOException>(() => LoopbackServer.Connection.CreateAsync(socket, stream, options));
Assert.Same(expectedException, exception.GetBaseException());
Assert.True(streamDisposed);
}

#if WINHTTPHANDLER_TEST
[OuterLoop("Uses external servers")]
[ConditionalFact(typeof(HttpClientHandler_ServerCertificates_Test), nameof(ClientSupportsDHECipherSuites))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/106634", typeof(PlatformDetection), nameof(PlatformDetection.IsAlpine))]
Expand All @@ -255,70 +323,85 @@ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails()
await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Configuration.Http.RevokedCertRemoteServer));
}
}
#endif

public static readonly object[][] CertificateValidationServersAndExpectedPolicies =
[Theory]
[InlineData(true, false, SslPolicyErrors.RemoteCertificateChainErrors)]
[InlineData(false, false, SslPolicyErrors.RemoteCertificateChainErrors)]
[InlineData(false, true, SslPolicyErrors.RemoteCertificateNameMismatch)]
public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(bool expired, bool wrongHostName, SslPolicyErrors expectedErrors)
{
new object[] { Configuration.Http.ExpiredCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors },
new object[] { Configuration.Http.WrongHostNameCertRemoteServer , SslPolicyErrors.RemoteCertificateNameMismatch},
};
using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(
name: wrongHostName ? "not-localhost" : "localhost", expired: expired);

private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, string useHttp2String, SslPolicyErrors expectedErrors)
{
HttpClientHandler handler = CreateHttpClientHandler(useHttp2String, allowTlsResume: false);
using (HttpClient client = CreateHttpClient(handler, useHttp2String))
using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false))
using (HttpClient client = CreateHttpClient(handler))
{
bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName);
X509ChainStatusFlags expectedChainStatus = expired ? X509ChainStatusFlags.NotTimeValid :
wrongHostName ? X509ChainStatusFlags.NoError : X509ChainStatusFlags.UntrustedRoot;

if (!customChainPolicy)
{
// Native handlers cannot trust the generated root without changing the OS certificate store.
expectedErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
expectedChainStatus |= X509ChainStatusFlags.UntrustedRoot;
}

string expectedThumbprint = certificate.Thumbprint;
bool callbackCalled = false;

handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
{
// https://github.com/dotnet/corefx/issues/21922#issuecomment-315555237
X509ChainStatusFlags flags = chain.ChainStatus.Aggregate(X509ChainStatusFlags.NoError, (cur, status) => cur | status.Status);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
flags == X509ChainStatusFlags.RevocationStatusUnknown)
{
expectedErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
}

callbackCalled = true;
Assert.NotNull(request);
Assert.NotNull(cert);
Assert.NotNull(chain);
Assert.Equal(expectedThumbprint, cert.Thumbprint);
Assert.Equal(expectedErrors, errors);
Assert.Equal(expectedChainStatus, chain.ChainStatus.Aggregate(
X509ChainStatusFlags.NoError, (flags, status) => flags | status.Status));
return true;
};

using (HttpResponseMessage response = await client.GetAsync(url))
await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
{
using HttpResponseMessage response = await client.GetAsync(new UriBuilder(uri) { Host = "localhost" }.Uri);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}, server => server.HandleRequestAsync(),
options: new GenericLoopbackOptions
{
UseSsl = true,
Certificate = certificate,
SslProtocols = SslProtocols.None,
});

Assert.True(callbackCalled);
}
}

[OuterLoop("Uses external servers")]
[Theory]
[MemberData(nameof(CertificateValidationServersAndExpectedPolicies))]
public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, SslPolicyErrors expectedErrors)
private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X509Certificate2 certificate, bool trustCertificate)
{
const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321);

if (!ClientSupportsDHECipherSuites)
#if !WINHTTPHANDLER_TEST
if (GetUnderlyingSocketsHttpHandler(handler) is SocketsHttpHandler socketsHandler)
{
return;
}
var policy = new X509ChainPolicy
{
TrustMode = X509ChainTrustMode.CustomRootTrust,
RevocationMode = X509RevocationMode.NoCheck,
DisableCertificateDownloads = true,
};

try
{
await UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(url, UseVersion.ToString(), expectedErrors);
}
catch (HttpRequestException e) when (e.InnerException?.GetType().Name == "WinHttpException" &&
e.InnerException.HResult == SEC_E_BUFFER_TOO_SMALL &&
!PlatformDetection.IsWindows10Version1607OrGreater)
{
// Testing on old Windows versions can hit https://github.com/dotnet/runtime/issues/17005
// Ignore SEC_E_BUFFER_TOO_SMALL error on such cases.
if (trustCertificate)
{
policy.CustomTrustStore.Add(certificate);
}

socketsHandler.SslOptions.CertificateChainPolicy = policy;
return true;
}
#endif
return false;
}

[Theory]
Expand Down
39 changes: 24 additions & 15 deletions src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -481,26 +481,35 @@ public static async Task<Connection> CreateAsync(SocketWrapper socket, Stream st
if (httpOptions.UseSsl)
{
var sslStream = new SslStream(stream, false, delegate { return true; });
#if !NETFRAMEWORK
SslServerAuthenticationOptions sslOptions = new SslServerAuthenticationOptions()
try
{
AllowTlsResume = httpOptions.AllowTlsResume,
EnabledSslProtocols = httpOptions.SslProtocols,
ServerCertificateContext = httpOptions.CertificateContext ?? SslStreamCertificateContext.Create(Configuration.Certificates.GetServerCertificate(), null),
ClientCertificateRequired = true,
};
#if !NETFRAMEWORK
SslServerAuthenticationOptions sslOptions = new SslServerAuthenticationOptions()
{
AllowTlsResume = httpOptions.AllowTlsResume,
EnabledSslProtocols = httpOptions.SslProtocols,
ServerCertificateContext = httpOptions.CertificateContext ?? SslStreamCertificateContext.Create(Configuration.Certificates.GetServerCertificate(), null),
ClientCertificateRequired = true,
};

await sslStream.AuthenticateAsServerAsync(sslOptions, default).ConfigureAwait(false);
await sslStream.AuthenticateAsServerAsync(sslOptions, default).ConfigureAwait(false);
#else
using (X509Certificate2 cert = httpOptions.Certificate ?? Configuration.Certificates.GetServerCertificate())
using (X509Certificate2 cert = httpOptions.Certificate ?? Configuration.Certificates.GetServerCertificate())
{
await sslStream.AuthenticateAsServerAsync(
cert,
clientCertificateRequired: true, // allowed but not required
enabledSslProtocols: httpOptions.SslProtocols,
checkCertificateRevocation: false).ConfigureAwait(false);
}
#endif
}
catch
{
await sslStream.AuthenticateAsServerAsync(
cert,
clientCertificateRequired: true, // allowed but not required
enabledSslProtocols: httpOptions.SslProtocols,
checkCertificateRevocation: false).ConfigureAwait(false);
sslStream.Dispose();
throw;
}
#endif

stream = sslStream;
}

Expand Down
Loading
Loading