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

Issue, renew and revoke certificates with a lab CA

Create a CSR as in the lesson, issue and renew it from your local lab CA, check the real domain records and expiry of your tenant certificate, and revoke after a key exposure.

Partly readyUses your lab tenant

The lesson

Builds on: Validating a certificate.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Partly ready. Most of this lab runs today. Steps that wait on platform features are marked, and Missing infrastructure says what they need.

Setup

The issuing, renewal and revocation steps run for real on the local lab CA from the Certificate authorities and chains lab. The expiry and DNS steps read your tenant's real certificate and domain. The tenant has no certificate features of its own yet (G27), so nothing in it changes.

  1. Set up a minimal CA database next to your lab CA:

cd ~/btl-pki-lab
ISSUER=https://tenant-<id>.beyondthelogin.dev
HOST=${ISSUER#https://}
touch index.txt; echo 1000 > crlnumber
cat > ca.cnf <<'EOF'
[ ca ]
default_ca = lab
[ lab ]
database = index.txt
crlnumber = crlnumber
default_md = sha256
default_crl_days = 7
EOF

Walkthrough

  1. Request a certificate. Run the lesson's command with names for your local server:

openssl req -new -noenc -newkey ec -pkeyopt ec_paramgen_curve:P-256 -keyout portal.key -out portal.csr \
  -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:portal.localhost"
openssl req -in portal.csr -noout -verify -text | head -5

Expected: Certificate request self-signature verify OK.

Why it matters: the CSR's own signature shows the CA that the requester holds the private key it wants vouched for. The private key never leaves your machine.

  1. Issue it from your lab issuing CA, copying the requested names, and verify the result:

openssl x509 -req -in portal.csr -CA int.pem -CAkey int.key -copy_extensions copy -days 7 -out portal.pem
openssl verify -CAfile myroot.pem -untrusted int.pem portal.pem

Why it matters: your lab CA skipped the most important step a real CA performs, proving that you control the names. The next step shows what that looks like for a real domain.

  1. Proving control of a name, read only. Look up the CAA and ACME records for your tenant's parent domain over DNS-over-HTTPS:

DOMAIN=beyondthelogin.dev
for t in CAA TXT; do curl -s -H 'accept: application/dns-json' "https://cloudflare-dns.com/dns-query?name=$DOMAIN&type=$t" | jq '.Answer'; done
curl -s -H 'accept: application/dns-json' "https://cloudflare-dns.com/dns-query?name=_acme-challenge.$DOMAIN&type=TXT" | jq '.Answer'

Note whether a CAA record names the CAs permitted to issue. An _acme-challenge record usually exists only while a check is running. If you own a domain, you can run an ACME client against Let's Encrypt's staging environment; that stays outside the tenant.

Why it matters: an ACME check shows control of the name at the moment of the check, much like an email code shows access to a mailbox at that moment.

  1. Renewing before expiry. Monitor your tenant certificate independently of whoever renews it:

openssl x509 -in tenant.pem -noout -dates
openssl x509 -in tenant.pem -noout -checkend $((30*86400)) && echo "more than 30 days left" || echo "ALERT: expires within 30 days"

Work out the certificate's total lifetime in days from the two dates and compare it with the lesson's falling maximum lifetimes. On crt.sh, compare successive certificates for your tenant's names to see the renewal cadence.

Why it matters: short lifetimes make manual renewal impractical, and a renewal that fails quietly becomes an outage unless something independent is watching the date.

  1. Renew with a fresh key. Repeat steps 1 and 2 with portal2 in place of portal, then serve the new certificate and connect as the client from the earlier lab:

openssl s_server -accept 8443 -cert portal2.pem -key portal2.key -cert_chain int.pem -www &
openssl s_client -connect localhost:8443 -CAfile myroot.pem -no-CApath -no-CAstore -verify_hostname portal.localhost </dev/null 2>/dev/null | grep 'Verify return code'

Expected: 0 (ok). The client trusts the CA, not a particular certificate, so a renewal needs no client change.

  1. Revoking a certificate. Pretend portal.key was committed to a public repository. You already replaced it in step 5; now revoke the old certificate with the reason key compromise and publish a CRL:

openssl ca -config ca.cnf -revoke portal.pem -keyfile int.key -cert int.pem -crl_reason keyCompromise
openssl ca -config ca.cnf -gencrl -keyfile int.key -cert int.pem -out int.crl
cat myroot.pem int.pem > bundle.pem
openssl verify -crl_check -CAfile bundle.pem -CRLfile int.crl portal.pem
openssl verify -CAfile bundle.pem portal.pem

Expected: certificate revoked with CRL checking, and OK without it.

Why it matters: revocation works only for clients that check. That is why replacement first, plus short lifetimes, carry most of the weight.

  1. Trust from end to end. Compare step 6 with the emergency drill in the Storing and using keys lab. Disabling a signing key removed it from your tenant's key set and made UserInfo refuse its tokens at once, while a verifier holding a cached key set kept accepting them, just as a client that skips CRL checks keeps accepting portal.pem.

Planned walkthrough

These steps need tenant certificate features (G27), certificate-based client authentication (G8) and a host that requests client certificates (G57).

  1. In Lab Photos, create a lab CA for clients, or register int.pem as a trusted issuer for client certificates.

  2. Issue a client certificate for lab-print-orders with CN=lab-print-orders, and switch the client's authentication method to tls_client_auth.

  3. Request a token over mutual TLS at the tenant's mTLS host. Expected: a token, with no client secret sent, and a certificate-bound access token whose cnf names the certificate's thumbprint.

  4. Revoke the client certificate in the tenant and request another token. Expected: refused, and Audit records the rejection with the reason.

Break it

  1. Connect to the renewed server with a name the CA never approved: -verify_hostname other.localhost. Expected: 62 (hostname mismatch). The CA vouched only for the names in the request it approved.

  2. Issue a one-day certificate with -days 1 as short.pem, run your step 4 monitor against it with -checkend $((2*86400)), and see the alert fire before any client fails.

Check your work

The tenant has no certificate features yet, so these steps produce no tenant events to check. You should have:

  • Certificate request self-signature verify OK for the CSR

  • OK for the issued and renewed certificates

  • certificate revoked with -crl_check, and OK without it

  • your tenant certificate's lifetime in days and the result of your expiry monitor

Cleanup

  1. Stop s_server with kill %1.

  2. Delete the whole lab folder, which holds private keys for your lab root and issuing CA: cd ~ && rm -r ~/btl-pki-lab.

Missing infrastructure

  • G27 PKI and X.509 certificates. The tenant cannot issue, register or validate certificates. Once it can, the planned walkthrough issues and revokes a real client certificate in the tenant.

  • G8 Client authentication beyond client_secret_basic. tls_client_auth is needed for the client certificate to replace the secret.

  • G57 mTLS edge. A per-tenant host that requests client certificates is needed to present one at the token endpoint.

  • A real ACME run needs a domain the learner controls and stays outside the tenant by design.

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