package main import ( "encoding/json" "errors" "flag" "fmt" "io" "os" "strings" "gitgud.io/mike/mpv-manager/pkg/releasemanifest" ) const manifestPublicKeysEnv = "MANIFEST_PUBLIC_KEYS" func main() { if err := run(os.Args[1:], os.Stdout, os.Stderr, os.Getenv); err != nil { fmt.Fprintln(os.Stderr, "Manifest verification failed:", err) os.Exit(1) } } func run(args []string, stdout, stderr io.Writer, getenv func(string) string) error { flags := flag.NewFlagSet("verify-manifest", flag.ContinueOnError) flags.SetOutput(stderr) manifestPath := flags.String("manifest", "releases.json", "signed manifest to verify") expectedChannel := flags.String("expected-channel", "", "required feed channel: stable or rc") expectedVersion := flags.String("expected-version", "", "required release version, with or without a v prefix") trustedKeySpec := flags.String("trusted-keys", "", "trusted key ring (defaults to MANIFEST_PUBLIC_KEYS)") keysOnly := flags.Bool("keys-only", false, "validate the trusted key ring without reading a manifest") if err := flags.Parse(args); err != nil { return err } if flags.NArg() != 0 { return fmt.Errorf("unexpected positional arguments: %s", strings.Join(flags.Args(), " ")) } spec := strings.TrimSpace(*trustedKeySpec) if spec == "" { spec = strings.TrimSpace(getenv(manifestPublicKeysEnv)) } trustedKeys, err := releasemanifest.ParseTrustedKeys(spec) if err != nil { return fmt.Errorf("parse trusted keys: %w", err) } if len(trustedKeys) == 0 { return errors.New("no trusted manifest keys are configured") } if *keysOnly { fmt.Fprintf(stdout, "Verified trusted release key ring (%d keys)\n", len(trustedKeys)) return nil } file, err := os.Open(*manifestPath) if err != nil { return err } defer file.Close() data, err := io.ReadAll(io.LimitReader(file, releasemanifest.MaxManifestBytes+1)) if err != nil { return err } if len(data) > releasemanifest.MaxManifestBytes { return fmt.Errorf("manifest exceeds %d-byte limit", releasemanifest.MaxManifestBytes) } var manifest releasemanifest.Manifest if err := json.Unmarshal(data, &manifest); err != nil { return fmt.Errorf("decode manifest: %w", err) } if err := manifest.Verify(trustedKeys); err != nil { return err } if err := manifest.Validate(); err != nil { return err } if *expectedChannel != "" && manifest.Channel != *expectedChannel { return fmt.Errorf("manifest channel %q does not match expected channel %q", manifest.Channel, *expectedChannel) } if expected := strings.TrimPrefix(strings.TrimSpace(*expectedVersion), "v"); expected != "" && manifest.Version != expected { return fmt.Errorf("manifest version %q does not match expected version %q", manifest.Version, expected) } fmt.Fprintf(stdout, "Verified release manifest %s (schema %d, key %s)\n", manifest.Version, manifest.SchemaVersion, manifest.Signature.KeyID) return nil }