I am trying to enable client certificate authentication in nginx where the certificates have been signed by an intermediate CA. I am able to get this working fine when using a certificate signed by a self-signed root CA; however, this does not work when the signing CA is an intermediate CA.
My simple server section looks like this:
server { listen 443; server_name _; ssl on; ssl_certificate cert.pem; ssl_certificate_key cert.key; ssl_session_timeout 5m; ssl_protocols SSLv2 SSLv3 TLSv1; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; ssl_prefer_server_ciphers on; ssl_client_certificate ca.pem; ssl_verify_client on; ssl_verify_depth 1; location / { root html; index index.html index.htm; } }
For the contents of ca.pem, I have tried using only the intermediate CA and also concatenating the intermediate CA cert and the root CA cert, i.e. something like:
cp intermediate.crt ca.pem cat root.crt >> ca.pem
I have also validated that the certificate is valid from openssl's perspective when using that same CA chain:
openssl verify -CAfile /etc/nginx/ca.pem certs/client.crt certs/client.crt: OK
I have experimented with setting ssl_verify_depth explicitly to 1 (as above) and then even 0 (not sure what that number means exactly), but still get same error.
The error I get in all variants of the intermed CA is "400 Bad Request" and more specifically "The SSL certificate error" (not sure what that means exactly).
Maybe nginx just doesn't support cert chains for intermediate certs? Any help greatly appreciated!
What is an Intermediate Certificate? Any certificate that sits between the SSL/TLS Certificate and the Root Certificate is called a chain or Intermediate Certificate. The Intermediate Certificate is the signer/issuer of the SSL/TLS Certificate.
You can create a self-signed key and certificate pair with OpenSSL in a single command: sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned. key -out /etc/ssl/certs/nginx-selfsigned. crt.
Edit: I had also this "problem", solution and explanation is at the bottom of the text.
It seemed like nginx doesn't support intermediate certificates. My certs self created: (RootCA is selfsigned, IntrermediateCA1 is signed by RootCA, etc.)
RootCA -> IntermediateCA1 -> Client1 RootCA -> IntermediateCA2 -> Client2
I want to use in nginx "IntermediateCA1", to allow access to site only to owner of the "Client1" certificate.
When I put to "ssl_client_certificate" file with IntermediateCA1 and RootCA, and set "ssl_verify_depth 2" (or more) , clients can login to site both using certificate Client1 and Client2 (should only Client1). The same result is when I put to "ssl_client_certificate" file with only RootCA - both clients can login.
When I put to "ssl_client_certificate" file with only IntermediateCA1, and set "ssl_verify_depth 1" (or "2" or more - no matter) , it is imposible to log in, I get error 400. And in debug mode i see logs:
verify:0, error:20, depth:1, subject:"/C=PL/CN=IntermediateCA1/[email protected]",issuer: "/C=PL/CN=RootCA/[email protected]" verify:0, error:27, depth:1, subject:"/C=PL/CN=IntermediateCA1/[email protected]",issuer: "/C=PL/CN=RootCA/[email protected]" verify:1, error:27, depth:0, subject:"/C=PL/CN=Client1/[email protected]",issuer: "/C=PL/CN=IntermediateCA1/[email protected]" (..) client SSL certificate verify error: (27:certificate not trusted) while reading client request headers, (..)
I thing this is a bug. Tested on Ubuntu, nginx 1.1.19 and 1.2.7-1~dotdeb.1, openssl 1.0.1. I see that nginx 1.3 has few more options about using client certificates, but I'dont see solution to this problem.
Currently, the only one way to separate clients 1 and 2 is to create two, selfsigned RootCAs, but this is only workaround..
Edit 1: I've reported this issue here: http://trac.nginx.org/nginx/ticket/301
Edit 2" *Ok, it's not a bug, it is feature ;)*
I get response here: http://trac.nginx.org/nginx/ticket/301 It is working, you must only check what your ssl_client_i_dn is (. Instead of issuer you can use also subject of certificate, or what you want from http://wiki.nginx.org/HttpSslModule#Built-in_variables
This is how certificate verification works: certificate must be verified up to a trusted root. If chain can't be built to a trusted root (not intermediate) - verification fails. If you trust root - all certificates signed by it, directly or indirectly, will be successfully verified.
Limiting verification depth may be used if you want to limit client certificates to a directly issued certificates only, but it's more about DoS prevention, and obviously it can't be used to limit verificate to intermediate1 only (but not intermediate2).
What you want here is some authorization layer based on the verification result - i.e. you may want to check that client's certificate issuer is intermediate1. Simplest solution would be to reject requests if issuer's DN doesn't match one allowed, e.g. something like this (completely untested):
[ Edit by me, it is working correctly in my configuration ]
server { listen 443 ssl; ssl_certificate ... ssl_certificate_key ... ssl_client_certificate /path/to/ca.crt; ssl_verify_client on; ssl_verify_depth 2; if ($ssl_client_i_dn != "/C=PL/CN=IntermediateCA1/[email protected]") { return 403; } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With