nginx proxy_pass with a backend requesting client certificates

We're proxying a backend that we don't control & the backend is requesting a client certificate when connecting on https.

When we request these pages through the proxy the connection times out with no response from the backend, however the pages work fine if we bypass the proxy. Is this a problem with nginx handling the client certificate or should we be looking elsewhere?

Nginx config:

server {
        listen 443 ssl;

        ssl on;
        ssl_certificate /etc/nginx/ssl/webserver.pem;
        ssl_certificate_key /etc/nginx/ssl/webserver.key;

        location / {
                 proxy_pass https://www.backendsite.com;
                 proxy_set_header X-Forwarded-For $http_x_forwarded_for;
        }
}

I'm not sure whether you realize this or not, but what you're doing is a Man-In-The-Middle attack on www.backendsite.com.

To understand why this can't work in your scenario we are going to need to explain the actual role of certificates and keys in TLS. So here we go.

First of all, we need to establish a key in the clear. We don't have and for efficiency reasons can't have a secure channel, so we use Diffie Hellman as our key exchange protocol. Previously we might have established parameters we use. Modern thinking says that's a bad idea, let's make some up as we go. This is called DHE (Ephemeral Diffie Hellman). If you're feeling really fancy you might do this over EC (EC-DHE).

Now, the question is, how do you know I sent you the parameters I did, using DH? To verify that, we need a digital signature algorithm so you can have some public information about me to verify that the signature made with my private key can only be made by me.

Server key material provides that for the server end - a private key and a public key (contained in the certificate, along with signatures by the CA saying yes, we trust this too).

NORMALLY, the client can just generate a public/private key pair for anonymous connections as it feels like. So under usual circumstances:

  • Client has any old key pair.
  • She establishes a working connection with your proxy. Traffic is encrypted/decrypted over this tunnel.
  • The proxy has it's own "any old key pair" with which it establishes a connection with the server.
  • The server doesn't much care who the client is, it'll establish a connection with just about anyone, so it uses your proxy's public key.
  • The session is established.

However, now the client provides a specific key pair. This key pair is used to protect the parameters of the protocol, so here's what happens:

  • Client has a specific key pair, asks for TLS and sends its parameters and public key.
  • Proxy decrypts and can re-package this one of two ways (I don't know which of these would actually happen - depends on implementation - I am just explaining why this cannot work):
    • It supplies the public certificate of the client instead of its own. Not having the private key, it is unable to decrypt any of the session responses.
    • So in a sulk (again this is not actually what happens, just an illustration) and generates its own certificates. However the server won't talk to certificates except those on a given criteria (e.g. signed by some known cert) and so refuses to negotiate the connection.

The reason this doesn't quite work is because nginx is trying to act as a http proxy, taking off and re-applying https as needed, and with trusted certificates established at both ends TLS is preventing MITM by design.

There are some alternative design decisions engineers can take where the backend is under their control. I ended up here because I wished to proxy to a unix socket passing some certificate information onwards. This can be done using these notes on authenticating certificates with nginx and this observation on HTTP headers (don't pass the entire raw certificate).