package release import ( "crypto/sha256" "encoding/hex" "net/http" "net/http/httptest" "os" "path/filepath" "testing" ) func releaseServer(t *testing.T, version, asset string, body []byte, sum string) *httptest.Server { t.Helper() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/" + version + "/checksums.txt": _, _ = w.Write([]byte(sum + " " + asset + "\n")) case "/" + version + "/" + asset: _, _ = w.Write(body) default: http.NotFound(w, r) } })) t.Cleanup(srv.Close) return srv } // unsigned has this build trust no key for the test: the checksum alone. func unsigned(t *testing.T) { t.Helper() was := trustedPub trustedPub = "" t.Setenv("LLMGW_RELEASE_KEY", "") t.Cleanup(func() { trustedPub = was }) } func TestABinaryIsReplacedOnlyWithWhatTheReleasePublished(t *testing.T) { unsigned(t) dir := t.TempDir() target := filepath.Join(dir, "llmgw") _ = os.WriteFile(target, []byte("old"), 0o755) body := []byte("new binary") sum := sha256.Sum256(body) asset := AssetName("v1.2.3", "linux", "amd64") srv := releaseServer(t, "v1.2.3", asset, body, hex.EncodeToString(sum[:])) backup, err := Install(t.Context(), srv.Client(), srv.URL, "v1.2.3", "linux", "amd64", target) if err != nil { t.Fatal(err) } if b, _ := os.ReadFile(target); string(b) != "new binary" { t.Errorf("target holds %q", b) } if b, _ := os.ReadFile(backup); string(b) != "old" { t.Errorf("the old binary was not kept: %q", b) } // A download that does not match its checksum changes nothing. bad := releaseServer(t, "v1.2.4", AssetName("v1.2.4", "linux", "amd64"), []byte("tampered"), hex.EncodeToString(sum[:])) if _, err := Install(t.Context(), bad.Client(), bad.URL, "v1.2.4", "linux", "amd64", target); err == nil { t.Fatal("a binary not matching its checksum was installed") } if b, _ := os.ReadFile(target); string(b) != "new binary" { t.Errorf("a failed install touched the binary: %q", b) } if _, err := Install(t.Context(), nil, srv.URL, "../../x", "linux", "amd64", target); err == nil { t.Error("a version that is not one was fetched") } } func TestReleasesAreFetchedOverHTTPSOrFromThisMachine(t *testing.T) { unsigned(t) for base, ok := range map[string]bool{ "https://gitea.example/releases/download": true, "http://127.0.0.1:8080/r": true, "http://localhost/r": true, "http://gitea.example/releases/download": false, "http://10.0.0.5/r": false, "ftp://x/r": false, "not a url": false, } { if err := CheckBase(base); (err == nil) != ok { t.Errorf("%s: %v", base, err) } } if _, err := Install(t.Context(), nil, "http://gitea.example/r", "v1.2.3", "linux", "amd64", "/nonexistent"); err == nil { t.Error("a release over plain http was installed") } } func TestASignedReleaseMayComeOverPlainHTTPFromAnOfflineMirror(t *testing.T) { pub, priv, err := GenerateKey() if err != nil { t.Fatal(err) } t.Setenv("LLMGW_RELEASE_KEY", pub) if err := CheckBase("http://10.0.0.5:8081/_llmgw/releases"); err != nil { t.Fatalf("a mirror's plain HTTP was refused with a signing key trusted: %v", err) } // And what comes over it is still held to the signature. body := []byte("the release") sum := sha256.Sum256(body) asset := AssetName("v1.2.3", "linux", "amd64") sums := []byte(hex.EncodeToString(sum[:]) + " " + asset + "\n") sig, _ := Sign(priv, sums) _, other, _ := GenerateKey() forged, _ := Sign(other, sums) for _, tc := range []struct { sig string ok bool }{{sig, true}, {forged, false}, {"", false}} { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1.2.3/checksums.txt": _, _ = w.Write(sums) case "/v1.2.3/" + SignatureName: if tc.sig == "" { http.NotFound(w, r) return } _, _ = w.Write([]byte(tc.sig)) case "/v1.2.3/" + asset: _, _ = w.Write(body) default: http.NotFound(w, r) } })) c, err := FetchChecksums(t.Context(), srv.Client(), srv.URL, "v1.2.3") if (err == nil) != tc.ok { t.Errorf("signature %q: %v", tc.sig, err) } if err == nil { if got, _ := c.For(asset); got != hex.EncodeToString(sum[:]) { t.Errorf("the checksum read is %s", got) } if err := c.Verify(); err != nil { t.Errorf("kept checksums do not verify again: %v", err) } } srv.Close() } } func TestFetchGivesOnlyACheckedBinary(t *testing.T) { unsigned(t) body := []byte("for another machine") sum := sha256.Sum256(body) asset := AssetName("v1.2.3", "linux", "arm64") srv := releaseServer(t, "v1.2.3", asset, body, hex.EncodeToString(sum[:])) got, err := Fetch(t.Context(), srv.Client(), srv.URL, "v1.2.3", "linux", "arm64") if err != nil || string(got) != string(body) { t.Fatalf("%q %v", got, err) } bad := releaseServer(t, "v1.2.3", asset, []byte("swapped"), hex.EncodeToString(sum[:])) if _, err := Fetch(t.Context(), bad.Client(), bad.URL, "v1.2.3", "linux", "arm64"); err == nil { t.Error("a binary not matching its checksum was given") } }