# Keysat master keypair — generation, storage, and import This document covers the keypair that signs every Keysat self-license — the "Keysat-licenses-Keysat" trust root. It is the single most security-critical piece of cryptographic material in the entire project. Anyone with the private key can mint Keysat licenses indefinitely. Treat it accordingly. ## What this keypair is An Ed25519 keypair generated by Grant on his laptop. The **public** half is embedded in the Keysat daemon source code at `licensing-service/src/license_self.rs` in the `TRUST_ROOT_PUBKEY_PEM` constant. Any Keysat install with that source embedded will accept license keys signed by the corresponding private half, and reject everything else. The **private** half is held offline and only used to mint Keysat licenses (eventually via a "master Keysat" instance that imports the keypair). ## How it was generated ``` openssl genpkey -algorithm Ed25519 -out keysat-master-private.pem openssl pkey -in keysat-master-private.pem -pubout -out keysat-master-public.pem ``` These are the same commands that ran. They produce two files: - `keysat-master-private.pem` — the private key, ~120 bytes - `keysat-master-public.pem` — the public key, ~120 bytes ## What's already done ✅ Public key embedded in `licensing-service/src/license_self.rs` as `TRUST_ROOT_PUBKEY_PEM`. The current value is: ``` -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAgsromMy4osMJplX1rY0fd4ouS6wfkm/vfeY2gXEQHkA= -----END PUBLIC KEY----- ``` If that block ever needs to change (e.g. if the private key leaks and you have to rotate), edit `TRUST_ROOT_PUBKEY_PEM` and ship a new package version. Note that all previously-issued licenses become unverifiable on the new package — plan rotations carefully. ## What you need to do ### Right now: secure the private key The private-key file is sitting in your home folder (`~/keysat-master-private.pem`). Do all of the following before anything else: 1. **Make a paper backup.** Open the file in TextEdit, print it on paper (one sheet, three lines of base64). Store the printout in a fireproof safe or safe-deposit box. Test that the printed copy is legible — small fonts and inkjet bleed are common gotchas. 2. **Make a digital backup in something secure.** Recommended in rough order: - 1Password / Bitwarden secure note (attach the .pem file). Synced across your devices, encrypted at rest. - Encrypted USB drive (VeraCrypt or APFS-encrypted disk image), kept offline. - GPG-encrypted backup uploaded to a personal cloud bucket. 3. **Delete the plaintext file from your laptop's home folder** once backed up. ``` srm ~/keysat-master-private.pem # macOS shred -u ~/keysat-master-private.pem # Linux ``` Don't just `rm` it — that leaves the bytes recoverable on most filesystems. ### When you're ready: stand up a master Keysat instance The master Keysat is a separate Keysat install whose only job is to mint licenses for the Keysat package itself. It runs on its own Start9 (or wherever) and uses the private key you generated as its issuer key. The keypair is stored in the daemon's SQLite database (under the `server_keys` table), not as a file on disk. To bootstrap the master instance, use the admin-only `POST /v1/admin/import-issuer-key` endpoint, added in v0.1.0:15 specifically for this scenario. Steps: 1. Install the Keysat package on a fresh Start9 (or a separate StartOS account on the same Start9, if possible). Standard install flow. The daemon will generate a throwaway issuer keypair on first boot — that's expected; you'll replace it in step 3. 2. Grab the admin API key for this fresh instance: StartOS dashboard → Keysat service → **Actions → Show admin API key**. Copy the 64-hex-character key. 3. From your laptop (the one with `keysat-master-private.pem`), POST the PEM to the master Keysat's import-issuer-key endpoint: ``` ADMIN_KEY="paste-the-admin-api-key-here" MASTER_KEYSAT="https://your-master-keysat.example" # or LAN URL like https://your-mdns.local:port curl -X POST \ -H "Authorization: Bearer $ADMIN_KEY" \ -H "Content-Type: application/x-pem-file" \ --data-binary @/path/to/keysat-master-private.pem \ "$MASTER_KEYSAT/v1/admin/import-issuer-key" ``` Expected response: ```json { "ok": true, "public_key_pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n", "restart_required": true, "message": "Issuer key imported. Restart the Keysat service for the new key to take effect..." } ``` The endpoint refuses if any licenses have already been issued by this Keysat — a safety guard against accidentally invalidating customer keys. Since this is a freshly-installed master instance, it should succeed. 4. **Stop and start the Keysat service** from the StartOS dashboard. The in-memory keypair gets re-loaded from the DB on startup, picking up the imported key. Confirm in the logs that the public key now matches your `keysat-master-public.pem`. 5. In the master Keysat's admin UI, define a product called "Keysat" with your intended pricing (e.g. 50,000 sats standard, 250,000 sats patron). 6. Define a `default` policy on the Keysat product. Perpetual single-seat is a reasonable default. Optionally add entitlements: `["patron"]` for a Patron tier. 7. The master instance is now ready. Self-redeem a free license for your own non-master Keysat installs (use `free_license` discount codes for bootstrapping). #### Alternative: SSH if you prefer file-system access If you'd rather not use the import endpoint, you can SSH into the Start9 host and edit the SQLite database directly. Less recommended (more error-prone), but documented for completeness: ``` # Enable SSH in StartOS Settings → SSH, add your laptop's pubkey, then: ssh start9@.local # find the keysat data volume sudo find / -name "*.db" -path "*keysat*" 2>/dev/null # edit server_keys row 1 with sqlite3 ``` This route requires you to UPSERT the right PEM strings into the `server_keys` table manually. Stick with the curl-based import endpoint unless you specifically need SSH access for some other reason. #### Why this isn't a StartOS button Putting an "Import master signing key" button in every Keysat operator's StartOS Actions tab would clutter the UX for the 95% of operators who don't need it (their auto-generated issuer key is exactly what they want). The import-issuer-key endpoint is admin-only, invisible by default, and called exactly once during master setup. ### When 100 free first-user licenses are needed In the master instance's admin UI: 1. Go to **Discount codes → Create new code**. 2. Set kind `free_license`, max uses 100, no expiry. 3. Pick a memorable code: `EARLY100`, `FOUNDERS100`, etc. 4. Distribute the code via your launch announcement / Twitter / Nostr / mailing list. First 100 users redeem at `https://registry.keysat.xyz` (or wherever the buy page is hosted by the master instance). ## Threat model and rotation plan **If the private key leaks:** - An attacker can mint Keysat licenses indefinitely. - Existing customers' licenses are unaffected — the leak doesn't retroactively invalidate signatures. - Mitigation: rotate. Generate a fresh keypair, edit `TRUST_ROOT_PUBKEY_PEM`, ship a new package version, re-issue licenses to existing customers, deprecate the leaked key in your customer comms. **If the private key is destroyed (and no backup):** - You can't issue any new licenses ever. - Existing customer licenses keep working. - Mitigation: rotation as above. Painful but recoverable. This is why the paper backup matters more than anything. Cloud sync is great for daily use, but a paper copy in a safe is the resilience floor. ## Future: rolling rotation (v0.3+) The current model is hard-cut: one public key, one private key, one rotation event invalidates everything. v0.3 will land a rolling-rotation flow: - Daemon embeds *two* trust-root pubkeys — primary and standby. - During rotation, the daemon accepts licenses signed by either. - After all customers have re-licensed under the new key, the old pubkey is retired in the next package version. This makes rotation graceful instead of catastrophic. Until that ships, treat the private key as if losing it would mean re-licensing every customer (because it would). ## Quick reference | Action | Command | |---|---| | Generate keypair (already done) | `openssl genpkey -algorithm Ed25519 -out keysat-master-private.pem && openssl pkey -in keysat-master-private.pem -pubout -out keysat-master-public.pem` | | Inspect public key | `openssl pkey -in keysat-master-public.pem -pubin -text -noout` | | Inspect private key (don't normally do this) | `openssl pkey -in keysat-master-private.pem -text -noout` | | Securely delete plaintext | macOS: `srm ` · Linux: `shred -u ` | | Verify which key is embedded in daemon | `grep -A4 TRUST_ROOT_PUBKEY_PEM licensing-service-startos/licensing-service/src/license_self.rs` |