Diagnose TLS failures

Diagnose protocol and key failures

Separate no-shared-protocol failures, unsupported algorithms, and certificate/private-key mismatches.

Not every TLS failure is about certificates. A handshake can die before certificate verification even starts, when the two sides can’t agree on a protocol version. And a server can refuse to start at all when its key doesn’t match its certificate. These are different problems with different errors. Telling them apart saves you from debugging the wrong layer.

No shared protocol

An old client that only speaks TLS 1.0 can’t talk to a server that requires TLS 1.2 or newer. The handshake fails right away, before any certificate is exchanged. You can reproduce the shape of this by forcing a version:

openssl s_client -tls1_2 -connect flaviocopes.com:443 -servername flaviocopes.com </dev/null

That one works, because the server still accepts TLS 1.2. Now force TLS 1.1:

openssl s_client -tls1_1 -connect flaviocopes.com:443 -servername flaviocopes.com </dev/null
# error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available
# no peer certificate available

Here my own OpenSSL refuses before even connecting, because TLS 1.1 is disabled at its security level. A server that rejects your version answers with an alert like tlsv1 alert protocol version instead. Either way, notice the second line: no certificate arrived. If you never received a certificate, stop staring at certificate fields. The problem is a layer below.

Key does not match certificate

The other failure lives on the server. After a renewal or a copy-paste deployment, the certificate on disk and the private key on disk can come from different generations. Node refuses to start with key values mismatch. nginx says SSL_CTX_use_PrivateKey_file ... key values mismatch.

You can confirm the mismatch without exposing any secret. A key pair matches when both files contain the same public key, so we compare public key fingerprints:

openssl x509 -in app.crt -noout -pubkey | openssl sha256
# SHA2-256(stdin)= f98d75aa775011a3d18886f1fc1d611af640fda49045a0302a315afb1d0a1a71
openssl pkey -in app.key -pubout | openssl sha256
# SHA2-256(stdin)= f98d75aa775011a3d18886f1fc1d611af640fda49045a0302a315afb1d0a1a71

The first command extracts the public key from the certificate. The second derives it from the private key. Same hash, same pair. Run the second command against lab-intermediate.key and you get a completely different hash, which is exactly what a mismatched deployment looks like. Find the key that was generated with this certificate’s CSR.

The tempting wrong fix

When an ancient device can’t connect, someone will suggest re-enabling TLS 1.0 on the server. Don’t. That lowers the floor for every connection, not just for that one client. Upgrade the client, or isolate it behind a dedicated proxy that only it uses, while the main endpoint keeps a modern minimum.

Lesson completed