package release import ( "context" "crypto/ed25519" "encoding/json" "errors" "os" "path/filepath" "strings" "testing" "time" ) func proverKey(t *testing.T) (ed25519.PrivateKey, ed25519.PublicKey, string) { t.Helper() seed, pubB64, err := GenerateKey() if err != nil { t.Fatal(err) } priv, err := ParseSigningKey(seed, "test") if err != nil { t.Fatal(err) } pub, err := ParsePublicKey(pubB64, "test") if err != nil { t.Fatal(err) } return priv, pub, pubB64 } var testSHA = strings.Repeat("ab", 32) func sampleProof() Proof { return Proof{App: "console", Platform: "linux-arm64", SHA256: testSHA, Version: "f4478b15", Prover: "dev", ActiveSince: time.Date(2026, 10, 6, 9, 0, 0, 0, time.UTC), ProvenAt: time.Date(2026, 10, 6, 9, 15, 0, 0, time.UTC)} } func TestProofSignVerifyRoundTrip(t *testing.T) { priv, pub, _ := proverKey(t) p, err := SignProof(priv, sampleProof()) if err != nil { t.Fatal(err) } b, err := json.MarshalIndent(p, "", " ") if err != nil { t.Fatal(err) } got, err := ParseProof(b) if err != nil { t.Fatalf("ParseProof: %v", err) } if got.Schema != 1 { t.Fatalf("schema = %d", got.Schema) } if err := got.Check(pub, "console", "linux-arm64", testSHA, "dev"); err != nil { t.Fatalf("Check: %v", err) } // The signature is over the canonical form, not the file's bytes, so a // proof re-encoded in transit still verifies. compact, err := json.Marshal(got) if err != nil { t.Fatal(err) } again, err := ParseProof(compact) if err != nil || again.Verify(pub) != nil { t.Fatalf("a compact re-encoding does not verify: %v", err) } } func TestProofEditedFieldDoesNotVerify(t *testing.T) { priv, pub, _ := proverKey(t) p, err := SignProof(priv, sampleProof()) if err != nil { t.Fatal(err) } for name, edit := range map[string]func(*Proof){ "sha256": func(p *Proof) { p.SHA256 = strings.Repeat("cd", 32) }, "app": func(p *Proof) { p.App = "other" }, "platform": func(p *Proof) { p.Platform = "linux-amd64" }, "version": func(p *Proof) { p.Version = "v9" }, "prover": func(p *Proof) { p.Prover = "staging" }, "proven_at": func(p *Proof) { p.ProvenAt = p.ProvenAt.Add(time.Second) }, } { q := *p edit(&q) if err := q.Verify(pub); err == nil { t.Fatalf("%s edited and the proof still verifies", name) } } _, other, _ := proverKey(t) if err := p.Verify(other); err == nil { t.Fatal("the proof verifies against another prover's key") } } func TestProofCheckNamesTheMismatch(t *testing.T) { priv, pub, _ := proverKey(t) p, err := SignProof(priv, sampleProof()) if err != nil { t.Fatal(err) } for _, c := range []struct{ app, plat, sha, prover, want string }{ {"other", "linux-arm64", testSHA, "dev", "app"}, {"console", "linux-amd64", testSHA, "dev", "platform"}, {"console", "linux-arm64", strings.Repeat("cd", 32), "dev", "sha256"}, {"console", "linux-arm64", testSHA, "staging", "prover"}, } { err := p.Check(pub, c.app, c.plat, c.sha, c.prover) if err == nil || !strings.Contains(err.Error(), c.want) { t.Fatalf("Check(%s,%s,%s,%s) = %v, want an error naming %s", c.app, c.plat, c.sha, c.prover, err, c.want) } } } func TestParseProofRejectsBadShape(t *testing.T) { priv, _, _ := proverKey(t) good, err := SignProof(priv, sampleProof()) if err != nil { t.Fatal(err) } for name, edit := range map[string]func(map[string]any){ "schema": func(m map[string]any) { m["schema"] = 2 }, "sha256": func(m map[string]any) { m["sha256"] = "../../etc" }, "prover": func(m map[string]any) { m["prover"] = "Dev Box" }, "app": func(m map[string]any) { m["app"] = "" }, "sig": func(m map[string]any) { m["sig"] = "not base64!" }, "proven_at": func(m map[string]any) { delete(m, "proven_at") }, } { b, _ := json.Marshal(good) var m map[string]any if err := json.Unmarshal(b, &m); err != nil { t.Fatal(err) } edit(m) b, _ = json.Marshal(m) if _, err := ParseProof(b); err == nil { t.Fatalf("%s: a malformed proof parsed", name) } } if _, err := ParseProof([]byte("{")); err == nil { t.Fatal("torn JSON parsed") } } func TestProofStoreRoundTrip(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub, _ := proverKey(t) p, err := SignProof(priv, sampleProof()) if err != nil { t.Fatal(err) } if got, want := ProofKey("bab", "console", testSHA, "dev"), "apps/bab/console/proofs/"+testSHA+"/dev.json"; got != want { t.Fatalf("ProofKey = %s, want %s", got, want) } if _, err := GetProof(ctx, s, "bab", "console", testSHA, "dev"); !errors.Is(err, os.ErrNotExist) { t.Fatalf("absent proof = %v, want os.ErrNotExist", err) } if err := PutProof(ctx, s, "bab", p); err != nil { t.Fatal(err) } if err := PutProof(ctx, s, "bab", p); err != nil { t.Fatalf("carrying the same proof twice: %v", err) } got, err := GetProof(ctx, s, "bab", "console", testSHA, "dev") if err != nil || got.Verify(pub) != nil { t.Fatalf("GetProof = %+v, %v", got, err) } if _, err := GetProof(ctx, s, "bab", "console", "../x", "dev"); err == nil { t.Fatal("a path in the sha256 reached the store") } } func TestProofPendingIsCreateOnly(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() first := time.Date(2026, 10, 6, 9, 30, 30, 0, time.UTC) rec := ProofPending{App: "console", Platform: "linux-arm64", SHA256: testSHA, Version: "v2", Prover: "dev", LiveSince: first, After: "15m"} if err := PutProofPending(ctx, s, "bab", rec); err != nil { t.Fatal(err) } rec.LiveSince = first.Add(time.Hour) if err := PutProofPending(ctx, s, "bab", rec); err != nil { t.Fatalf("a second sighting: %v", err) } got, err := GetProofPending(ctx, s, "bab", "console", testSHA, "dev") if err != nil || !got.LiveSince.Equal(first) { t.Fatalf("pending = %+v, %v; want the first sighting kept", got, err) } } func TestFileSHA256(t *testing.T) { p := filepath.Join(t.TempDir(), "f") if err := os.WriteFile(p, []byte("bad build"), 0o644); err != nil { t.Fatal(err) } got, err := FileSHA256(p) if err != nil || got != etagOf([]byte("bad build")) { t.Fatalf("FileSHA256 = %q, %v", got, err) } }