CI / test (push) Successful in 4m6s
ed25519 over checksums.txt: llmgw release keygen makes the pair, CI signs with the RELEASE_SIGNING_KEY secret when it is set, and release.Install refuses an unsigned or wrongly signed release when the build embeds a trusted key (internal/release/trusted.pub, empty for now) or LLMGW_RELEASE_KEY names one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
100 lines
3.0 KiB
Go
100 lines
3.0 KiB
Go
package release
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// signedServer publishes a release whose checksums are signed with seed,
|
|
// or not signed at all when seed is empty; tamper rewrites the checksums
|
|
// after signing.
|
|
func signedServer(t *testing.T, seed string, body []byte, tamper bool) (*httptest.Server, string) {
|
|
t.Helper()
|
|
asset := AssetName("v2.0.0", "linux", "amd64")
|
|
sum := sha256.Sum256(body)
|
|
sums := hex.EncodeToString(sum[:]) + " " + asset + "\n"
|
|
sig := ""
|
|
if seed != "" {
|
|
var err error
|
|
if sig, err = Sign(seed, []byte(sums)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if tamper {
|
|
sums += strings.Repeat("0", 64) + " extra-file\n"
|
|
}
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/v2.0.0/checksums.txt":
|
|
_, _ = w.Write([]byte(sums))
|
|
case "/v2.0.0/" + SignatureName:
|
|
if sig == "" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(sig + "\n"))
|
|
case "/v2.0.0/" + asset:
|
|
_, _ = w.Write(body)
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
return srv, asset
|
|
}
|
|
|
|
func TestABuildThatTrustsAKeyInstallsOnlyReleasesSignedWithIt(t *testing.T) {
|
|
pub, priv, err := GenerateKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, otherPriv, _ := GenerateKey()
|
|
t.Setenv("LLMGW_RELEASE_KEY", pub)
|
|
install := func(srv *httptest.Server) error {
|
|
target := filepath.Join(t.TempDir(), "llmgw")
|
|
_ = os.WriteFile(target, []byte("old"), 0o755)
|
|
_, err := Install(t.Context(), srv.Client(), srv.URL, "v2.0.0", "linux", "amd64", target)
|
|
return err
|
|
}
|
|
body := []byte("new binary")
|
|
|
|
good, _ := signedServer(t, priv, body, false)
|
|
if err := install(good); err != nil {
|
|
t.Fatalf("a release signed with the trusted key was refused: %v", err)
|
|
}
|
|
for name, srv := range map[string]*httptest.Server{
|
|
"unsigned": func() *httptest.Server { s, _ := signedServer(t, "", body, false); return s }(),
|
|
"signed by another": func() *httptest.Server { s, _ := signedServer(t, otherPriv, body, false); return s }(),
|
|
"checksums rewritten": func() *httptest.Server { s, _ := signedServer(t, priv, body, true); return s }(),
|
|
} {
|
|
if err := install(srv); err == nil {
|
|
t.Errorf("%s: installed", name)
|
|
}
|
|
}
|
|
|
|
// A build that trusts no key installs on checksums, as before.
|
|
t.Setenv("LLMGW_RELEASE_KEY", "")
|
|
if TrustedKey() != "" {
|
|
t.Skip("this build embeds a trusted key; the unsigned path cannot be tested here")
|
|
}
|
|
unsigned, _ := signedServer(t, "", body, false)
|
|
if err := install(unsigned); err != nil {
|
|
t.Errorf("without a trusted key an unsigned release was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAKeyThatIsNotOneIsSaid(t *testing.T) {
|
|
if _, err := Sign("not-a-key", []byte("x")); err == nil {
|
|
t.Error("signed with a malformed key")
|
|
}
|
|
if err := Verify("bm9wZQ==", []byte("x"), "c2ln"); err == nil || !strings.Contains(err.Error(), "public key") {
|
|
t.Errorf("a malformed trusted key: %v", err)
|
|
}
|
|
}
|