runtime: HttpClientUsesSslCertEnvironmentVariables() test will fail on Linux with SocketsHttpHandler

The HttpClientUsesSslCertEnvironmentVariables test will fail on Linux with SocketsHttpHandler for these two test data:

[InlineData(false, false, true, true, true)] [InlineData(false, false, true, false, true)]

        [Theory]
        [PlatformSpecific(~TestPlatforms.OSX)] // Not implemented
        [InlineData(false, false, false, false, false)] // system -> ok
        [InlineData(true, true, true, true, true)]      // empty dir, empty bundle file -> fail
        // It is enough to override the bundle, since all tested platforms don't have a default dir:
        [InlineData(false, false, true, true, true)]    // empty bundle -> fail
        [InlineData(false, false, true, false, true)]   // non-existing bundle -> fail
        public void HttpClientUsesSslCertEnvironmentVariables(bool setSslCertDir, bool createSslCertDir,
            bool setSslCertFile, bool createSslCertFile, bool expectedFailure)
        {
            // This test sets SSL_CERT_DIR and SSL_CERT_FILE to empty/non-existing locations and then
            // checks the http request fails.
            // Some platforms will use the system default when not specifying a value, while others
            // will not use those certificates. Due to these platform differences, we only check specific
            // combinations that are expected to work the same cross-platform.
            var psi = new ProcessStartInfo();
            if (setSslCertDir)
            {
                string sslCertDir = GetTestFilePath();
                if (createSslCertDir)
                {
                    Directory.CreateDirectory(sslCertDir);
                }
                psi.Environment.Add("SSL_CERT_DIR", sslCertDir);
            }

            if (setSslCertFile)
            {
                string sslCertFile = GetTestFilePath();
                if (createSslCertFile)
                {
                    File.WriteAllText(sslCertFile, "");
                }
                psi.Environment.Add("SSL_CERT_FILE", sslCertFile);
            }

            RemoteInvoke(async arg =>
            {
                bool shouldFail = bool.Parse(arg);
                const string Url = "https://www.microsoft.com";

+                // Here, if we change to client = CreateHttpClient(useSocketsHttpHandlerBoolString: "true"), the test will always fail. Test will pass if client = CreateHttpClient(useSocketsHttpHandlerBoolString: "false"). (Not using SocketsHttpHandler)
+                using (HttpClient client = new HttpClient())
                {
                    if (shouldFail)
                    {
                        await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Url));
                    }
                    else
                    {
                        await client.GetAsync(Url);
                    }
                }
                return SuccessExitCode;
            }, expectedFailure.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
        }

Callstack:

