Search code examples
c#.netlinuxssltitanium-web-proxy

SSL Verification Fails in Linux


I'm using Titanium Web Proxy to modify headers of https requests. It works fine in Windows, but in Linux it fails with alert handshake failure error.

I have generated root CA certificates with following commands.

openssl genrsa -out rootCert.key 4096
openssl req -x509 -new -nodes -key rootCert.key -sha256 -days 1024 -out rootCert.crt
openssl pkcs12 -export -out rootCert.pfx -inkey rootCert.key -in rootCert.crt

And installed generated certificates using following commands. Also made sure my certificate file is correctly appended to the /etc/ssl/certs/ca-certificates.crt file.

sudo cp rootCert.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

My ProxyService Code

public class ProxyService : IHostedService, IDisposable
{
    #region Private Fields
    private readonly ILogger<ProxyService> logger;

    private readonly ProxyConfig proxyConfig;
    private readonly HeaderConfig headerConfig;

    private readonly ProxyServer proxyServer;
    private ExplicitProxyEndPoint explicitEndPoint;
    #endregion

    #region Constructor
    public ProxyService(ILogger<ProxyService> logger, IOptions<ProxyConfig> proxyConfig, IOptions<HeaderConfig> headerConfig)
    {
        this.logger = logger;
        this.proxyConfig = proxyConfig.Value;
        this.headerConfig = headerConfig.Value;

        proxyServer = new ProxyServer();

        proxyServer.ExceptionFunc += onException;

        proxyServer.TcpTimeWaitSeconds = 10;
        proxyServer.ConnectionTimeOutSeconds = 15;
        proxyServer.ReuseSocket = false;
        proxyServer.EnableConnectionPool = false;
        proxyServer.ForwardToUpstreamGateway = true;
        proxyServer.CertificateManager.SaveFakeCertificates = true;
    }
    #endregion

    #region IHostedService Implementation
    public Task StartAsync(CancellationToken cancellationToken)
    {
        proxyServer.BeforeRequest += onRequest;
        proxyServer.ServerCertificateValidationCallback += onCertificateValidation;

        explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Parse(proxyConfig.IPAddress), proxyConfig.Port);
        explicitEndPoint.BeforeTunnelConnectRequest += onBeforeTunnelConnectRequest;

        proxyServer.CertificateManager.CertificateEngine = Titanium.Web.Proxy.Network.CertificateEngine.BouncyCastleFast;

        proxyServer.AddEndPoint(explicitEndPoint);
        proxyServer.Start();

        foreach (ProxyEndPoint endPoint in proxyServer.ProxyEndPoints)
        {
            logger.LogInformation("Listening on '{0}' at IP {1} and port: {2} ", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
        }

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("Stopping proxy service!");

        explicitEndPoint.BeforeTunnelConnectRequest -= onBeforeTunnelConnectRequest;

        proxyServer.BeforeRequest -= onRequest;
        proxyServer.ServerCertificateValidationCallback -= onCertificateValidation;

        proxyServer.Stop();

        return Task.CompletedTask;
    }
    #endregion

    #region IDisposable Implementation
    public void Dispose()
    {
        proxyServer.Dispose();
    }
    #endregion

    #region Event Handlers
    private Task onBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
    {
        string hostname = e.HttpClient.Request.RequestUri.Host;

        logger.LogInformation("Tunnel to: {0}", hostname);


        if (!hostname.Contains(headerConfig.BaseHostName))
        {
            e.DecryptSsl = false;
        }

        return Task.CompletedTask;
    }

    private Task onRequest(object sender, SessionEventArgs e)
    {
        var headers = e.HttpClient.Request.Headers;
        if (headers.HeaderExists(headerConfig.TokenHeaderName))
        {
            var uri = e.HttpClient.Request.RequestUri;

            if (uri.Host.EndsWith(headerConfig.BaseHostName) && uri.AbsolutePath.StartsWith(headerConfig.UriPath))
            {
                logger.LogInformation("Adding auth header to: {0}", uri.ToString());

                var token = headers.GetFirstHeader(headerConfig.TokenHeaderName).Value;
                headers.AddHeader("Authorization", token);
            }

            headers.RemoveHeader(headerConfig.TokenHeaderName);
        }

        return Task.CompletedTask;
    }

    private Task onCertificateValidation(object sender, CertificateValidationEventArgs e)
    {
        if (e.SslPolicyErrors == SslPolicyErrors.None)
        {
            e.IsValid = true;
        }

        return Task.CompletedTask;
    }

    private void onException(Exception exception)
    {
        logger.LogError(exception, exception.Message);
    }
    #endregion
}

Full Error

proxy_1   |       Error occured whilst handling session request
proxy_1   |       Titanium.Web.Proxy.Exceptions.ProxyHttpException: Error occured whilst handling session request
proxy_1   |        ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
proxy_1   |        ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
proxy_1   |        ---> Interop+Crypto+OpenSslCryptographicException: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
proxy_1   |          --- End of inner exception stack trace ---
proxy_1   |          at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, Byte[] recvBuf, Int32 recvOffset, Int32 recvCount, Byte[]& sendBuf, Int32& sendCount)
proxy_1   |          at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteContext& context, ArraySegment`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions)
proxy_1   |          --- End of inner exception stack trace ---
proxy_1   |          at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
proxy_1   |          at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
proxy_1   |          at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
proxy_1   |          at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
proxy_1   |          at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
proxy_1   |          at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
proxy_1   |       --- End of stack trace from previous location where exception was thrown ---
proxy_1   |          at System.Net.Security.SslStream.ThrowIfExceptional()
proxy_1   |          at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
proxy_1   |          at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
proxy_1   |          at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
proxy_1   |          at System.Net.Security.SslStream.<>c.<AuthenticateAsClientAsync>b__65_1(IAsyncResult iar)
proxy_1   |          at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
proxy_1   |       --- End of stack trace from previous location where exception was thrown ---

Solution

  • It's hard to give a definite answer, but I think that the problem lies in the certificate you generate. Try adding the -addext parameter with subject alternative name set to your target hostname. For example, for 'example.com' domain:

    openssl req -x509 -new -nodes -key rootCert.key -sha256 -days 1024 \
        -out rootCert.crt -addext "subjectAltName = DNS:example.com"
    

    Alternatively, if you'd like to use a root certificate, you may run Titanium service with no rootCert.pfx file and it should generate one. You may then extract the PEM cert and add it to ca-certificates:

    openssl pkcs12 -in rootCert.pfx -out rootCert.crt -clcerts -nokeys
    

    After having trusted the root certificate, all certificates generated by Titanium should work.