feat(reports): Ed25519 snapshot signing + offline verification (A4a)#636
Merged
Conversation
Reports A4 backend (docs/engineering/reports_design.md). Signs report
snapshots for tamper-evidence; the signature/signing_key_id columns
reserved in A3a get their writer.
- A Signer (internal/report/signing.go) signs a snapshot's content
address with a domain-separated Ed25519 payload
("openwatch/report-snapshot/v1\n" + content_sha256). Generate stores
the signature + a public key-id fingerprint; the Report wire exposes
content_sha256 + signature (base64) + signing_key_id (null when no
signer is wired).
- Key custody: a 32-byte raw Ed25519 seed loaded from
[reports].signing_key_file (OPENWATCH_REPORTS_SIGNING_KEY_FILE), never
in the DB - same model as the credential key. Unconfigured = an
EPHEMERAL per-boot key (dev) with a boot warning; production sets a
durable key so signatures verify across restarts.
- GET /api/v1/reports/signing-key (host:read) publishes the public key
{key_id, algorithm, public_key, ephemeral} for OFFLINE verification;
503 when no signer. VerifySignature reconstructs the payload from
content_sha256.
- The json export face is now CANONICAL: re-marshaled so
sha256(json face) == content_sha256 (the stored jsonb is
Postgres-normalized), making content<->hash independently checkable.
- PDF footer shows the signed key-id instead of "not signed (MVP)".
No new dependency (stdlib crypto/ed25519; go mod tidy no-op). SDD:
api-reports v1.5.0 (C-11 signing + canonical-face note on C-09/C-10;
AC-14 signer, AC-15 generate-signed, AC-16 end-to-end sign+publish+verify
over the API). gofmt/vet/build clean; specter 111 (api-reports 16/16,
100%); report + full server suites green.
Follow-up A4b: frontend Signed badge + Verify action + PDF-footer note.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reports A4 (backend) — Ed25519 signing for tamper-evidence. The
signature/signing_key_idcolumns reserved in A3a get their writer.What
Signersigns a snapshot's content address with a domain-separated Ed25519 payload ("openwatch/report-snapshot/v1\n" + content_sha256). Generate stores the signature + a public key-id fingerprint; the Report wire exposescontent_sha256+signature(base64) +signing_key_id(null when no signer is wired).[reports].signing_key_file(OPENWATCH_REPORTS_SIGNING_KEY_FILE), never in the DB — same model as the credential key. Unconfigured = an ephemeral per-boot key (dev) with a boot warning; production sets a durable key so signatures verify across restarts.GET /api/v1/reports/signing-key(host:read) publishes the public key{key_id, algorithm, public_key, ephemeral}for offline verification; 503 when no signer.sha256(json face) == content_sha256(the stored jsonb is Postgres-normalized), making the content↔hash link independently checkable. So a verifier can: re-hash the json face to confirm content, then Ed25519-verify the signature over that hash with the published key.No new dependency
Signing is stdlib
crypto/ed25519—go mod tidyis a no-op. (No supply-chain change, unlike A3b's fpdf.)SDD
api-reportsv1.5.0 — C-11 (signing + signing-key endpoint + offline verification), canonical-face note on C-09/C-10; AC-14 (signer sign/verify), AC-15 (generate stores a verifying signature), AC-16 (end-to-end over the API: generate → publish key → verify the signature offline, anon rejected).Validation
gofmt/vet/build clean;
go mod tidyno-op;specter check111 structural; api-reports 16/16, 100%; report suite + full server suite (411s) green; frontendschema.d.tsregenerated.Follow-up A4b: frontend Signed badge + Verify action.