IDENTITY AND TRUST · LAB
Run every certificate check, then read each failure
Perform each check from the lesson's table against your tenant host, look up revocation by hand, read real failures from public test hosts, and replace turning verification off with trusting exactly one root.
ReadyUses your lab tenant
The lesson
Builds on: Certificate authorities and chains.
New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.
Setup
You need the tenant chain files and the local private PKI from the previous lab, openssl 3.x and curl. Step 8 connects to badssl.com, a public site that serves deliberately broken certificates for testing. Nothing in the tenant changes.
cd ~/btl-pki-lab
ISSUER=https://tenant-<id>.beyondthelogin.dev
HOST=${ISSUER#https://}
Walkthrough
Chain to a trust anchor:
openssl s_client -connect "$HOST:443" -servername "$HOST" -verify_return_error </dev/null 2>/dev/null | grep 'Verify return code'
Expected: 0 (ok).
Validity period, for every certificate the server sent:
for f in chain-*.pem; do echo "== $f"; openssl x509 -in "$f" -noout -dates; openssl x509 -in "$f" -noout -checkend 0; done
Name. Tell the library which name you intended, then a wrong one:
openssl s_client -connect "$HOST:443" -servername "$HOST" -verify_hostname "$HOST" </dev/null 2>/dev/null | grep 'Verify return code'
openssl s_client -connect "$HOST:443" -servername "$HOST" -verify_hostname "wrong.$HOST" </dev/null 2>/dev/null | grep 'Verify return code'
Expected: 0 (ok), then 62 (hostname mismatch).
Why it matters: the name check works only when the client knows which name it meant. Code that connects by IP address or builds a socket by hand may skip it without noticing.
Permitted use:
openssl verify -purpose sslserver -untrusted chain-2.pem chain-1.pem
openssl verify -purpose smimesign -untrusted chain-2.pem chain-1.pem
Expected: OK, then a failure with unsupported certificate purpose.
CA constraints. Confirm that every issuer in the chain has
CA:TRUEand the leaf hasCA:FALSE, as you saw in the previous lab:for f in chain-*.pem; do openssl x509 -in "$f" -noout -ext basicConstraints; done.Revocation, by hand. Find where the CA publishes status:
openssl x509 -in chain-1.pem -noout -ext crlDistributionPoints,authorityInfoAccess
If a CRL address is listed, download the list and look for your leaf's serial number:
curl -s -o leaf.crl <CRL URL>
openssl crl -inform DER -in leaf.crl -noout -lastupdate -nextupdate
SERIAL=$(openssl x509 -in chain-1.pem -noout -serial | cut -d= -f2)
openssl crl -inform DER -in leaf.crl -noout -text | grep -ci "$SERIAL"
Expected: 0, meaning not revoked. If an OCSP address is listed instead, try openssl ocsp -issuer chain-2.pem -cert chain-1.pem -url <OCSP URL> -resp_text | grep 'Cert Status'. Write down which mechanism your tenant's CA offers; some CAs no longer run OCSP at all.
Why it matters: revocation is a separate lookup that many clients skip or soft-fail, which is why falling certificate lifetimes carry so much of the weight.
Proof of the private key. Try to start a server with your local certificate and the wrong key:
openssl s_server -accept 8443 -cert leaf.pem -key root.key -www
Expected: it refuses to start with a key values mismatch.
Why it matters: a copied certificate is useless without the matching private key, and the TLS handshake is where the server proves it holds that key.
Read real failures:
for h in expired wrong.host self-signed untrusted-root; do echo "== $h"; curl -sS -o /dev/null "https://$h.badssl.com/" 2>&1 | head -2; done
Map each message to the check in the lesson's table that failed: validity period, name, and chain to a trust anchor (twice).
Turning verification off, compared with trusting one root:
curl -s -o /dev/null -w '%{http_code}\n' --insecure https://self-signed.badssl.com/
openssl s_server -accept 8443 -cert leaf.pem -key leaf.key -cert_chain int.pem -www &
curl -s -o /dev/null -w '%{http_code}\n' --cacert myroot.pem https://localhost:8443/
The first "works", and would equally accept an attacker's certificate. The last is verified, trusting only your root for that connection.
Why it matters: the fix gives the client exactly the trust it lacks. btl-lab verify fetches your tenant's key set over verified HTTPS; with verification turned off, anyone on the path could hand it their own signing keys.
Break it
The deliberate failures above cover the hostname mismatch (62), a missing intermediate (20, previous lab) and the purpose mismatch. For an expired certificate, issue a local leaf that is already out of date and verify it:
openssl x509 -req -in leaf.csr -CA int.pem -CAkey int.key -out old.pem -not_after 20200101000000Z -extfile leaf.ext 2>/dev/null \
|| curl -sS -o /dev/null https://expired.badssl.com/
openssl verify -CAfile myroot.pem -untrusted int.pem old.pem
Expected: certificate has expired (error 10). If your OpenSSL lacks -not_after, the expired.badssl.com request shows the same check failing.
Check your work
This lab does not touch the tenant's services, so there are no tenant events to check. You should have:
verify return codes 0, 62, 20 and 10, each matched to its check
the revocation status of your tenant's leaf and the mechanism (CRL or OCSP) you used
the refused server start with a mismatched key
Cleanup
Stop the local server with
kill %1. Deleteleaf.crlandold.pem.Keep the lab CA files (
myroot.pem,int.pem,int.key) andtenant.pemfor the last certificate lab.