Blob Blame History Raw
From dfa0c242cd9b329554971cf80c094e8f58d756e3 Mon Sep 17 00:00:00 2001
From: Jeremy Barton <jbarton@microsoft.com>
Date: Wed, 26 Sep 2018 15:35:35 -0700
Subject: [PATCH 6/7] Check for the specific in-use version of OpenSSL when
 working with libcurl

Rather than check a generic 1.0/1.1, test for the specific library version that
the crypto shim has loaded.  This makes things work when both libcurl
and the crypto shim are using OpenSSL 1.1 and also prevents a state where two
different copies of the library (at different patch versions) are utilized.
---
 .../Interop.Initialization.cs                 |  8 +--
 .../Interop.VersionInfo.cs                    | 44 +++++++++++-
 .../Interop.OpenSslVersion.cs                 |  2 +-
 .../src/System.Net.Http.csproj                |  3 +
 .../CurlHandler.SslProvider.Linux.cs          |  8 +--
 .../FunctionalTests/HttpClientEKUTest.cs      |  2 +-
 ...ttpClientHandlerTest.ClientCertificates.cs | 14 +---
 ...ientHandlerTest.ServerCertificates.Unix.cs |  9 +--
 ...HttpClientHandlerTest.SslProtocols.Unix.cs |  3 +-
 .../System.Net.Http.Functional.Tests.csproj   |  3 +-
 .../tests/FunctionalTests/TestHelper.cs       | 69 +++++++++++++++++++
 11 files changed, 130 insertions(+), 35 deletions(-)

diff --git a/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.Initialization.cs b/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.Initialization.cs
index eef56ec0b3..d6bcc8df02 100644
--- a/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.Initialization.cs
+++ b/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.Initialization.cs
@@ -26,11 +26,11 @@ internal static partial class Interop
 #if !SYSNETHTTP_NO_OPENSSL
             string opensslVersion = Interop.Http.GetSslVersionDescription();
             if (string.IsNullOrEmpty(opensslVersion) ||
-                opensslVersion.IndexOf(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) != -1)
+                opensslVersion.IndexOf(Interop.Http.OpenSslDescriptionPrefix, StringComparison.OrdinalIgnoreCase) != -1)
             {
-                // CURL uses OpenSSL which we must initialize first to guarantee thread-safety
-                // Only initialize for OpenSSL/1.0, any newer versions may have mismatched
-                // pointers, resulting in segfaults.
+                // CURL uses OpenSSL which we must initialize first to guarantee thread-safety.
+                // We'll wake up whatever OpenSSL we're going to run against, but might later determine that
+                // they aren't compatible.
                 CryptoInitializer.Initialize();
             }
 #endif
diff --git a/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.VersionInfo.cs b/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.VersionInfo.cs
index 1899fd0af3..8175159b6f 100644
--- a/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.VersionInfo.cs
+++ b/src/Common/src/Interop/Unix/System.Net.Http.Native/Interop.VersionInfo.cs
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 
 internal static partial class Interop
@@ -47,8 +48,49 @@ internal static partial class Interop
         [DllImport(Libraries.HttpNative, EntryPoint = "HttpNative_GetSslVersionDescription")]
         internal static extern string GetSslVersionDescription();
 
-        internal const string OpenSsl10Description = "openssl/1.0";
+        internal const string OpenSslDescriptionPrefix = "OpenSSL/";
         internal const string SecureTransportDescription = "SecureTransport";
         internal const string LibreSslDescription = "LibreSSL";
+
+#if !SYSNETHTTP_NO_OPENSSL
+        private static readonly Lazy<string> s_requiredOpenSslDescription =
+            new Lazy<string>(() => DetermineRequiredOpenSslDescription());
+
+        private static readonly Lazy<bool> s_hasMatchingOpenSsl =
+            new Lazy<bool>(() => RequiredOpenSslDescription == GetSslVersionDescription());
+
+        internal static string RequiredOpenSslDescription => s_requiredOpenSslDescription.Value;
+        internal static bool HasMatchingOpenSslVersion => s_hasMatchingOpenSsl.Value;
+
+        private static string DetermineRequiredOpenSslDescription()
+        {
+            string versionDescription = Interop.OpenSsl.OpenSslVersionDescription();
+            var version = versionDescription.AsSpan();
+
+            // OpenSSL version description looks like this:
+            //
+            // OpenSSL 1.1.1 FIPS  11 Sep 2018
+            //
+            // libcurl's OpenSSL vtls backend ignores status in the version string.
+            // Major, minor, and fix are encoded (by libcurl) as unpadded hex
+            // (0 => "0", 15 => "f", 16 => "10").
+            //
+            // Patch is encoded as in the way OpenSSL would do it.
+
+            string prefix = "OpenSSL ";
+            if (version.StartsWith(prefix))
+            {
+                version = version.Slice(prefix.Length).Trim();
+            }
+            int end = version.IndexOf(" ");
+            if (end != -1)
+            {
+                version = version.Slice(0, end);
+            }
+            version = version.Trim();
+
+            return $"{OpenSslDescriptionPrefix}{version.ToString()}";
+        }
+#endif
     }
 }
diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSslVersion.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSslVersion.cs
index 70805706ef..13c2339ce6 100644
--- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSslVersion.cs
+++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSslVersion.cs
@@ -12,7 +12,7 @@ internal static partial class Interop
         private static Version s_opensslVersion;
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SSLEayVersion")]
-        private static extern string OpenSslVersionDescription();
+        internal static extern string OpenSslVersionDescription();
 
         internal static Version OpenSslVersion
         {
diff --git a/src/System.Net.Http/src/System.Net.Http.csproj b/src/System.Net.Http/src/System.Net.Http.csproj
index 66e5b8d5f8..34b4d22e15 100644
--- a/src/System.Net.Http/src/System.Net.Http.csproj
+++ b/src/System.Net.Http/src/System.Net.Http.csproj
@@ -500,6 +500,9 @@
     <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs">
       <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.OpenSslVersion.cs">
+      <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.OpenSslVersion.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Ssl.cs">
       <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Ssl.cs</Link>
     </Compile>
diff --git a/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs
index 55e583e137..2fdcde686d 100644
--- a/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs
+++ b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs
@@ -55,7 +55,7 @@ namespace System.Net.Http
 
                 // Configure the options.  Our best support is when targeting OpenSSL/1.0.  For other backends,
                 // we fall back to a minimal amount of support, and may throw a PNSE based on the options requested.
-                if (CurlSslVersionDescription.IndexOf(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) != -1)
+                if (Interop.Http.HasMatchingOpenSslVersion)
                 {
                     // Register the callback with libcurl.  We need to register even if there's no user-provided
                     // server callback and even if there are no client certificates, because we support verifying
@@ -169,12 +169,12 @@ namespace System.Net.Http
             {
                 if (certProvider != null)
                 {
-                    throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_clientcerts_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.OpenSsl10Description));
+                    throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_clientcerts_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription));
                 }
 
                 if (easy._handler.CheckCertificateRevocationList)
                 {
-                    throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_revocation_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.OpenSsl10Description));
+                    throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_revocation_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription));
                 }
 
                 if (easy._handler.ServerCertificateCustomValidationCallback != null)
@@ -187,7 +187,7 @@ namespace System.Net.Http
                     }
                     else
                     {
-                        throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_callback_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.OpenSsl10Description));
+                        throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_callback_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription));
                     }
                 }
                 else
diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs
index c6badc770e..9ac90390e3 100644
--- a/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs
+++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs
@@ -22,7 +22,7 @@ namespace System.Net.Http.Functional.Tests
 #if TargetsWindows
             true;
 #else
-            Interop.Http.GetSslVersionDescription()?.StartsWith(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) ?? false;
+            TestHelper.NativeHandlerSupportsSslConfiguration();
 #endif
 
         private static bool CanTestCertificates =>
diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs
index 78d0fdb09f..217726db64 100644
--- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs
+++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs
@@ -319,19 +319,7 @@ namespace System.Net.Http.Functional.Tests
 #if TargetsWindows
                 return true;
 #else
-                if (UseSocketsHttpHandler)
-                {
-                    return true;
-                }
-
-                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-                {
-                    return false;
-                }
-
-                // For other Unix-based systems it's true if (and only if) the openssl backend
-                // is used with libcurl.
-                return (Interop.Http.GetSslVersionDescription()?.StartsWith(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) ?? false);
+                return TestHelper.NativeHandlerSupportsSslConfiguration();
 #endif
             }
         }
diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs
index d19a63f598..eb38f93615 100644
--- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs
+++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs
@@ -58,14 +58,7 @@ namespace System.Net.Http.Functional.Tests
                     return true;
                 }
 
-                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-                {
-                    return false;
-                }
-
-                // For other Unix-based systems it's true if (and only if) the openssl backend
-                // is used with libcurl.
-                return (Interop.Http.GetSslVersionDescription()?.StartsWith(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) ?? false);
+                return TestHelper.NativeHandlerSupportsSslConfiguration();
             }
         }
 
diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs
index 615f2cb4fa..e7631e3940 100644
--- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs
+++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.Unix.cs
@@ -17,7 +17,6 @@ namespace System.Net.Http.Functional.Tests
     public abstract partial class HttpClientHandler_SslProtocols_Test
     {
         private bool BackendSupportsSslConfiguration =>
-            UseSocketsHttpHandler ||
-            (Interop.Http.GetSslVersionDescription()?.StartsWith(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) ?? false);
+            UseSocketsHttpHandler || TestHelper.NativeHandlerSupportsSslConfiguration();
     }
 }
diff --git a/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj
index 68c87c2b6e..b3b9d2437d 100644
--- a/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj
+++ b/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj
@@ -5,6 +5,7 @@
     <ProjectGuid>{C85CF035-7804-41FF-9557-48B7C948B58D}</ProjectGuid>
     <DefineConstants Condition="'$(TargetGroup)'=='netcoreapp'">$(DefineConstants);netcoreapp</DefineConstants>
     <DefineConstants Condition="'$(TargetsWindows)'=='true'">$(DefineConstants);TargetsWindows</DefineConstants>
+    <DefineConstants>$(DefineConstants);SYSNETHTTP_NO_OPENSSL</DefineConstants>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Unix-Debug|AnyCPU'" />
@@ -169,4 +170,4 @@
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
   </ItemGroup>
   <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs b/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs
index 9bf6f216d1..2e29128a92 100644
--- a/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs
+++ b/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs
@@ -6,6 +6,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Net.NetworkInformation;
 using System.Net.Security;
+using System.Reflection;
+using System.Runtime.InteropServices;
 using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
 using System.Text;
@@ -107,5 +109,72 @@ namespace System.Net.Http.Functional.Tests
                 .Select(a => a.Address)
                 .Where(a => a.IsIPv6LinkLocal)
                 .FirstOrDefault();
+
+        public static void EnsureHttp2Feature(HttpClientHandler handler)
+        {
+            // All .NET Core implementations of HttpClientHandler have HTTP/2 enabled by default except when using
+            // SocketsHttpHandler. Right now, the HTTP/2 feature is disabled on SocketsHttpHandler unless certain
+            // AppContext switches or environment variables are set. To help with testing, we can enable the HTTP/2
+            // feature for a specific handler instance by using reflection.
+            FieldInfo field_socketsHttpHandler = typeof(HttpClientHandler).GetField(
+                "_socketsHttpHandler",
+                BindingFlags.NonPublic | BindingFlags.Instance);
+            if (field_socketsHttpHandler == null)
+            {
+                // Not using .NET Core implementation, i.e. could be .NET Framework or UAP.
+                return;
+            }
+
+            object _socketsHttpHandler = field_socketsHttpHandler.GetValue(handler);
+            if (_socketsHttpHandler == null)
+            {
+                // Not using SocketsHttpHandler, i.e. using WinHttpHandler or CurlHandler.
+                return;
+            }
+
+            // Get HttpConnectionSettings object from SocketsHttpHandler.
+            Type type_SocketsHttpHandler = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.SocketsHttpHandler");
+            FieldInfo field_settings = type_SocketsHttpHandler.GetField(
+                "_settings",
+                BindingFlags.NonPublic | BindingFlags.Instance);
+            Assert.NotNull(field_settings);
+            object _settings = field_settings.GetValue(_socketsHttpHandler);
+            Assert.NotNull(_settings);
+
+            // Set _maxHttpVersion field to HTTP/2.0.
+            Type type_HttpConnectionSettings = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.HttpConnectionSettings");
+            FieldInfo field_maxHttpVersion = type_HttpConnectionSettings.GetField(
+                "_maxHttpVersion",
+                BindingFlags.NonPublic | BindingFlags.Instance);
+            field_maxHttpVersion.SetValue(_settings, new Version(2, 0));
+        }
+
+        public static bool NativeHandlerSupportsSslConfiguration()
+        {
+#if TargetsWindows
+            return true;
+#else
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                return false;
+            }
+
+            // For other Unix-based systems it's true if (and only if) the currect openssl backend
+            // is used with libcurl.
+            bool hasAnyOpenSsl =
+                Interop.Http.GetSslVersionDescription()?.StartsWith(Interop.Http.OpenSslDescriptionPrefix, StringComparison.OrdinalIgnoreCase) ?? false;
+
+            if (!hasAnyOpenSsl)
+            {
+                return false;
+            }
+
+            // We're on an OpenSSL-based system, with an OpenSSL backend.
+            // Ask the product how it feels about this.
+            Type interopHttp = typeof(HttpClient).Assembly.GetType("Interop+Http");
+            PropertyInfo hasMatchingOpenSslVersion = interopHttp.GetProperty("HasMatchingOpenSslVersion", BindingFlags.Static | BindingFlags.NonPublic);
+            return (bool)hasMatchingOpenSslVersion.GetValue(null);
+#endif
+        }
     }
 }
-- 
2.20.1