Skip to content

ota: embed release root key, use keyservers only as fallback - #1493

Draft
adamshiervani wants to merge 1 commit into
devfrom
fix/embed-ota-root-key
Draft

ota: embed release root key, use keyservers only as fallback#1493
adamshiervani wants to merge 1 commit into
devfrom
fix/embed-ota-root-key

Conversation

@adamshiervani

@adamshiervani adamshiervani commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Problem

The OTA verifier fetches the release public key from public keyservers (keys.openpgp.org, keyserver.ubuntu.com) on the update path. But the trust anchor is the pinned fingerprint compiled into the binary (rootKeyFingerprint), and the fetched key is rejected unless it matches that fingerprint — so the key bytes are effectively constant. Fetching them over the network buys nothing and adds downsides:

  • Availability: verification depends on third-party keyservers being reachable. They're flaky, rate-limit, and are blocked on some networks. If they're unreachable, the update can't be verified and fails.
  • Reboot fragility: the key cache is in-memory only, so the first verify after every reboot has to hit a keyserver again.
  • Privacy: every device queries public keyservers with the JetKVM fingerprint from its own IP.

To be clear, this was not a security hole — the fingerprint pin already prevents a malicious/MITM'd keyserver from substituting a key. It's an availability/robustness fix.

Change

  • Embed the release root public key in the binary (internal/ota/root_key.asc, via go:embed) and use it first in FetchPublicKey. It's validated against the same pinned fingerprint, so it's no less trusted.
  • Keep keyservers as a fallback, only used if the embedded key is missing or fails to load.
  • Add TestEmbeddedRootKey_MatchesPinnedFingerprint so a wrong/garbled key can't ship silently, and TestFetchPublicKey_PrefersEmbeddedKey to confirm no keyserver request is made when the embedded key is valid.

Notes for review

  • The embedded key was pulled from keys.openpgp.org by fingerprint and matches the pinned AF5A36A993D828FEFE7C18C2D1B9856C26A79E95 (UID JetKVM Release Root <release@jetkvm.com>). Please confirm it against the canonical/offline copy before merge.
  • go test ./internal/ota/... passes.

Note

Medium Risk
Touches OTA signature verification trust path; trust model is unchanged (fingerprint pin) but shipping the wrong root_key.asc would break updates until fixed—mitigated by new fingerprint tests.

Overview
OTA GPG verification no longer depends on keyservers for the common path: the JetKVM release root public key is go:embed’d from internal/ota/root_key.asc and tried in FetchPublicKey before any HTTP lookup. The embedded material is validated with the same pinned fingerprint as fetched keys, then cached like a successful keyserver response; keyservers stay as fallback only if the embedded key is missing or fails validation.

Tests assert the embedded bytes match rootKeyFingerprint, that a valid embedded key triggers zero keyserver calls (even when HTTP would fail), and TestFetchPublicKey_AllKeyserversFail clears embeddedKey so the failure path still exercises the network.

Reviewed by Cursor Bugbot for commit 660b85a. Bugbot is set up for automated code reviews on this repo. Configure here.

The OTA verifier fetched the release public key from public keyservers
(keys.openpgp.org, keyserver.ubuntu.com) every time it had to verify a
downloaded update. The trust anchor is the pinned fingerprint compiled into
the binary, so the key bytes are effectively constant — fetching them over
the network bought nothing while adding a hard dependency on third-party
keyservers being reachable, plus a per-device IP leak to those servers. The
in-memory-only cache made it worse: the first verify after every reboot had
to reach a keyserver.

Embed the root public key in the binary and use it first; it is validated
against the same pinned fingerprint, so it is no less trusted. Keyservers
remain a fallback if the embedded key is ever missing or fails to load.

A test asserts the embedded key matches the pinned fingerprint so a wrong
key can't ship silently.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant