Inspect and tighten a TLS service

applied · 40 min · Objective 3.5

Task

Stand up a TLS service, inspect it from the outside the way an auditor would, find the three failures that pass in a browser and fail everywhere else, and tighten the protocol and cipher configuration. Then confirm an old client is correctly refused.

Steps

  1. Generate a self-signed certificate and configure nginx to use it. Confirm with openssl s_client -connect localhost:443 that the connection is encrypted, and state what the self-signed certificate does NOT provide.
  2. Create the incomplete-chain failure deliberately: install a leaf certificate without its intermediate. Show that a browser you have visited the CA from works, while curl reports "unable to get local issuer certificate".
  3. Create the SAN failure: issue a certificate with the hostname only in the Common Name, not the SAN. Browsers reject it (Chrome since version 58) and so does Go (since 1.15), while openssl x509 -text still shows the expected name. Now try your command-line tools and notice they do NOT reject it: openssl s_client -verify_hostname, curl and Python's ssl all still fall back to the Common Name when there is no SAN. That is the trap -- the certificate passes every check you can run from a shell and fails for your users. To see the failure from a shell, switch the fallback off: python3 -c "import ssl,socket; c=ssl.create_default_context(cafile='lab.crt'); c.hostname_checks_common_name=False; c.wrap_socket(socket.create_connection(('localhost',443)), server_hostname='localhost')" raises "Hostname mismatch".
  4. Create the key-mismatch failure: install a certificate alongside the wrong private key. Confirm nginx refuses to start, and diagnose it by comparing the two modulus hashes.
  5. Tighten the configuration to TLS 1.2 and 1.3 only, with a modern cipher list. Confirm the SERVER now refuses TLS 1.1 -- which takes more care than it looks. A modern OpenSSL client will not offer TLS 1.1 at its default security level, so a plain openssl s_client -tls1_1 fails with "no protocols available" against ANY server, hardened or not: the refusal is the client's own. Let the client try, and read who said no: openssl s_client -connect localhost:443 -tls1_1 -cipher 'DEFAULT:@SECLEVEL=0'. A hardened server answers alert protocol version; a server that still accepts 1.1 completes the handshake. Run it once BEFORE step 5 to see the second, then after to see the first.
  6. Enumerate what the server actually accepts with nmap --script ssl-enum-ciphers -p 443 localhost and confirm no weak suites remain.
  7. On RHEL, set this system-wide with update-crypto-policies --set FUTURE and observe that it affects every TLS service at once.

Verify

echo | openssl s_client -connect localhost:443 2>/dev/null | openssl x509 -noout -dates
openssl s_client -connect localhost:443 -tls1_1 -cipher 'DEFAULT:@SECLEVEL=0' </dev/null 2>&1 | grep -qi 'alert protocol version' && echo "server refuses TLS 1.1"
openssl x509 -noout -modulus -in /etc/pki/tls/certs/lab.crt | openssl md5
openssl rsa -noout -modulus -in /etc/pki/tls/private/lab.key | openssl md5
nmap --script ssl-enum-ciphers -p 443 localhost 2>/dev/null | grep -ci 'sslv3\|tlsv1.0\|tlsv1.1'   # 0

The two modulus hashes must match, and the SERVER must refuse the old-protocol connection -- alert protocol version is the server speaking; "no protocols available" is only your own client, and proves nothing about the server. The nmap count of weak protocols must be zero. Steps 2 to 4 are the cases an auditor meets weekly, and they do not all fail in the same place: the incomplete chain passes a browser and fails curl, while the CN-only certificate does the opposite -- it passes curl and fails a browser. Test with more than one kind of client.

Notes

The incomplete chain is the nastiest of the three because the obvious test -- open it in a browser -- passes, since browsers cache intermediates from previous sites. curl and every server-to-server call do not. Always test with something other than a browser.

This is an independent study companion for CompTIA Linux+ and is not produced by or endorsed by CompTIA.