Unhandled Exception: Xunit.Sdk.ThrowsException: Assert.Throws() Failure
2018-03-12 22:36:11,472: INFO: proc(54): run_and_log_output: Output: Expected: typeof(System.Net.Http.HttpRequestException)
2018-03-12 22:36:11,472: INFO: proc(54): run_and_log_output: Output: Actual:   (No exception was thrown)
2018-03-12 22:36:11,472: INFO: proc(54): run_and_log_output: Output:    at Xunit.Assert.Throws(Type exceptionType, Exception exception)
2018-03-12 22:36:11,472: INFO: proc(54): run_and_log_output: Output:    at Xunit.Assert.ThrowsAsync[T](Func`1 testCode)
2018-03-12 22:36:11,473: INFO: proc(54): run_and_log_output: Output:    at System.Net.Http.Functional.Tests.HttpClientHandler_ServerCertificates_Test.<>c.<<HttpClientUsesSslCertEnvironmentVariables>b__29_0>d.MoveNext() in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Release+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs:line 117
2018-03-12 22:36:11,474: INFO: proc(54): run_and_log_output: Output: --- End of stack trace from previous location where exception was thrown ---
2018-03-12 22:36:11,474: INFO: proc(54): run_and_log_output: Output:    at RemoteExecutorConsoleApp.Program.Main(String[] args) in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Release+AGroup_x64+TestOuter_true_prtest/src/Common/tests/System/Diagnostics/RemoteExecutorConsoleApp/RemoteExecutorConsoleApp.cs:line 63
2018-03-12 22:36:11,475: INFO: proc(54): run_and_log_output: Output: --- End of stack trace from previous location where exception was thrown ---
2018-03-12 22:36:11,475: INFO: proc(54): run_and_log_output: Output:    at RemoteExecutorConsoleApp.Program.Main(String[] args) in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Release+AGroup_x64+TestOuter_true_prtest/src/Common/tests/System/Diagnostics/RemoteExecutorConsoleApp/RemoteExecutorConsoleApp.cs:line 92
2018-03-12 22:36:12,308: INFO: proc(54): run_and_log_output: Output:    System.Net.Http.Functional.Tests.PlatformHandler_HttpClientHandler_ServerCertificates_Test.HttpClientUsesSslCertEnvironmentVariables(setSslCertDir: False, createSslCertDir: False, setSslCertFile: True, createSslCertFile: True, expectedFailure: True) [FAIL]
2018-03-12 22:36:12,310: INFO: proc(54): run_and_log_output: Output:       System.Diagnostics.RemoteExecutorTestBase+RemoteInvokeHandle+RemoteExecutionException : Remote process failed with an unhandled exception.
2018-03-12 22:36:12,342: INFO: proc(54): run_and_log_output: Output:       Stack Trace:
2018-03-12 22:36:12,767: INFO: proc(54): run_and_log_output: Output:          
2018-03-12 22:36:12,768: INFO: proc(54): run_and_log_output: Output:          Child exception:
2018-03-12 22:36:12,778: INFO: proc(54): run_and_log_output: Output:            Xunit.Sdk.ThrowsException: Assert.Throws() Failure
2018-03-12 22:36:12,779: INFO: proc(54): run_and_log_output: Output:          Expected: typeof(System.Net.Http.HttpRequestException)
2018-03-12 22:36:12,779: INFO: proc(54): run_and_log_output: Output:          Actual:   (No exception was thrown)
2018-03-12 22:36:12,796: INFO: proc(54): run_and_log_output: Output:          /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Release+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.Unix.cs(117,0): at System.Net.Http.Functional.Tests.HttpClientHandler_ServerCertificates_Test.<>c.<<HttpClientUsesSslCertEnvironmentVariables>b__29_0>d.MoveNext()
2018-03-12 22:36:12,797: INFO: proc(54): run_and_log_output: Output:          --- End of stack trace from previous location where exception was thrown ---
2018-03-12 22:36:12,797: INFO: proc(54): run_and_log_output: Output:          /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Release+AGroup_x64+TestOuter_true_prtest/src/Common/tests/System/Diagnostics/RemoteExecutorConsoleApp/RemoteExecutorConsoleApp.cs(63,0): at RemoteExecutorConsoleApp.Program.Main(String[] args)
2018-03-12 22:36:12,798: INFO: proc(54): run_and_log_output: Output:          
2018-03-12 22:36:12,798: INFO: proc(54): run_and_log_output: Output:          Child process:
2018-03-12 22:36:12,798: INFO: proc(54): run_and_log_output: Output:            System.Net.Http.Functional.Tests, Version=4.2.1.0, Culture=neutral, PublicKeyToken=9d77cc7ad39b68eb System.Net.Http.Functional.Tests.HttpClientHandler_ServerCertificates_Test+<>c System.Threading.Tasks.Task`1[System.Int32] <HttpClientUsesSslCertEnvironmentVariables>b__29_0(System.String)
2018-03-12 22:36:12,799: INFO: proc(54): run_and_log_output: Output:          
2018-03-12 22:36:12,799: INFO: proc(54): run_and_log_output: Output:          Child arguments:
2018-03-12 22:36:12,799: INFO: proc(54): run_and_log_output: Output:            True
2018-03-12 22:36:12,799: INFO: proc(54): run_and_log_output: Output:          

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 26 (26 by maintainers)

Most upvoted comments

Only setting SSL_CERT_FILE is not enough. When SSL_CERT_DIR is not set, the implementation falls back to the default directory, which causes system certificates to be loaded.

It depends on the goal of the user.

  • If they want to completely replace the system trust (permanently or during this program execution)
    • Set both variables
  • If they want to add a certificate to their trust list permanently, being in addition to system trust
    • Open X509Store(Root, CurrentUser) and add the cert
  • If they want to add a bundle of certificates for this program execution, but not generally, and they don’t care if other things are also trusted
    • Set either SSL_CERT_FILE or SSL_CERT_DIR, as needed.

The platform uses additive behaviors. That is, a certificate is considered trusted if it is in either the SSL_CERT_FILE file or the SSL_CERT_DIR directory. The test assumes that by setting just SSL_CERT_FILE that it has replaced the entire store, when in reality it has (depending on the particular distro’s certificate configurations, “probably”) just added to the store.

Another way of expressing the correct actions is

  • if “I want requests to fail because if they aren’t in this list”
    • Set both
  • else if “I want to always trust these CAs in addition to what the system trusts”
    • if admin
      • Edit system trust
    • else if “not part of an interleaved program execution via a script”
      • Use the .NET API to add the cert to the user Root store
    • else if “distro uses only _DIR”
      • set SSL_CERT_FILE to point to a (possibly-multi-)PEM
    • else if “distro uses only _FILE”
      • set SSL_CERT_DIR to point to a directory containing single-PEM certificates
    • else
      • set whichever one
  • else if “I want to trust these CAs in addition to what the system trusts only when this script/program runs”
    • if “distro uses only _DIR”
      • set SSL_CERT_FILE to point to a (possibly-multi-)PEM
    • else if “distro uses only _FILE”
      • set SSL_CERT_DIR to point to a directory containing single-PEM certificates
    • else
      • set whichever one
  • else
    • Don’t do anything, system trust just happens.