1

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 ---

1 Answers1

1

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.

Sebastian
  • 3,764
  • 21
  • 28