package main import ( "crypto/ed25519" "crypto/rand" "encoding/base64" "flag" "fmt" "os" "path/filepath" "strings" "gitgud.io/mike/mpv-manager/pkg/releasemanifest" ) func main() { keyID := flag.String("key-id", "", "stable identifier for this release signing key") privateKeyFile := flag.String("private-key-file", "", "new file that will receive the base64 private seed") flag.Parse() if flag.NArg() != 0 || strings.TrimSpace(*keyID) == "" || strings.TrimSpace(*privateKeyFile) == "" { fmt.Fprintln(os.Stderr, "Usage: manifest-keygen -key-id -private-key-file ") os.Exit(2) } publicSpec, err := generateKey(strings.TrimSpace(*keyID), *privateKeyFile) if err != nil { fmt.Fprintln(os.Stderr, "Key generation failed:", err) os.Exit(1) } fmt.Println("Private signing seed written with mode 0600:", *privateKeyFile) fmt.Println("MANIFEST_SIGNING_KEY_ID=" + strings.TrimSpace(*keyID)) fmt.Println("MANIFEST_PUBLIC_KEYS=" + publicSpec) } func generateKey(keyID, privateKeyFile string) (string, error) { _, privateKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { return "", err } // Reuse Sign's strict key-ID validation so the generated public spec is // guaranteed to be accepted by publishers and clients. probe := releasemanifest.Manifest{} if err := probe.Sign(keyID, privateKey); err != nil { return "", err } if err := os.MkdirAll(filepath.Dir(privateKeyFile), 0700); err != nil { return "", err } file, err := os.OpenFile(privateKeyFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) if err != nil { return "", err } removeOnError := true defer func() { _ = file.Close() if removeOnError { _ = os.Remove(privateKeyFile) } }() encodedSeed := base64.StdEncoding.EncodeToString(privateKey.Seed()) + "\n" if _, err := file.WriteString(encodedSeed); err != nil { return "", err } if err := file.Sync(); err != nil { return "", err } if err := file.Close(); err != nil { return "", err } removeOnError = false publicKey := privateKey.Public().(ed25519.PublicKey) return keyID + "=" + base64.StdEncoding.EncodeToString(publicKey), nil }