Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

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

  1. 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).

  1. 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
  1. 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.

  1. 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.

  1. CA constraints. Confirm that every issuer in the chain has CA:TRUE and the leaf has CA:FALSE, as you saw in the previous lab: for f in chain-*.pem; do openssl x509 -in "$f" -noout -ext basicConstraints; done.

  2. 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.

  1. 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.

  1. 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).

  1. 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

  1. 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

  1. Stop the local server with kill %1. Delete leaf.crl and old.pem.

  2. Keep the lab CA files (myroot.pem, int.pem, int.key) and tenant.pem for the last certificate lab.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab