package release import ( "crypto/ed25519" "crypto/rand" _ "embed" "encoding/base64" "errors" "fmt" "os" "strings" "gitea.mynodes.xyz/ilker/llmgw/internal/version" ) // trustedPub is the public key releases are signed with, base64, built into // every binary from the repository. Empty — as it is until a key is made — // trusts a release on its checksums alone, as every release before this did. // // Built in rather than fetched, because the point of a signature is that the // server the release comes from cannot vouch for it: a machine that already // runs llmgw checks the next release against the key the release it runs was // built with, so replacing both the binaries and the checksums on the server // is no longer enough. // //go:embed trusted.pub var trustedPub string // TrustedKey is the key releases are verified with: the built-in one; empty // is none. LLMGW_RELEASE_KEY replaces it in a development build — for testing // a release pipeline — and never in a release: a key taken from the // environment would let whatever sets the environment of an upgrading // process choose what it trusts. func TrustedKey() string { if !Tag.MatchString(version.Version) { if k := strings.TrimSpace(os.Getenv("LLMGW_RELEASE_KEY")); k != "" { return k } } return strings.TrimSpace(trustedPub) } // SignatureName is the file a release's checksums are signed in. const SignatureName = "checksums.txt.sig" // GenerateKey makes a signing key pair, both base64: the private key is the // 32-byte seed, which is what goes into the CI secret. func GenerateKey() (public, private string, err error) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return "", "", err } return base64.StdEncoding.EncodeToString(pub), base64.StdEncoding.EncodeToString(priv.Seed()), nil } // Sign signs data with a base64 seed, returning the signature in base64. func Sign(privateSeed string, data []byte) (string, error) { seed, err := base64.StdEncoding.DecodeString(strings.TrimSpace(privateSeed)) if err != nil || len(seed) != ed25519.SeedSize { return "", errors.New("the signing key is not a base64 ed25519 seed") } return base64.StdEncoding.EncodeToString(ed25519.Sign(ed25519.NewKeyFromSeed(seed), data)), nil } // Verify checks a base64 signature of data against a base64 public key. func Verify(public string, data []byte, signature string) error { pub, err := base64.StdEncoding.DecodeString(strings.TrimSpace(public)) if err != nil || len(pub) != ed25519.PublicKeySize { return errors.New("the trusted release key is not a base64 ed25519 public key") } sig, err := base64.StdEncoding.DecodeString(strings.TrimSpace(signature)) if err != nil { return fmt.Errorf("the release signature is not base64: %w", err) } if !ed25519.Verify(pub, data, sig) { return errors.New("the release's checksums are not signed by the trusted key") } return nil }