package publicrelease import ( "context" "crypto/ed25519" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "net/http" "net/http/httptest" "strings" "testing" ) func testFeed(t *testing.T, sign bool, assets map[string][]byte) (Feed, string) { t.Helper() pub, priv, _ := ed25519.GenerateKey(nil) var sums strings.Builder for name, body := range assets { s := sha256.Sum256(body) fmt.Fprintf(&sums, "%s %s\n", hex.EncodeToString(s[:]), name) } sig := ed25519.Sign(priv, []byte(sums.String())) mux := http.NewServeMux() mux.HandleFunc("/latest.txt", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "v0.18.0") }) mux.HandleFunc("/dl/v0.18.0/SHA256SUMS.txt", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, sums.String()) }) mux.HandleFunc("/dl/v0.18.0/SHA256SUMS.txt.sig", func(w http.ResponseWriter, r *http.Request) { if sign { fmt.Fprintln(w, base64.StdEncoding.EncodeToString(sig)) } else { fmt.Fprintln(w, base64.StdEncoding.EncodeToString(make([]byte, ed25519.SignatureSize))) } }) for name, body := range assets { body := body mux.HandleFunc("/dl/v0.18.0/"+name, func(w http.ResponseWriter, r *http.Request) { w.Write(body) }) } ts := httptest.NewServer(mux) t.Cleanup(ts.Close) return Feed{LatestURL: ts.URL + "/latest.txt", DownloadBase: ts.URL + "/dl", PubKey: base64.StdEncoding.EncodeToString(pub)}, ts.URL } func TestFeedLatestAndVerifiedAsset(t *testing.T) { body := []byte("ELF arm64") f, _ := testFeed(t, true, map[string][]byte{"carlos-linux-arm64": body, "carlos-linux-amd64": []byte("ELF amd64")}) ctx := context.Background() tag, err := f.Latest(ctx) if err != nil || tag != "v0.18.0" { t.Fatalf("latest = %q %v", tag, err) } data, sha, err := f.Asset(ctx, tag, AssetName("linux-arm64"), 1<<20) if err != nil || string(data) != string(body) { t.Fatalf("asset: %v", err) } s := sha256.Sum256(body) if sha != hex.EncodeToString(s[:]) { t.Fatalf("sha = %s", sha) } if _, _, err := f.Asset(ctx, tag, "carlos-windows-amd64.zip", 1<<20); err == nil { t.Fatal("an asset absent from the sums was accepted") } } func TestFeedRefusesABadSignatureAndATamperedAsset(t *testing.T) { body := []byte("ELF") f, _ := testFeed(t, false, map[string][]byte{"carlos-linux-arm64": body}) if _, err := f.Sums(context.Background(), "v0.18.0"); err == nil || !strings.Contains(err.Error(), "signature") { t.Fatalf("bad signature: %v", err) } // Signed sums, but the served asset differs from the sum. f2, _ := testFeed(t, true, map[string][]byte{"carlos-linux-arm64": body}) // Simulate tampering by asking for a limit smaller than the asset: the // truncated download cannot match its sum. if _, _, err := f2.Asset(context.Background(), "v0.18.0", "carlos-linux-arm64", 1); err == nil { t.Fatal("a truncated asset matched its sum") } } func TestFeedLatestRequiresATag(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "main") })) defer ts.Close() f := Feed{LatestURL: ts.URL, DownloadBase: ts.URL, PubKey: ""} if _, err := f.Latest(context.Background()); err == nil { t.Fatal("accepted a non-tag latest") } }