ssl client auth trouble

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

ssl client auth trouble

AJ Weber

I have been trying to configure client certificates (really just one cert for now) for two days on CentOS 7, Nginx 1.16.1, and have had very limited success.

I have tried various online guides and they are mostly the same - but all have resulted in the same exact scenario.  One such guide is here, for example: https://gist.github.com/mtigas/952344

(Another is here: https://www.guyrutenberg.com/2015/09/15/securing-access-using-tlsssl-client-certificates/)

When this is all done, and I import the p12 client certificate on my Windows PCs (tested 2) Chrome and Firefox show me the "400 Bad Request\n No required SSL certificate was sent".  The very strange thing is IE11 on one of the two PCs, actually prompts me to use my newly-installed cert the first time, and it works.  No other browser (including IE on a different PC) works.

I have exhausted my Google-foo and am frustrated.  I don't think this should be so hard.

Does anyone have any suggestions to troubleshoot this?

Thanks.


_______________________________________________
nginx mailing list
[hidden email]
http://mailman.nginx.org/mailman/listinfo/nginx
Reply | Threaded
Open this post in threaded view
|

RE: ssl client auth trouble

Reinis Rozitis
> When this is all done, and I import the p12 client certificate on my Windows PCs (tested 2) Chrome and Firefox show me the "400 Bad Request\n No required SSL certificate was sent".  The very strange thing is IE11 on one of the two PCs, actually prompts me to use my newly-installed cert the first time, and it works.  No other browser (including IE on a different PC) works.


Afaik Chrome uses Windows certificate store (and iirc as of FF49 there is an optional setting for firefox too) so if IE11 works it could be that rather than nginx configuration it is browser related.

For example - some time ago when I had to implement client certificate authentication myself one such caveat turned out to be how Chrome handles http2 - I had several virtualhosts,  but client auth only for one domain and it randomly didn't work. When I inspected the http2 stream I noticed that if the resolved IP for the domain matched an existing connection Chrome happily reused/pipelined the request through it without sending the certificate.
When the particular domain was placed on a separate ip everything started to work as expected. While there might not be a technical issue for such behavior (not sure?) it wasn't very obvious at first.


I would suggest to share at least minimal nginx configuration snippet - it's hard to help without that.

Maybe try with ssl_verify_client optional_no_ca; - depending on how the client certificate was created/signed there might be intermediate CAs (not sure if you followed the guides directly about self-made CAs etc) and then the default ssl_verify_depth 1; would also fail at verification.
Also log if $ssl_client_s_dn / $ssl_client_escaped_cert actually contain anything.

rr

_______________________________________________
nginx mailing list
[hidden email]
http://mailman.nginx.org/mailman/listinfo/nginx
Reply | Threaded
Open this post in threaded view
|

Re: ssl client auth trouble

AJ Weber

On 8/30/2019 12:33 PM, Reinis Rozitis wrote:
>> When this is all done, and I import the p12 client certificate on my Windows PCs (tested 2) Chrome and Firefox show me the "400 Bad Request\n No required SSL certificate was sent".  The very strange thing is IE11 on one of the two PCs, actually prompts me to use my newly-installed cert the first time, and it works.  No other browser (including IE on a different PC) works.
>
> Afaik Chrome uses Windows certificate store (and iirc as of FF49 there is an optional setting for firefox too) so if IE11 works it could be that rather than nginx configuration it is browser related.
The tricky thing there is that it works on one PC's IE and not another. 
But you are correct, Chrome does use the Windows cert store.  I have
checked a dozen times that the cert is in there as correctly as I can
surmise.  And when the initial tests show that 1 out of 5 browsers are
successful, it is not something I can go forward with before I solve it. :)
>
> For example - some time ago when I had to implement client certificate authentication myself one such caveat turned out to be how Chrome handles http2 - I had several virtualhosts,  but client auth only for one domain and it randomly didn't work. When I inspected the http2 stream I noticed that if the resolved IP for the domain matched an existing connection Chrome happily reused/pipelined the request through it without sending the certificate.
> When the particular domain was placed on a separate ip everything started to work as expected. While there might not be a technical issue for such behavior (not sure?) it wasn't very obvious at first.
>
>
> I would suggest to share at least minimal nginx configuration snippet - it's hard to help without that.

I can do that.  This is initial setup of nginx. The default nginx.conf
has only been edited by certbot (trying Lets Encrypt), and I have zero
virtual hosts...in fact nothing in default.d/

>
> Maybe try with ssl_verify_client optional_no_ca; - depending on how the client certificate was created/signed there might be intermediate CAs (not sure if you followed the guides directly about self-made CAs etc)

I tried following both of those links precisely.  From my eye, they both
do the same exact set of steps...just some syntactically separated.

The client cert appears to be signed correctly with the output of
"openssl verify -verbose -CAfile ..."

> and then the default ssl_verify_depth 1; would also fail at verification.
I actually set this to 2 based on a recommendation in SO post, but it
did not make a difference either way.
> Also log if $ssl_client_s_dn / $ssl_client_escaped_cert actually contain anything.

I will search for this.  Not sure how to add this info to my logs, or
whether it logs failures too?


Thank you for your help!

_______________________________________________
nginx mailing list
[hidden email]
http://mailman.nginx.org/mailman/listinfo/nginx
Reply | Threaded
Open this post in threaded view
|

RE: ssl client auth trouble

Reinis Rozitis
> I will search for this.  Not sure how to add this info to my logs, or
> whether it logs failures too?

$ssl_client_verify - contains the verification status


You have to define a custom log_format (http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format )

For example:

