尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
Self-hosted, end-to-end-signed SSH key sync
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Publish your SSH authorized_keys from a web page and have your machines pull
updates automatically — without trusting the web server. Every update is
verified against a hardware-backed signing key before it touches
authorized_keys, so a compromised host, CDN, DNS, or TLS cert cannot inject a
key or roll you back to an old one.
This is the system that runs at https://illixion.github.io. You can adopt it for your own keys; it's a single static Go binary plus two shell scripts.
curl https://example.com/keys >> authorized_keys?Because that trusts the web server completely. If the site (or its CDN/host)
serves attacker bytes with 200 OK, you've authorized the attacker. The whole
point here is to remove that trust:
See updater/DESIGN.md for the full threat model.
Your Mac (trusted) your website (UNTRUSTED) your machines
────────────────── ──────────────────────── ─────────────
generate/sign-keys.sh ─push─► /discovery.json ─GET─► ssh-keys-updater
build manifest /manifest.json (pinned keys
sign with YubiKey /manifest.json.sig compiled in)
+ self-verify discover → verify →
write discovery.json serial → atomic install
serial, issued_at, the keys
list, and an optional disable_signer for revoking a compromised signing key.SSHSIG (ssh-keygen -Y sign).install; the binary bakes in only the signing keys, so moving the site needs
no rebuild.<repo root, served at your Pages URL>/
index.html generated page (served at /)
discovery.json PUBLIC location descriptor clients fetch first
manifest.json[.sig] the signed, published artifacts
bin/ binaries + SHA256SUMS — built by CI on deploy, NOT committed
CNAME custom domain for GitHub Pages (ssh.illixion.com)
README.md this file
keys.list YOUR source key list (authorized_keys format) — edit + re-sign
updater/
config.env deployment config: base URL, identity, repo, cadence, source list
*.go the updater (pure stdlib, no external deps)
pinned_signers YOUR trust anchor: the signing public keys
page.tmpl.html self-contained page template (gen-page fills it)
generate/sign-keys.sh build + sign + self-verify; write discovery.json + page
release.sh cross-compile all targets (bakes only the version)
DESIGN.md threat model & internals
INSTALL.md per-platform download/verify/install
Make a signing keypair on hardware, plus an offline backup keypair.
Plain ed25519 keys (the verifier is a single ed25519.Verify):
ssh-keygen -t ed25519 -C you@backup -f securebackup_ed25519 # keep offline
# your YubiKey's ed25519 (PGP/PIV/FIDO) exposed to ssh-agent is the daily signer
Pin them. Put both public keys in updater/pinned_signers,
one per line, ssh-ed25519 AAAA... comment. This is the trust anchor baked
into every binary — get it right.
Configure. Edit updater/config.env — your public
base URL, page title/handle, source repo, cadence, and the source key list.
That one file feeds the signer, the page, and discovery.json.
Record the binary hashes out-of-band. cd updater && ./release.sh v1
cross-compiles all targets and writes dist/SHA256SUMS. Save those numbers
somewhere you control (an iOS note). The Pages CI rebuilds the same bytes
on deploy (reproducible: -trimpath, fixed -ldflags, Go pinned via
go.mod), so what's served matches what you recorded. See INSTALL.md.
List the keys to authorize in keys.list (one SSH public key
per line), then sign: generate/sign-keys.sh builds manifest.json,
signs it through your agent (auto-finds the pinned signer; touch your key),
self-verifies, and writes discovery.json + index.html.
Publish: commit and push. CI builds the binaries and deploys. Binaries are never committed, so the repo stays small.
Install on each machine: download the binary, verify its SHA-256 against
your out-of-band value, run ./ssh-keys-updater install <your-domain> (e.g.
ssh.illixion.com). The binary fetches discovery.json from that domain,
prints it for you to confirm, and schedules periodic runs. Use
to first copy the binary
to the system path () and schedule from there — recommended, so
deleting the download never breaks updates. Full per-platform steps in
.
Steps 1–2 bake your signer into the binary. If instead you want to reuse
someone else's prebuilt binary with your own signer, build with an empty
pinned_signers (a "neutral" build). At install/system-install the binary
fetches the manifest, validates the signature, and prints the signer's
SHA256:… fingerprint; you confirm it interactively (or pass
-accept-signer SHA256:…) after verifying it out-of-band — the same trust
step as the binary's SHA-256. The accepted signer is pinned locally in the
.ssh-keys-updater.json sidecar; scheduled runs never accept a new signer. A
host can serve several identities from one shared binary — install once per
account, each accepting its own signer and domain.
This deployment serves at https://ssh.illixion.com via a CNAME file +
DNS. To point a subdomain of your own at the Pages site:
illixion.com is hosted) — add a record:
CNAME, Name ssh (→ ssh.illixion.com), Target illixion.github.io.CNAME file (containing ssh.illixion.com) is already in the
deployed site. In Settings → Pages → Custom domain, set ssh.illixion.com
and, once the cert is issued (minutes to ~an hour), tick Enforce HTTPS.illixion.github.io.The binary carries the trust anchor compiled in, so confirming you have an authentic copy is the one step that matters. The website is untrusted — trust the verification, not the download. Two independent ways, strongest first:
Build the binaries yourself once and record their hashes somewhere you control
(a password manager, an iOS note). Builds are reproducible — CGO_ENABLED=0,
-trimpath, fixed -ldflags from config.env, Go pinned in go.mod, zero
external modules — so your local hashes match what CI serves byte-for-byte:
cd updater && ./release.sh # builds dist/ for every target
cat dist/SHA256SUMS # <- record these out-of-band
On a new machine, after downloading, compare:
shasum -a 256 <file> # macOS
sha256sum <file> # Linux / OpenWRT
certutil -hashfile <file> SHA256 # Windows (built in)
This check survives even a full compromise of GitHub/the host, because the
reference value lives only with you. To re-derive the canonical hashes at any
time, build the tagged release: git checkout v1.0.0 && cd updater && ./release.sh.
Every published binary has a signed SLSA attestation proving it was built by this
repo's GitHub Actions workflow from a specific commit. With the gh CLI you can
verify without any pre-recorded hash:
gh attestation verify ssh-keys-updater-macos-arm64 --repo illixion/illixion.github.io
This is great for first installs and adopters, but it is minted by GitHub — so it trusts GitHub. Treat it as defense-in-depth; the out-of-band hash (#1) remains the ultimate check. The manifest itself is separately signed by a hardware key, a stronger root than either.
sign-keys.sh (serial bumps),
push. Machines pick it up within the check interval (default every 15 min,
with random splay so traffic isn't synchronized).sign-keys.sh --disable <comment-of-yubikey> --key-file <backup-private-key>, push. Every client
permanently stops trusting it.ssh-keys-updater run # one fetch+verify+install
ssh-keys-updater install # schedule periodic runs + run once (-interval, -splay, -accept-signer)
ssh-keys-updater system-install # copy binary to the system path, then schedule from there (root)
ssh-keys-updater uninstall # remove the schedule
ssh-keys-updater verify M S # offline-verify a manifest+sig
ssh-keys-updater gen-page # render the self-contained page (-base-url -title -handle -out)
ssh-keys-updater print-pins # show trusted signer fingerprints (embedded + locally-accepted)
MIT License. See LICENSE for details.
sudo ./ssh-keys-updater system-install <your-domain>/usr/local/bin