最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - The SSL connection could not be established, see inner exception. Docker problem - Stack Overflow

programmeradmin0浏览0评论

SSL Handshake Error in Docker: SSL_ERROR_SSL with .NET 8 and OpenSSL

Issue

I'm running a .NET 8 application in a Windows + Visual Studio environment, and everything works fine. However, when I run it inside a Docker container, I get the following error when making an HTTPS request with a client certificate:

Dockerfile

FROM mcr.microsoft/dotnet/aspnet:8.0 AS base
USER root
RUN apt-get update && apt-get install -y ca-certificates

COPY ["InsuranceApi/sosMedecin.crt", "/usr/local/share/ca-certificates/sosMedecin.crt"]
RUN update-ca-certificates

USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

COPY ["InsuranceApi/InsuranceApi.csproj", "InsuranceApi/"]
RUN dotnet restore "./InsuranceApi/InsuranceApi.csproj"
COPY . .
WORKDIR "/src/InsuranceApi"
RUN dotnet build "./InsuranceApi.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./InsuranceApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app

COPY --from=publish /app/publish .
COPY ["InsuranceApi/sosMedecin.pfx", "/app/publish/sosMedecin.pfx"]

ENTRYPOINT ["dotnet", "InsuranceApi.dll"]

AuthenticationService.cs


public class AuthenticationService
{
    private readonly CertificateOptions _certificateOptions;
    private readonly SsoOptions _ssoOptions;

    public AuthenticationService(IOptions<CertificateOptions> certificateOptions, IOptions<SsoOptions> ssoOptions)
    {
        _certificateOptions = certificateOptions.Value;
        _ssoOptions = ssoOptions.Value;
    }

    public async Task<string?> GetSsoToken()
    {
        string? ssoToken = null;
        HttpClientHandler handler = new HttpClientHandler();

        try
        {
            // Adding client certificate to HttpClientHandler
            handler.ClientCertificates.Add(new X509Certificate2(_certificateOptions.CertificatePath, _certificateOptions.CertificatePassword));

            // Making the HTTP request to get the SSO token
            using (var client = new HttpClient(handler))
            {
                var content = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("format", "text"),
                    new KeyValuePair<string, string>("username", _ssoOptions.Username),
                    new KeyValuePair<string, string>("password", _ssoOptions.Password),
                    new KeyValuePair<string, string>("submit", "confirm")
                });

                HttpResponseMessage response = await client.PostAsync(_ssoOptions.SsoTokenUrl, content);

                // If response contains the SSO token cookie
                if (response.Headers.Contains("Set-Cookie"))
                {
                    var cookieHeader = response.Headers
                        .GetValues("Set-Cookie")
                        .FirstOrDefault(c => c.StartsWith("SSOV2"));

                    if (!string.IsNullOrEmpty(cookieHeader))
                    {
                        ssoToken = cookieHeader.Split(';')[0].Split('=')[1];
                    }
                    else
                    {
                        throw new ArgumentNullException("Cookie with SSO token was not found.");
                    }
                }
                else
                {
                    throw new ArgumentNullException($"SSO token not found in response.\nResponse: {await response.Content.ReadAsStringAsync()}");
                }
            }
        }
        catch (Exception ex)
        {
            // Log any errors and inner exceptions
            Console.WriteLine($"Unexpected error: {ex.Message}");
            while (ex.InnerException != null)
            {
                Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
                ex = ex.InnerException;
            }
            throw;
        }

        return ssoToken;
    }
}

I attempted to add the certificate manually in the Dockerfile using update-ca-certificates, but it did not resolve the issue.

SSL Handshake Error in Docker: SSL_ERROR_SSL with .NET 8 and OpenSSL

Issue

I'm running a .NET 8 application in a Windows + Visual Studio environment, and everything works fine. However, when I run it inside a Docker container, I get the following error when making an HTTPS request with a client certificate:

Dockerfile

FROM mcr.microsoft/dotnet/aspnet:8.0 AS base
USER root
RUN apt-get update && apt-get install -y ca-certificates

COPY ["InsuranceApi/sosMedecin.crt", "/usr/local/share/ca-certificates/sosMedecin.crt"]
RUN update-ca-certificates

USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

COPY ["InsuranceApi/InsuranceApi.csproj", "InsuranceApi/"]
RUN dotnet restore "./InsuranceApi/InsuranceApi.csproj"
COPY . .
WORKDIR "/src/InsuranceApi"
RUN dotnet build "./InsuranceApi.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./InsuranceApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app

COPY --from=publish /app/publish .
COPY ["InsuranceApi/sosMedecin.pfx", "/app/publish/sosMedecin.pfx"]

ENTRYPOINT ["dotnet", "InsuranceApi.dll"]

AuthenticationService.cs


public class AuthenticationService
{
    private readonly CertificateOptions _certificateOptions;
    private readonly SsoOptions _ssoOptions;

    public AuthenticationService(IOptions<CertificateOptions> certificateOptions, IOptions<SsoOptions> ssoOptions)
    {
        _certificateOptions = certificateOptions.Value;
        _ssoOptions = ssoOptions.Value;
    }

    public async Task<string?> GetSsoToken()
    {
        string? ssoToken = null;
        HttpClientHandler handler = new HttpClientHandler();

        try
        {
            // Adding client certificate to HttpClientHandler
            handler.ClientCertificates.Add(new X509Certificate2(_certificateOptions.CertificatePath, _certificateOptions.CertificatePassword));

            // Making the HTTP request to get the SSO token
            using (var client = new HttpClient(handler))
            {
                var content = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("format", "text"),
                    new KeyValuePair<string, string>("username", _ssoOptions.Username),
                    new KeyValuePair<string, string>("password", _ssoOptions.Password),
                    new KeyValuePair<string, string>("submit", "confirm")
                });

                HttpResponseMessage response = await client.PostAsync(_ssoOptions.SsoTokenUrl, content);

                // If response contains the SSO token cookie
                if (response.Headers.Contains("Set-Cookie"))
                {
                    var cookieHeader = response.Headers
                        .GetValues("Set-Cookie")
                        .FirstOrDefault(c => c.StartsWith("SSOV2"));

                    if (!string.IsNullOrEmpty(cookieHeader))
                    {
                        ssoToken = cookieHeader.Split(';')[0].Split('=')[1];
                    }
                    else
                    {
                        throw new ArgumentNullException("Cookie with SSO token was not found.");
                    }
                }
                else
                {
                    throw new ArgumentNullException($"SSO token not found in response.\nResponse: {await response.Content.ReadAsStringAsync()}");
                }
            }
        }
        catch (Exception ex)
        {
            // Log any errors and inner exceptions
            Console.WriteLine($"Unexpected error: {ex.Message}");
            while (ex.InnerException != null)
            {
                Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
                ex = ex.InnerException;
            }
            throw;
        }

        return ssoToken;
    }
}

I attempted to add the certificate manually in the Dockerfile using update-ca-certificates, but it did not resolve the issue.

Share Improve this question edited Feb 27 at 7:59 Jack J Jun- MSFT 5,9861 gold badge13 silver badges44 bronze badges asked Feb 3 at 11:32 Markiian HoinetsMarkiian Hoinets 133 bronze badges 6
  • "I get the following error" what error? And what is the value of $ex.InnerException – Charlieface Commented Feb 3 at 11:45
  • @Charlieface Unexpected error: The SSL connection could not be established, see inner exception. Inner Exception: Authentication failed, see inner exception. Inner Exception: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL. Inner Exception: error:0A000412:SSL routines::sslv3 alert bad certificate – Markiian Hoinets Commented Feb 3 at 11:49
  • And your logging/debugging tells you it loaded the client certificate into handler.ClientCertificates correctly? Side notes: handler should be a global static, not created every time, and you are missing using on response. Also use UseCookies = true instead of handling cookies yourself. – Charlieface Commented Feb 3 at 11:54
  • @Charlieface handler.ClientCertificates correctly loads certificate, I checked handler and there is a certificate with all information about it. Error happens at HttpResponseMessage response = await client.PostAsync(_ssoOptions.SsoTokenUrl, content); – Markiian Hoinets Commented Feb 3 at 12:09
  • Have you checked out this Github thread ? – dan-kli Commented Feb 4 at 11:57
 |  Show 1 more comment

2 Answers 2

Reset to default 0

We had the same issue about three weeks ago, but we were using a Windows image. C# certificate validation worked outside of Docker, but failed inside the container with an SSL exception. It is not exactly your problem since you are using a Linux container, but it might be very similar. I will write up our solution and hope that it might point you in the right direction.


We were running the Windows container on systems behind proxies. Our problem was that whatever client the container was using for the certificate revocation check (it is not the C# HTTP client!) was not respecting the proxy settings set to Docker and the C# http client, and we got the SSL connection exception.

The simple solution was to set the certificate revocation mode to Offline in our certificate validation callback. No more calls where send out of the container for the certificate revocation check, and the certificate verification worked fine inside the container.

Since we did not want to do that, we had to set the proxy settings to the registry of the container, which fixed the issue. This could be done in the Dockerfile using reg add, but since we are using the same container for different customer systems with different proxies, we had to pass the proxy / no proxy settings during the startup of the container via environment variables, f.e. HTTP_PROXY and NO_PROXY. The C# app then writes these values into the registry during startup, simplified code:

string path = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
string httpProxy = Environment.GetEnvironmentVariable("HTTP_PROXY");
string noProxy = Environment.GetEnvironmentVariable("NO_PROXY");

using (RegistryKey key = Registry.CurrentUser.CreateSubKey(path, writable: true))
{
  key.SetValue("ProxyServer", httpProxy, RegistryValueKind.String);
  key.SetValue("ProxyEnable", 1, RegistryValueKind.DWord);
  key.SetValue("MigrateProxy", 1, RegistryValueKind.DWord);
  key.SetValue("ProxyOverride", noProxy, RegistryValueKind.String);
}

Hope this helps and gives you some ideas on what can go wrong, debugging these certificate issues in a container is not straight forward. What helped me is building the certificate chain in the C# code manually and logging all results, and using tools like openssl and the Test-Certificate cmdlet on the command line inside the container to get more results on what exactly goes wrong during the certificate validation.

you maybe need to lower the min allowed ssl protocoll from the docker image with something like this:

USER root
RUN sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' 
  /etc/ssl/openssl.cnf

RUN echo "\n[ssl_configuration]" >> /etc/ssl/openssl.cnf \
  && echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf

RUN echo "\n[tls_system_default]" >> /etc/ssl/openssl.cnf \
  && echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf \
  && echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf
#RUN cat -n /etc/ssl/openssl.cnf

USER $APP_UID
发布评论

评论列表(0)

  1. 暂无评论