$ssl_client_escaped_cert does not contain intermediate client

E
  • 4 Jul '20
I have the following certificate chain: Root certificate > Intermediate
certificate > End user certificate. 

I've set up nginx as an SSL termination proxy for a backend service that
differentiates it actions based on the serial of the intermediate
certificate and the subject of the end user certificate. Only the root
certificate is available at the (nginx) server, the client will present the
intermediate + end user certificate.

Relevant nginx configuration is as follows:

ssl_client_certificate root_cert.pem; # so only the root certificate
ssl_verify_client on;
ssl_verify_depth 2;

proxy_set_header X-Ssl-Client-Escaped-Cert $ssl_client_escaped_cert; # to
pass it on to the backend service

Connectivity works great: nginx accepts the request if the client (I'm
testing with curl) presents intermediate + end user certificate and passes
it on to the backend service. If the client presents only one of the
certificates, nginx rightly rejects it. So I'm sure curl shares both
certificates with nginx.

Where it goes wrong, is when nginx passes the certificate information to the
backend service. The embedded variable $ssl_client_escaped_cert only seems
to contain the end user certificate and not the intermediate one(s). I did
some logging to check $ssl_client_raw_cert, but that also only contains the
end user certificate. 

Is there a way to get the intermediate client certificates included in these
embedded variables?

Posted at Nginx Forum: https://forum.nginx.org/read.php?2,288553,288553#msg-288553
M
  • 6 Jul '20
Hello!

On Sat, Jul 04, 2020 at 05:52:09AM -0400, everhardt wrote:

> I have the following certificate chain: Root certificate > Intermediate
> certificate > End user certificate. 
> 
> I've set up nginx as an SSL termination proxy for a backend service that
> differentiates it actions based on the serial of the intermediate
> certificate and the subject of the end user certificate. Only the root
> certificate is available at the (nginx) server, the client will present the
> intermediate + end user certificate.
> 
> Relevant nginx configuration is as follows:
> 
> ssl_client_certificate root_cert.pem; # so only the root certificate
> ssl_verify_client on;
> ssl_verify_depth 2;
> 
> proxy_set_header X-Ssl-Client-Escaped-Cert $ssl_client_escaped_cert; # to
> pass it on to the backend service
> 
> Connectivity works great: nginx accepts the request if the client (I'm
> testing with curl) presents intermediate + end user certificate and passes
> it on to the backend service. If the client presents only one of the
> certificates, nginx rightly rejects it. So I'm sure curl shares both
> certificates with nginx.
> 
> Where it goes wrong, is when nginx passes the certificate information to the
> backend service. The embedded variable $ssl_client_escaped_cert only seems
> to contain the end user certificate and not the intermediate one(s). I did
> some logging to check $ssl_client_raw_cert, but that also only contains the
> end user certificate. 
> 
> Is there a way to get the intermediate client certificates included in these
> embedded variables?

No.  Futher, intermediate certs as sent by the client are not 
saved by the OpenSSL into session information, so the approach you 
are trying to use is not going to work at all, more or less 
universally (or at least it won't work with session resumption).  
For things to work, you may want to reconsider the approach and 
make sure all intermediate certificates are known on the server 
instead.

-- 
Maxim Dounin
http://mdounin.ru/
E
  • 6 Jul '20
Thanks for your reply, Maxim! I'll work out an alternative then. 

Re. session resumption, I read in the OpenSSL docs
(https://www.openssl.org/docs/man1.1.0/man3/SSL_get0_verified_chain.html)
that OpenSSL is willing to store the chain longer than a single request, but
only if the implementing application (nginx) is managing freeing it at the
proper time (eg. when the session times out):
> If applications wish to use any certificates in the returned chain
indefinitely they must increase the reference counts using X509_up_ref() or
obtain a copy of the whole chain with X509_chain_up_ref().

ps. I now see that HAProxy is also discussing it:
https://www.mail-archive.com/haproxy at formilux.org/msg35607.html

Posted at Nginx Forum: https://forum.nginx.org/read.php?2,288553,288596#msg-288596
M
  • 7 Jul '20
Hello!

On Mon, Jul 06, 2020 at 03:55:05PM -0400, everhardt wrote:

> Thanks for your reply, Maxim! I'll work out an alternative then. 
> 
> Re. session resumption, I read in the OpenSSL docs
> (https://www.openssl.org/docs/man1.1.0/man3/SSL_get0_verified_chain.html)
> that OpenSSL is willing to store the chain longer than a single request, but
> only if the implementing application (nginx) is managing freeing it at the
> proper time (eg. when the session times out):
> > If applications wish to use any certificates in the returned chain
> indefinitely they must increase the reference counts using X509_up_ref() or
> obtain a copy of the whole chain with X509_chain_up_ref().

This quote is about how to use the chain if it is returned.  The 
problem is that the chain is _not_ returned for resumed sessions, 
and there is no way to obtain it for a resumed session as long as 
the chain uses intermediate certificates provided by the client.  
Saving the chain somewhere once session is established may work as 
a band-aid in some simple cases, but certainly not an option in 
general for multiple reasons, including the fact that this won't 
work with TLS session tickets when there is no server-side state.

-- 
Maxim Dounin
http://mdounin.ru/
E
  • 7 Jul '20
Hi Maxim,

I, naively maybe, thought the following would work. At an incoming request,
nginx checks whether the session is new or resumed. 
* new: it retrieves the chain, calls X509_chain_up_ref and stores a mapping
from session ID to the chain pointer
* resumed: it retrieves the session ID, looks up the pointer from the
mapping and retrieves the chain from the pointer

At session timeout nginx should drop the session ID from the mapping and
calls X509_free on each certificate in the chain.

Best,
Rob

Posted at Nginx Forum: https://forum.nginx.org/read.php?2,288553,288600#msg-288600