Sunday, October 6, 2019

Setting up SSL EC/RSA client certificates for Mac, Android connecting to nginx

Update: Android Chrome's failure to use ECDSA client certificates turns out to be a chrome bug, fixed here.

Some notes about getting SSL client certificates working for Mac and Android clients, connecting to an nginx server.

Configuring nginx

To configure nginx, add the following to the site configuration:

server {
        listen xxxx ssl;

        ...

        ssl_certificate /etc/ssl/certs/server.crt;
        ssl_certificate_key /etc/ssl/private/server.key;

        ssl_client_certificate /etc/ssl/ca/ca-chain.crt;
        ssl_crl /etc/ssl/ca/crl.pem;
        ssl_verify_client on;

        ssl_session_timeout 5m;

        ...
}

I also used ssllabs.com/ssltest/analyze.html to test the exposed server. It complained about the ciphers and protocols I was exporting, so I made this snippet:

# I started with the list of ciphers from
# https://www.acunetix.com/blog/articles/tls-ssl-cipher-hardening/
# (2019-04-10), then removed the ones deemed weak by ssllabs.com/ssltest.
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

# The order of cipher suites matters
ssl_prefer_server_ciphers on;

That made it pretty happy.

Making Certificates

This site has a wonderful introduction to the world of root and intermediate CAs, how to create them, how to create client certificates, how to sign them, and so on. It shows how to create RSA certificates. To create EC certificates instead, substitute the following for his genrsa commands:

openssl ecparam -genkey -name prime256v1 | \
  openssl ec -aes256 -out foo/bar.key.pem 


That creates a key with a password. To create one without a password, lose the -aes256 flag. (source)

Deploying to clients

Several pages talk about distributing PEM files, but I didn't have much luck with that. Instead, bundle them up as PKCS12 files.

openssl pkcs12 \
  -export \
  -out intermediate/private/${CLIENT_ID}.full.pfx \
  -inkey intermediate/private/${CLIENT_ID}.key.pem \
  -in intermediate/certs/${CLIENT_ID}.cert.pem \
  -certfile intermediate/certs/ca-chain.cert.pem
(source, adapted for the directory layout used here)

Deploying to Macs

Mac Chrome really seems to want to be restarted when you update certificates. If you don't, it'll still tag sites as untrusted even as it's saying the certificate is valid.

Deploying to Android

Android (at least this early Android 10 build) does not like EC certificates. It only works with RSA certificates. Or I'm holding it wrong. The rest of this subsection goes into detail about the error messages in an attempt to get those messages indexed so anyone else with this problem doesn't have to spend the time I spent figuring it out.

Regardless, if you install an EC certificate, an nginx server configured as above will serve an error that says "No required SSL certificate was sent". Stick Wireshark in the middle, and it confirms that no certificates ("Handshake Protocol: Certificate", "Certificates Length: 0") were sent.

If you run adb logcat on the phone while it's trying to connect, you'll see something like this:

10-06 16:01:35.633 14828 15243 I KeyChain: Inspecting certificate CN=simmonmt-pixel2,O=simmonmt,ST=New York,C=US aliased with 4f90d81f11a3f93a65a8fb5cb77f24a5dbf33b4c
10-06 16:01:35.635 14828 15243 I KeyChain: Certificate key algorithm: EC
10-06 16:01:35.650 10774 10774 E chromium: [ERROR:ssl_client_certificate_request.cc(337)] No client certificate selected

Looking at the source, there should've been a log message about the certificate issuer about the one after the one about the key algorithm. The only way that doesn't happen is if the certificate algorithm isn't one of the ones the caller (Chrome in this case, I assume) has said it'll accept.

Change to an RSA certificate and these problems all go away.