Thursday, 5 November 2026. Alex typed an ssh command that had worked a week earlier. The server refused.

Nothing was broken. The certificate had been signed on 29 October and expired two hours later, as designed.

One touch on a hardware security key opened a new two-hour window. The next command copied a folder of build files, 89 MB, off the server.

The refusal was the system working. The command had not changed. The permission had.

The Key That Never Expires

A plain SSH key is easy to understand because it keeps doing the same thing. Put the public half on a server, keep the private half on a laptop, and the server accepts that key whenever it sees it. A key in id_rsa or id_ed25519 works on every server that lists it, forever, until someone removes the line.

That last clause sounds like control. In practice, the line can outlive the reason it was added. A key gets copied to new laptops, loaded into agents and forwarded. The person who made it may remember why they needed access once, but not every server that still trusts it.

Its permission has no natural end. A laptop may change hands or disappear, an account may no longer need access, or a task may be finished. The key keeps offering the same claim until someone finds every place that accepted it and removes the claim.

SSH certificates move the decision to a different object. A certificate authority signs a user key. Its signature carries a key ID, principals, a validity period and options.[1] The key proves possession of its private half. The certificate adds a bounded statement about what that key may do and when.

A server can trust the authority instead of collecting every user's public key. One authorized_keys line marked cert-authority tells it which authority to accept. At login, the server checks the principal, source address and expiry. It can log the key ID as well. The permission becomes something the server evaluates on every login, rather than a line that stays valid until someone remembers to remove it.

A certificate gives access an explicit end. When that end arrives, the server rejects the certificate. The laptop does not have to announce anything.

A Signature With a Clock

A window is a fresh throwaway key for a session, signed for a few hours. The certificate turns that key into a login credential for a limited validity period. The server checks the clock on the certificate. It does not ask the laptop whether the window should still be open.

The window lasts from one to twelve hours for ssh-window. For sage-box boxes, the permitted range is one to four hours. The tool refuses a longer request instead of quietly shortening it. That refusal preserves a decision: whoever asks for six hours decides again, rather than discovering that the tool silently chose a different duration.

The server's clock does the closing. A window can close even if the laptop remains open, the key file remains there, and nobody remembers to clean up. Expiry is part of the server's test at every login.

There is also an early close. --lock deletes the throwaway key file. That helps when the work ends before the certificate does. It is not revocation. A copy of the key taken earlier still works until the window ends.

The Authority in a Pocket

A short certificate needs a signer. If the authority's private key lives as an ordinary file on the laptop, then the window around the user's key does not bound the authority's power. Whoever gets the CA key can sign more credentials. The clock on one certificate would not contain the power to create another.

The signing key can instead live on a hardware security key. OpenSSH added support for security-key-backed ed25519-sk and ecdsa-sk keys in version 8.2, released in 2020.[2] Their private half stays on the device. The file on disk is only a handle. Signing requires a touch.

That touch supplies a human action when a new window is created. No device and no person means no new signature. The sage-box source states the rule in one line: “A CA must need a touch to sign, so it must live on a FIDO device.” The authority becomes something Alex carries, rather than a private file that can silently make credentials whenever a process can read it.

The certificate still has its principal, validity and options, and the server still checks them. The touch is the deliberate step that makes a fresh signed window available. It bounds issuing; the certificate bounds using.

Facebook moved staff SSH to certificates in 2016.[3] Netflix BLESS signs short-lived certificates from an AWS Lambda function.[4] Both are online CAs. Here, the authority is offline until a person uses the security key. No service can ask the signer for another window without the device and a touch.

One Lock Per Zone

An authority should match the boundary of the trust it represents. A home office, a product server and client boxes each trust their own CA. Retiring the authority for one zone touches nothing else.

This separation keeps one decision from becoming another. A product server that trusts its own CA does not need to accept a certificate because the same signer can reach a home office. Each zone decides what credentials it accepts. Its signing authority can be retired without changing the others.

Signing with the wrong CA succeeds on the laptop. The signature operation has happened. But the server rejects the certificate because it trusts a different authority. A local success does not mean every remote door has opened.

One authority for every place might reduce the number of objects to track. It can also widen the consequences when one object must change. The zone model keeps the relationship legible: this authority belongs to this set of machines, and those machines decide whether its signatures count.

One Door Per Job

The sage-box model gives each kind of access its own key. The read door uses a fixed list of verbs through a forced command. The backup door uses rrsync, read-only, for one folder. The window door uses a CA certificate. The me door uses the person's own FIDO key. The legacy door uses a vault password that is being retired.[5]

These doors do not mean the same thing. A fixed command is not a general shell. A read-only backup path is not a certificate window. Giving each job its own key keeps one permission from concealing another.

Only window and me can administer. That division makes “access” less vague. A credential is tied to what its door permits, and the server-side setup decides which credential belongs at which entrance.