log_format clientcerts '$remote_addr $ssl_client_s_dn $ssl_client_verify';
access_log  logs/ssl.log clientcerts;


.. and you can add whatever variables to better identify what's going on  http://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables 

rr

_______________________________________________
nginx mailing list
[hidden email]
http://mailman.nginx.org/mailman/listinfo/nginx
xrd
Reply | Threaded
Open this post in threaded view
|

Re: ssl client auth trouble

xrd
In reply to this post by AJ Weber
I'm a big fan of throw-away certificates, i.e., self-signed certificates you
may dispose of any time. It seems, the generation of proper certificates is
still a mystery to some, so let me briefly include a recipe how to create
them:

Create a cert-client.conf of the following form:

---------------------snip-----snip------------------------
# Client certificate request

[ req ]
default_bits = 4096 # RSA key size
encrypt_key = yes # Protect private key
default_md = sha256 # MD to use
utf8 = yes # Input is UTF-8
string_mask = utf8only # Emit UTF-8 strings
prompt = no # Prompt for DN
distinguished_name = email_dn # DN template
req_extensions = email_reqext # Desired extensions

[ email_dn ]
0.domainComponent       = "com"
1.domainComponent       = "example"
2.domainComponent       = "project"
organizationName = "{{ORG}}"
organizationalUnitName  = "{{CUSTOMER}}"
commonName              = "{{NAME}}"
emailAddress            = "{{EMAIL}}"

[ email_reqext ]
keyUsage                = critical,digitalSignature,keyEncipherment
extendedKeyUsage        = emailProtection,clientAuth
subjectKeyIdentifier    = hash
subjectAltName          = email:{{EMAIL}}
---------------------snip-----snip------------------------

Replace project.example.com and the stuff in {{...}} accordingly. I keep the
domain components usually fixed for a project, while the rest is dynamically
replaced with sed.

Assume, you have an instance of cert-client.conf replaced properly, and it's
in wherever the value of $conf points to. Assume $name is the name you want
to give to this certificate. Then you create it with

---------------------snip-----snip------------------------
openssl req -new -newkey rsa:4096 -x509 -days 365 -nodes -config $conf
-sha256 \
        -passout "pass:" -out "$name.pem" \
        -keyout "$name.key"
openssl pkcs12 -export \
        -inkey "$name.key" -in "$name.pem" \
        -passin "pass:" \
        -out "$name.pkcs12" \
        -passout "pass:" \
        -name "$name"
openssl x509 -text -noout -in "$name.pem" \
        -passin "pass:" > "$name.info"
---------------------snip-----snip------------------------

This creates a PEM file (public key), key file (private key), and pkcs12
file (certificate).

For Chrome and Firefox, use the respective configuration tool of the browser
to introduce this as a new personal certificate. For IE, it is sufficient to
import the certificate into the Windows certificate store.

For the server side of TLS, I usually use something of this sort:

---------------------snip-----snip------------------------
    # SAN certificates are defined once on the http context level
    #
    ssl_certificate       /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/private.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # TLSv1.3 requires openssl 1.1.1 or later. This tries to enable 0-RTT.
    #ssl_early_data on;

    # Prefer the faster Diffie Hellman Parameters over slower RSA
algorithms
    #
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_prefer_server_ciphers on;
    ssl_ecdh_curve secp384r1;
    # openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
    ssl_dhparam /etc/nginx/certs/dhparam.pem;

    # SSL session parameters
    #
    ssl_session_cache shared:SSL:36m;
    ssl_session_timeout 3m;
    ssl_session_tickets on;
    ssl_session_ticket_key /etc/nginx/certs/ticket.key;
    ssl_session_ticket_key /etc/nginx/certs/ticket.key.old;
---------------------snip-----snip------------------------

with fullchain.pem being the public keys of the certificate chain, starting
with the most specific, i.e., the server's SAN certificate. I use a little
awk script to re-arrange public keys in the proper order - from the output
of a "openssl pkcs7 -print_certs ..."

and with dhparam.pem generated by

openssl dhparam -out /etc/nginx/certs/dhparam.pem 4096

and ticket.key generated by

openssl rand 80 > /etc/nginx/ticket.key

Next thing is that TLS can only be switched on per server, not per location.
I generally don't like SNI, so I prefer to have SAN certificates for
servers.

---------------------snip-----snip------------------------
server {
        listen 443 ssl;
        server_name ...{all the names you need}...;
        ssl_trusted_certificate /etc/nginx/certs/clients.pem;
        ssl_verify_client optional_no_ca;
        ssl_verify_depth 1;

        ...
}
---------------------snip-----snip------------------------

will do the trick, if clients.pem is simply a concatenation of all .pem
files you have produced earlier (the public keys of clients who are supposed
to have access). Note that I use the directive "ssl_trusted_certificate", so
the list of permitted CAs is not communicated, but rather the client has to
present a certificate. There is no requirement to use any specific CA. As
each certificate is self-signed, the "CA" (issuer) of each certificate is
the certificate itself.

You may now check in the nginx.conf or in a Javascript handler what the
client status is:

$ssl_client_verify will be NONE if no certificate was presented, SUCCESS if
the client specified one of the permitted certificates, and FAILED... if
there was some failure.

Once this is done, you can check $ssl_client_s_dn to find out who was
authenticated. You may use a map directive to map this variable to the
e-mail address from the certificate's DN, or look at any one of the other
attributes. What you can query you'll find here:
https://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables

This should be a complete recipe on how to set up working self-signed client
certificates.

If this works, you may play with other options :-)

Cheers,
--j.

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

_______________________________________________
nginx mailing list
[hidden email]
http://mailman.nginx.org/mailman/listinfo/nginx