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
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 4f90d81f11a3f93a65a8fb5cb77f24 a5dbf33b4c
10-06 16:01:35.635 14828 15243 I KeyChain: Certificate key algorithm: EC
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.