The SSH client is constrained too. sage-box runs SSH with -F none, so the user's configuration cannot offer other keys or turn on agent forwarding. The box keeps those choices within its own boundary.

The sage-box access standard says it plainly: “A window grants what the box user can do, data included. That is why windows are short, named and logged.” The certificate's short life does not make its permission small. Its scope comes from the user and the door it opens. Time answers how long the grant remains acceptable; the door answers what it can reach.

What It Costs

There is one touch per window. That is a deliberate interruption, but it is still an interruption. A person cannot ask for a fresh signature without the key and the action that authorizes signing.

There is client setup too. Apple's built-in /usr/bin/ssh needs a FIDO provider, supplied by openssh-sk-provider from Homebrew or Homebrew OpenSSH. There is also a Host block per server. Connecting to the raw address skips that block, so a client invocation can bypass the settings the window expects.

The laptop clock must be right. There is a cost in habits as well. A long-lived key asks its owner to remember where it is trusted and to remove it when the time comes. A window asks for a new touch and a deliberate validity period. It replaces quiet, potentially indefinite permission with a repeated step. That friction is by design.

People also have to keep several ideas distinct: the key, certificate, CA, principal, server trust and door scope. Calling them all “the SSH key” hides where the authority sits and what the server checks. A local signature can succeed while the server rejects it because the wrong CA signed it.

What You Are Still Trusting

The laptop remains trusted during the window. The throwaway key sits on disk until expiry or until --lock deletes it. If a copy exists elsewhere, deleting the local file does not revoke that copy. Hard revocation needs a KRL, configured through RevokedKeys in sshd_config.

That distinction sets a limit on “short-lived.” The certificate expires; the key file does not erase itself from every place it might have been copied. Expiry stops the server accepting that certificate after its time. It does not remove copies or undo anything done while the credential was valid.

The design still trusts the security key's firmware, the server's clock and sshd itself. The CA in a pocket cannot inspect or repair the machinery at either end. The hardware has to sign as expected. The server has to evaluate the certificate as expected. Its clock has to tell the right time for expiry to mean what the policy says.

No single key makes these responsibilities disappear. The hardware key keeps the CA's private half from sitting as an ordinary file. The certificate bounds a user key in time and by its contents. The server decides whether the signed statement matches its trust and login conditions. The laptop holds the throwaway key while the window is open. Each layer has a job, and each remains part of what must be trusted.

The Window That Closed

On Thursday, 5 November 2026, Alex's command failed because the certificate had reached the end of its life. It had been signed on 29 October and expired two hours later. The server did what the certificate asked: it stopped accepting that credential when its window closed.

A touch on the hardware security key made another window. The next command copied a folder of build files, 89 MB, off the server. The new signature did not restore the old permission. It gave Alex a fresh certificate, with a new clock running on it.

There is a difference between access that persists until someone finds and removes it and access that ends at the time its certificate names. The second still depends on the person, device, laptop and server. It still grants real permission while it is open. But it gives the server an ending it can enforce without waiting for somebody to remember.

The refusal was the system remembering the date. A week earlier, the same command had worked. This time the old window had closed, and a new one took a touch to open.

The commands are below.

The Runbook

Every name below is a placeholder. example-zone-ca is the CA, example-host the server, example-user the login, 203.0.113.10 the server's address and 198.51.100.0/24 the network you connect from. Put your own in their place.

Before You Start

Check that your ssh-keygen can talk to a security key. Touch the key when it blinks, then delete the test key.

ssh -V
ssh-keygen -t ed25519-sk -f /tmp/sk-test
rm -f /tmp/sk-test /tmp/sk-test.pub

Apple's /usr/bin/ssh ships without a FIDO driver. If the test fails on a Mac, install the driver from Homebrew and name it in your shell profile, or install Homebrew's openssh instead.

brew install openssh-sk-provider
export SSH_SK_PROVIDER="$(brew --prefix)/opt/openssh-sk-provider/lib/sk-libfido2.dylib"

Make the CA

One CA per trust zone, on the security key. Do this once.

ssh-keygen -t ed25519-sk -O resident -O verify-required \
  -O application=ssh:example-zone-ca \
  -C "example-zone CA" -f ~/.ssh/example-zone-ca

-O resident keeps the key on the device, so ssh-keygen -K can rebuild the handle on another computer. -O verify-required asks for the device PIN as well as the touch. On a U2F-only device, use -t ecdsa-sk and drop -O verify-required.

~/.ssh/example-zone-ca is only a handle. Without the device, it signs nothing. The private half never leaves the device. ~/.ssh/example-zone-ca.pub is what servers trust.

Trust the CA on the Server

Add one line to the login user's ~/.ssh/authorized_keys on the server.[6] Paste the whole of example-zone-ca.pub where the line ends.

