Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 4be4b45

Browse files
committed
Special-case DangerousAcceptAnyServerCertificateValidator on Unix
In situations where setting ServerCertificateValidationCallback would result in a PlatformNotSupportedException, if it was set to DangerousAcceptAnyServerCertificateValidator instead simply disable CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST. This enables a common case of `delegate { return true; }` to work on libcurls with non-SSL backends, such as the default on macOS.
1 parent bb1a7d3 commit 4be4b45

File tree

6 files changed

+149
-84
lines changed

6 files changed

+149
-84
lines changed

src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.Initialization.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ static HttpInitializer()
2626
#if !SYSNETHTTP_NO_OPENSSL
2727
string opensslVersion = Interop.Http.GetSslVersionDescription();
2828
if (string.IsNullOrEmpty(opensslVersion) ||
29-
opensslVersion.IndexOf("openssl/1.0", StringComparison.OrdinalIgnoreCase) != -1)
29+
opensslVersion.IndexOf(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) != -1)
3030
{
3131
// CURL uses OpenSSL which me must initialize first to guarantee thread-safety
3232
// Only initialize for OpenSSL/1.0, any newer versions may have mismatched

src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.VersionInfo.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,8 @@ internal enum CurlFeatures : int
4646

4747
[DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_GetSslVersionDescription")]
4848
internal static extern string GetSslVersionDescription();
49+
50+
internal const string OpenSsl10Description = "openssl/1.0";
51+
internal const string SecureTransportDescription = "SecureTransport";
4952
}
5053
}

src/System.Net.Http/src/System/Net/Http/OSX/CurlHandler.SslProvider.cs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption cli
3636
CurlSslVersionDescription));
3737
}
3838

39+
// Revocation checking is always on for darwinssl (SecureTransport).
40+
// If any other backend is used and revocation is requested, we can't guarantee
41+
// that assertion.
42+
if (easy._handler.CheckCertificateRevocationList &&
43+
!CurlSslVersionDescription.Equals(Interop.Http.SecureTransportDescription))
44+
{
45+
throw new PlatformNotSupportedException(
46+
SR.Format(
47+
SR.net_http_libcurl_revocation_notsupported,
48+
CurlVersionDescription,
49+
CurlSslVersionDescription));
50+
}
51+
3952
if (easy._handler.ServerCertificateValidationCallback != null)
4053
{
4154
// libcurl (as of 7.49.1) does not have any callback which can be registered which fires
@@ -58,24 +71,23 @@ internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption cli
5871
// user's keychain and setting the SSL policy trust for it to "Always Trust".
5972
// Similarly, the "block this" could be attained by setting the SSL policy for a cert in the
6073
// keychain to "Never Trust".
61-
throw new PlatformNotSupportedException(
62-
SR.Format(
63-
SR.net_http_libcurl_callback_notsupported,
64-
CurlVersionDescription,
65-
CurlSslVersionDescription));
66-
}
67-
68-
// Revocation checking is always on for darwinssl (SecureTransport).
69-
// If any other backend is used and revocation is requested, we can't guarantee
70-
// that assertion.
71-
if (easy._handler.CheckCertificateRevocationList &&
72-
!CurlSslVersionDescription.Equals("SecureTransport"))
73-
{
74-
throw new PlatformNotSupportedException(
75-
SR.Format(
76-
SR.net_http_libcurl_revocation_notsupported,
77-
CurlVersionDescription,
78-
CurlSslVersionDescription));
74+
//
75+
// However, one case we can support is when we know all certificates will pass validation.
76+
// We can detect a key case of that: whether DangerousAcceptAnyServerCertificateValidator was used.
77+
if (easy.ServerCertificateValidationCallbackAcceptsAll)
78+
{
79+
EventSourceTrace("Warning: Disabling peer verification per {0}", nameof(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator), easy: easy);
80+
easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYPEER, 0); // don't verify the peer
81+
// Don't set CURLOPT_SSL_VERIFHOST to 0; doing so disables SNI.
82+
}
83+
else
84+
{
85+
throw new PlatformNotSupportedException(
86+
SR.Format(
87+
SR.net_http_libcurl_callback_notsupported,
88+
CurlVersionDescription,
89+
CurlSslVersionDescription));
90+
}
7991
}
8092

8193
SetSslVersion(easy);

src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.EasyRequest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,10 @@ private void SetSslOptions()
742742
SslProvider.SetSslOptions(this, _handler.ClientCertificateOptions);
743743
}
744744

745+
internal bool ServerCertificateValidationCallbackAcceptsAll => ReferenceEquals(
746+
_handler.ServerCertificateValidationCallback,
747+
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator);
748+
745749
internal void SetCurlCallbacks(
746750
IntPtr easyGCHandle,
747751
ReadWriteCallback receiveHeadersCallback,

src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.SslProvider.cs

Lines changed: 64 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -47,84 +47,83 @@ internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption cli
4747
CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
4848
}
4949

50-
// Register the callback with libcurl. We need to register even if there's no user-provided
51-
// server callback and even if there are no client certificates, because we support verifying
52-
// server certificates against more than those known to OpenSSL.
53-
if (CurlSslVersionDescription.IndexOf("openssl/1.0", StringComparison.OrdinalIgnoreCase) != -1)
50+
// Configure the options. Our best support is when targeting OpenSSL/1.0. For other backends,
51+
// we fall back to a minimal amount of support, and may throw a PNSE based on the options requested.
52+
if (CurlSslVersionDescription.IndexOf(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) != -1)
5453
{
55-
CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer);
56-
switch (answer)
57-
{
58-
case CURLcode.CURLE_OK:
59-
// We successfully registered. If we'll be invoking a user-provided callback to verify the server
60-
// certificate as part of that, disable libcurl's verification of the host name. The user's callback
61-
// needs to be given the opportunity to examine the cert, and our logic will determine whether
62-
// the host name matches and will inform the callback of that.
63-
if (easy._handler.ServerCertificateValidationCallback != null)
64-
{
65-
easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // don't verify the peer cert's hostname
66-
// We don't change the SSL_VERIFYPEER setting, as setting it to 0 will cause
67-
// SSL and libcurl to ignore the result of the server callback.
68-
}
54+
// Register the callback with libcurl. We need to register even if there's no user-provided
55+
// server callback and even if there are no client certificates, because we support verifying
56+
// server certificates against more than those known to OpenSSL.
57+
SetSslOptionsForSupportedBackend(easy, certProvider, userPointer);
58+
}
59+
else
60+
{
61+
// Newer versions of OpenSSL, and other non-OpenSSL backends, do not currently support callbacks.
62+
// That means we'll throw a PNSE if a callback is required.
63+
SetSslOptionsForUnsupportedBackend(easy, certProvider);
64+
}
65+
}
6966

70-
// The allowed SSL protocols will be set in the configuration callback.
71-
break;
72-
73-
case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.38 and prior
74-
case CURLcode.CURLE_NOT_BUILT_IN: // Curl 7.39 and later
75-
// It's ok if we failed to register the callback if all of the defaults are in play
76-
// with relation to handling of certificates. But if that's not the case, failing to
77-
// register the callback will result in those options not being factored in, which is
78-
// a significant enough error that we need to fail.
79-
EventSourceTrace("CURLOPT_SSL_CTX_FUNCTION not supported: {0}", answer, easy: easy);
80-
if (certProvider != null ||
81-
easy._handler.ServerCertificateValidationCallback != null ||
82-
easy._handler.CheckCertificateRevocationList)
83-
{
84-
throw new PlatformNotSupportedException(
85-
SR.Format(SR.net_http_unix_invalid_certcallback_option, CurlVersionDescription, CurlSslVersionDescription));
86-
}
67+
private static void SetSslOptionsForSupportedBackend(EasyRequest easy, ClientCertificateProvider certProvider, IntPtr userPointer)
68+
{
69+
CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer);
70+
switch (answer)
71+
{
72+
case CURLcode.CURLE_OK:
73+
// We successfully registered. If we'll be invoking a user-provided callback to verify the server
74+
// certificate as part of that, disable libcurl's verification of the host name; we need to get
75+
// the callback from libcurl even if the host name doesn't match, so we take on the responsibility
76+
// of doing the host name match in the callback prior to invoking the user's delegate.
77+
if (easy._handler.ServerCertificateValidationCallback != null)
78+
{
79+
easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0);
80+
// But don't change the CURLOPT_SSL_VERIFYPEER setting, as setting it to 0 will
81+
// cause SSL and libcurl to ignore the result of the server callback.
82+
}
8783

88-
// Since there won't be a callback to configure the allowed SSL protocols, configure them here.
89-
SetSslVersion(easy);
84+
// The allowed SSL protocols will be set in the configuration callback.
85+
break;
9086

91-
break;
87+
case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.38 and prior
88+
case CURLcode.CURLE_NOT_BUILT_IN: // Curl 7.39 and later
89+
EventSourceTrace("CURLOPT_SSL_CTX_FUNCTION not supported: {0}", answer, easy: easy);
90+
SetSslOptionsForUnsupportedBackend(easy, certProvider);
91+
break;
9292

