Proxy_ssl_certificate not exchanging client certificates

I was excited to see proxy_ssl_certificate and friends land in Nginx
1.7.8,
and decided to revisit Nginx as a candidate for proxy caching an
upstream
server requiring client authentication. I’ve included the debugging
configuration I’ve been playing around with at the end of this post.

This particular upstream server does not trigger client authentication
for
all endpoints. For example, I can issue


http http://NGINX_PROXY_IP/test/path Host:UPSTREAM_SERVER

and get back the proxied response without error. However, for endpoints
that
require client authentication (triggered by the server after it examines
the
request path), nginx never gets a response. I’ve verified that the
upstream
server is working as expected using both wget:


wget --verbose --debug --save-headers -O -
–ca-certificate=PATH_TO_SERVER_CERTFICATE
–certificate=PATH_TO_CLIENT_CERTIFICATE
–private-key=PATH_TO_CLIENT_PRIVATE_KEY
https://UPSTREAM_SERVER/path/requiring/client/authentication

and openssl’s s_client:

echo -e “GET
https://UPSTREAM_SERVER/path/requiring/client/authentication
HTTP/1.0\r\n\r\n” | openssl s_client -ign_eof -connect
UPSTREAM_SERVER:443
-state -debug -cert PATH_TO_CLIENT_CERTIFICATE -key
PATH_TO_CLIENT_PRIVATE_KEY

In the latter case, it’s apparent that the server triggers renegotiation
after seeing the requested path, and openssl’s s_client responds
accordingly
by sending the client certificate, completing the exchange moments
later.
However, when invoking the same via the Nginx proxy:


http http://NGINX_PROXY_IP/
Host:UPSTREAM_SERVER/path/requiring/client/authentication

the upstream connection eventually closes after sending no data – the
same
behaviour it exhibits when no client certificates are provided via wget
or
openssl’s s_client.

I’m using nginx.debug and have verified that the client certificate
files
are read (throwing errors if the wrong path is specified). I’ve also
used
ltrace and verified that Nginx appears to inject the client certificate
via
SSL_CTX_use_certificate (taking a somewhat different path than wget’s
SSL_CTX_use_certificate_file).

Have I failed to configure Nginx with the requisite client certificates?
Is
there anyway to see the level of debugging from openssl’s s_client but
via
Nginx?

Here’s the configuration in question:


user nginx;
worker_processes 1;
daemon off;

error_log /var/log/nginx/error.log debug;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
proxy_cache_methods GET HEAD;
proxy_cache_min_uses 1;

server {
    listen 80;

    proxy_http_version 1.1;
    proxy_ssl_protocols 'TLSv1.2';
    proxy_ssl_certificate PATH_TO_CLIENT_CERTIFICATE;
    proxy_ssl_certificate_key PATH_TO_CLIENT_PRIVATE_KEY;
    proxy_ssl_session_reuse off;
    proxy_ssl_trusted_certificate PATH_TO_SERVER_CERTFICATE;
    proxy_ssl_verify on;

    location / {
        resolver 8.8.8.8;
        proxy_pass https://$host$request_uri;
        proxy_cache rest-cache;

        add_header rt-Fastcgi-Cache $upstream_cache_status;
        proxy_set_header Host $host;
        proxy_ignore_headers Set-Cookie;
    }
}

}

Hello!

On Tue, Apr 28, 2015 at 05:17:32PM -0400, lieut_data wrote:


and get back the proxied response without error. However, for endpoints that
require client authentication (triggered by the server after it examines the
request path), nginx never gets a response. I’ve verified that the upstream
server is working as expected using both wget:

What nginx doesn’t support (or, rather, explicitly forbids) is
renegotiation. On the other hand, renegotiation is required if
one needs to ask for a client certificate only for some URIs, so
it’s likely used in your case. You should see something like “SSL
renegotiation disabled” in logs at notice level.


Maxim D.
http://nginx.org/

Thanks for getting back to me so quickly!

Maxim D. Wrote:

What nginx doesn’t support (or, rather, explicitly forbids) is
renegotiation. On the other hand, renegotiation is required if
one needs to ask for a client certificate only for some URIs, so
it’s likely used in your case. You should see something like “SSL
renegotiation disabled” in logs at notice level.

Yes, this is exactly the problem. With your hint, I commented out the
relevant code in ngx_ssl_handshake and ngx_ssl_handle_recv – and
proxying
worked flawlessly. (Interestingly, I never saw the log you identified
because of SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS having been set on the
openssl
connection object.)

I think I understand the gist of why nginx forbids client-initiated
renegotiation (denial of service concerns? security concerns?), but I’m
not
well-versed in openssl enough to know if the same concerns apply to
server-initiated renegotiation with nginx as the client, especially when
it
applies to cipher renegotiation as noted above.

Would nginx be open to a patch that would make this use case feasible?
Perhaps as a modification to only disable these renegotiations when
nginx is
the server in the SSL equation?

Posted at Nginx Forum:

Hello!

On Wed, Apr 29, 2015 at 05:09:26PM -0400, lieut_data wrote:

Yes, this is exactly the problem. With your hint, I commented out the

Would nginx be open to a patch that would make this use case feasible?
Perhaps as a modification to only disable these renegotiations when nginx is
the server in the SSL equation?

The renegotiation is disabled for security reasons since
CVE-2009-3555. While CVE-2009-3555 is believed to be mitigated by
Secure Renegotiation extension, renegotiation itself, even secure,
still allows various bad side effects if allowed: in particular,
peer credentials and/or ciphers used may be changed unexpectedly.

I don’t think we care much about the above happening on an
upstream connection though, so patches are welcome. (Actually,
original patch I submitted back in 2009 did not touch upstream
connections at all, but Igor decided to disable renegotiation
completely.)


Maxim D.
http://nginx.org/