restrict,pty,cert-authority,principals="example-host",from="198.51.100.0/24" [email protected] AAAA… example-zone CA
  • cert-authority trusts certificates this key signed, not the key itself.
  • principals names what a certificate must carry. Use the host's name, so a window for one machine never opens another.
  • from lists the addresses allowed in.
  • restrict turns off forwarding, and pty gives back a terminal.

The other lines in the file stay as they are.

The Window Script

Save this as ~/bin/ssh-window and make it executable. It makes a throwaway key, has the CA sign it for a few hours, and prints when the window ends.[7]

#!/bin/bash
# ssh-window HOST [HOURS]   sign a fresh key for HOURS (1 to 12, default 2)
# ssh-window HOST --lock    close the window now
set -euo pipefail
host="${1:?usage: ssh-window HOST [HOURS|--lock]}"
arg="${2:-2}"
dir="$HOME/.ssh/windows/$host"
ca="${SSH_WINDOW_CA:-$HOME/.ssh/example-zone-ca}"

if [ "$arg" = --lock ]; then rm -rf "$dir"; echo "$host: window closed"; exit 0; fi
[[ "$arg" =~ ^[0-9]+$ ]] && [ "$arg" -ge 1 ] && [ "$arg" -le 12 ] ||
  { echo "ssh-window: HOURS is a whole number from 1 to 12" >&2; exit 2; }

rm -rf "$dir"
mkdir -p -m 700 "$dir"
ssh-keygen -q -t ed25519 -N '' -C "window:$host" -f "$dir/key"
echo "Touch the security key to sign a $arg-hour window for $host."
ssh-keygen -q -s "$ca" -I "window:$host:$(date +%Y%m%d-%H%M)" -n "$host" \
  -V "+${arg}h" -O clear -O permit-pty "$dir/key.pub"
ssh-keygen -L -f "$dir/key-cert.pub" | sed -n 's/^ *Valid: /Valid: /p'

-n sets the principal to the host's name, to match the server's line. -O clear -O permit-pty grants a terminal and nothing else. -I is the key ID that the server logs with every login.

The Client Side

One block per server in ~/.ssh/config, above any Host * block. The first value ssh finds wins.

Host example-host
  HostName 203.0.113.10
  User example-user
  IdentityFile ~/.ssh/windows/example-host/key
  CertificateFile ~/.ssh/windows/example-host/key-cert.pub
  IdentitiesOnly yes
  AddKeysToAgent no

IdentitiesOnly yes stops ssh offering every other key you own. AddKeysToAgent no keeps the window's key out of the agent, so --lock really closes it.

Daily Use

SSH_WINDOW_CA=~/.ssh/example-zone-ca ssh-window example-host 2
ssh example-host
rsync -a example-host:/srv/example/downloads/ ~/Downloads/example-downloads/
ssh-window example-host --lock

Every Trap, In One Place

  • Wrong CA. A certificate signed by another zone's CA is made without complaint, then refused by the server. Name the CA every time.
  • Raw address. ssh [email protected] skips the Host example-host block and offers your default key. Use the alias, or write Host example-host 203.0.113.10.
  • Expired window. The server answers Permission denied (publickey). Check the end time with ssh-keygen -L -f ~/.ssh/windows/example-host/key-cert.pub | grep Valid.
  • Clock. A computer whose clock is wrong signs a certificate that is not valid yet, or already over.
  • IPv6. On a local link, a computer can arrive by an IPv6 address outside from=. Add AddressFamily inet to the Host block.
  • Lock is local. --lock deletes your copy of the key. A copy taken earlier works until the window ends. To cut one off sooner, list it in a revocation file on the server (RevokedKeys in sshd_config).
  • Debugging. ssh -v example-host shows which key and certificate ssh offers, and whether the server accepts them.

Footnotes


  1. OpenSSH 5.4 release notes, 8 March 2010. Certificate authentication for users and hosts arrives in a minimal format of OpenSSH's own. openssh.org ↩︎

  2. OpenSSH 8.2 release notes, 14 February 2020. FIDO/U2F hardware authenticators arrive as the ecdsa-sk and ed25519-sk key types. openssh.org ↩︎

  3. Facebook Engineering, "Scalable and secure access with SSH", 12 September 2016. engineering.fb.com ↩︎

  4. Netflix, BLESS: an SSH certificate authority that runs as an AWS Lambda function. github.com/Netflix/bless ↩︎

  5. sage-box, docs/boxes/access.md: the doors, the keys and the windows of a Sage.is box. github.com/Sage-is/homebrew-apps ↩︎

  6. sshd(8), section AUTHORIZED_KEYS FILE FORMAT. man.openbsd.org ↩︎

  7. ssh-keygen(1), section CERTIFICATES. man.openbsd.org ↩︎