verifier.go

v1.3.7
Doc Versions Source
1
package main
2
3
import (
4
	"crypto/ed25519"
5
	"encoding/base64"
6
	"fmt"
7
	"os"
8
	"strings"
9
10
	"github.com/spf13/cobra"
11
12
	"go.bigb.es/curator/internal/config"
13
)
14
15
func verifierCmd() *cobra.Command {
16
	var configPath string
17
18
	cmd := &cobra.Command{
19
		Use:   "verifier [private-key-or-@file]",
20
		Short: "Extract the public verifier key from a sumdb signing key",
21
		Long: `Extracts the public verifier key from a private signing key.
22
The verifier key is needed for GOSUMDB configuration.
23
24
The key can be provided as:
25
  - A positional argument (inline key or @file)
26
  - Via --config flag (reads sumdb.key from config file)
27
  - Via CURATOR_SUMDB_KEY env var (reads from config)
28
29
Examples:
30
  curator verifier "PRIVATE+KEY+go.example.com+..."
31
  curator verifier @/etc/curator/sumdb.key
32
  curator verifier --config curator.yaml`,
33
		Args: cobra.MaximumNArgs(1),
34
		RunE: func(cmd *cobra.Command, args []string) error {
35
			if len(args) == 1 {
36
				return runVerifier(args[0])
37
			}
38
			// Try loading from config.
39
			cfg, err := config.Load(configPath)
40
			if err != nil {
41
				return fmt.Errorf("load config: %w", err)
42
			}
43
			if cfg.Sumdb == nil || cfg.Sumdb.Key == "" {
44
				return fmt.Errorf("no sumdb.key in config (pass key as argument or set CURATOR_SUMDB_KEY)")
45
			}
46
			return runVerifier(cfg.Sumdb.Key)
47
		},
48
	}
49
50
	cmd.Flags().StringVarP(&configPath, "config", "c", "", "path to config file")
51
	return cmd
52
}
53
54
func runVerifier(input string) error {
55
	key, err := config.ResolveValue(input)
56
	if err != nil {
57
		return fmt.Errorf("read key: %w", err)
58
	}
59
	key = strings.TrimSpace(key)
60
61
	// Private key format: PRIVATE+KEY+<name>+<keyhash>+<base64(4-byte-hash + 32-byte-seed)>
62
	if !strings.HasPrefix(key, "PRIVATE+KEY+") {
63
		return fmt.Errorf("not a valid private key (expected PRIVATE+KEY+... prefix)")
64
	}
65
66
	rest := strings.TrimPrefix(key, "PRIVATE+KEY+")
67
	lastPlus := strings.LastIndex(rest, "+")
68
	if lastPlus < 0 {
69
		return fmt.Errorf("malformed private key")
70
	}
71
72
	nameAndHash := rest[:lastPlus]
73
	b64 := rest[lastPlus+1:]
74
75
	raw, err := base64.StdEncoding.DecodeString(b64)
76
	if err != nil {
77
		return fmt.Errorf("decode key: %w", err)
78
	}
79
80
	if len(raw) != 36 { // 4 bytes hash + 32 bytes seed
81
		return fmt.Errorf("unexpected key length: %d (expected 36)", len(raw))
82
	}
83
84
	hashBytes := raw[:4]
85
	seed := raw[4:36]
86
	pub := ed25519.NewKeyFromSeed(seed).Public().(ed25519.PublicKey)
87
88
	vkeyRaw := append(hashBytes, pub...)
89
	fmt.Printf("%s+%s\n", nameAndHash, base64.StdEncoding.EncodeToString(vkeyRaw))
90
91
	fmt.Fprintf(os.Stderr, "\nUsage:\n  export GOSUMDB=\"%s+%s\"\n", nameAndHash, base64.StdEncoding.EncodeToString(vkeyRaw))
92
93
	return nil
94
}
95

Source Files