93-
default:
94-
ThrowIfCURLEError(answer);
95-
break;
96-
}
93+
default:
94+
ThrowIfCURLEError(answer);
95+
break;
9796
}
98-
else
97+
}
98+
99+
private static void SetSslOptionsForUnsupportedBackend(EasyRequest easy, ClientCertificateProvider certProvider)
100+
{
101+
if (certProvider != null)
99102
{
100-
// For newer versions of openssl throw PNSE, if default not used.
101-
if (certProvider != null)
102-
{
103-
throw new PlatformNotSupportedException(
104-
SR.Format(
105-
SR.net_http_libcurl_clientcerts_notsupported,
106-
CurlVersionDescription, CurlSslVersionDescription));
107-
}
103+
throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_clientcerts_notsupported, CurlVersionDescription, CurlSslVersionDescription));
104+
}
105+
106+
if (easy._handler.CheckCertificateRevocationList)
107+
{
108+
throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_revocation_notsupported, CurlVersionDescription, CurlSslVersionDescription));
109+
}
108110

109-
if (easy._handler.ServerCertificateValidationCallback != null)
111+
if (easy._handler.ServerCertificateValidationCallback != null)
112+
{
113+
if (easy.ServerCertificateValidationCallbackAcceptsAll)
110114
{
111-
throw new PlatformNotSupportedException(
112-
SR.Format(
113-
SR.net_http_libcurl_callback_notsupported,
114-
CurlVersionDescription, CurlSslVersionDescription));
115+
EventSourceTrace("Warning: Disabling peer and host verification per {0}", nameof(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator), easy: easy);
116+
easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYPEER, 0);
117+
easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0);
115118
}
116-
117-
if (easy._handler.CheckCertificateRevocationList)
119+
else
118120
{
119-
throw new PlatformNotSupportedException(
120-
SR.Format(
121-
SR.net_http_libcurl_revocation_notsupported,
122-
CurlVersionDescription, CurlSslVersionDescription));
121+
throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_callback_notsupported, CurlVersionDescription, CurlSslVersionDescription));
123122
}
124-
125-
// In case of defaults configure the allowed SSL protocols.
126-
SetSslVersion(easy);
127123
}
124+
125+
// In case of defaults configure the allowed SSL protocols.
126+
SetSslVersion(easy);
128127
}
129128

130129
private static void SetSslVersion(EasyRequest easy, IntPtr sslCtx = default(IntPtr))

src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AcceptAllCerts.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace System.Net.Http.Functional.Tests
1313
{
14+
using Configuration = System.Net.Test.Common.Configuration;
15+
1416
public class HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test
1517
{
1618
[Fact]
@@ -20,5 +22,50 @@ public void SingletonReturnsTrue()
2022
Assert.Same(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, HttpClientHandler.DangerousAcceptAnyServerCertificateValidator);
2123
Assert.True(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator(null, null, null, SslPolicyErrors.None));
2224
}
25+
26+
[Theory]
27+
[InlineData(SslProtocols.Tls, false)] // try various protocols to ensure we correctly set versions even when accepting all certs
28+
[InlineData(SslProtocols.Tls, true)]
29+
[InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)]
30+
[InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)]
31+
[InlineData(SslProtocols.None, false)]
32+
[InlineData(SslProtocols.None, true)]
33+
public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
34+
{
35+
using (var handler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator })
36+
using (var client = new HttpClient(handler))
37+
{
38+
if (requestOnlyThisProtocol)
39+
{
40+
handler.SslProtocols = acceptedProtocol;
41+
}
42+
43+
var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol };
44+
await LoopbackServer.CreateServerAsync(async (server, url) =>
45+
{
46+
await TestHelper.WhenAllCompletedOrAnyFailed(
47+
LoopbackServer.ReadRequestAndSendResponseAsync(server, options: options),
48+
client.GetAsync(url));
49+
}, options);
50+
}
51+
}
52+
53+
public static readonly object[][] InvalidCertificateServers =
54+
{
55+
new object[] { Configuration.Http.ExpiredCertRemoteServer },
56+
new object[] { Configuration.Http.SelfSignedCertRemoteServer },
57+
new object[] { Configuration.Http.WrongHostNameCertRemoteServer },
58+
};
59+
60+
[OuterLoop] // TODO: Issue #11345
61+
[Theory]
62+
[MemberData(nameof(InvalidCertificateServers))]
63+
public async Task InvalidCertificateServers_CertificateValidationDisabled_Succeeds(string url)
64+
{
65+
using (var client = new HttpClient(new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }))
66+
{
67+
(await client.GetAsync(url)).Dispose();
68+
}
69+
}
2370
}
2471
}

0 commit comments

Comments
 (0)