1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
package main
import (
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"fmt"
"os"
"path/filepath"
)
const (
// ConfigDir is where we store keys
ConfigDir = ".config/capability-tokens"
// PrivateKeyFile stores the ED25519 private key
PrivateKeyFile = "signing-key.private"
// PublicKeyFile stores the ED25519 public key
PublicKeyFile = "signing-key.public"
)
// KeyPair holds ED25519 signing keys
type KeyPair struct {
Private ed25519.PrivateKey
Public ed25519.PublicKey
}
// GetConfigDir returns the full path to the config directory
func GetConfigDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("cannot get home directory: %w", err)
}
return filepath.Join(home, ConfigDir), nil
}
// LoadOrGenerateKeys loads existing keys or generates new ones if they don't exist
func LoadOrGenerateKeys() (*KeyPair, error) {
configDir, err := GetConfigDir()
if err != nil {
return nil, err
}
privateKeyPath := filepath.Join(configDir, PrivateKeyFile)
publicKeyPath := filepath.Join(configDir, PublicKeyFile)
// Check if keys exist
if _, err := os.Stat(privateKeyPath); err == nil {
// Keys exist, load them
return loadKeys(privateKeyPath, publicKeyPath)
}
// Keys don't exist, generate new ones
return generateAndSaveKeys(configDir, privateKeyPath, publicKeyPath)
}
// loadKeys reads existing keys from disk
func loadKeys(privateKeyPath, publicKeyPath string) (*KeyPair, error) {
// Read private key
privateData, err := os.ReadFile(privateKeyPath)
if err != nil {
return nil, fmt.Errorf("cannot read private key: %w", err)
}
privateKey, err := base64.StdEncoding.DecodeString(string(privateData))
if err != nil {
return nil, fmt.Errorf("cannot decode private key: %w", err)
}
if len(privateKey) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("invalid private key size: %d (expected %d)", len(privateKey), ed25519.PrivateKeySize)
}
// Read public key
publicData, err := os.ReadFile(publicKeyPath)
if err != nil {
return nil, fmt.Errorf("cannot read public key: %w", err)
}
publicKey, err := base64.StdEncoding.DecodeString(string(publicData))
if err != nil {
return nil, fmt.Errorf("cannot decode public key: %w", err)
}
if len(publicKey) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key size: %d (expected %d)", len(publicKey), ed25519.PublicKeySize)
}
return &KeyPair{
Private: ed25519.PrivateKey(privateKey),
Public: ed25519.PublicKey(publicKey),
}, nil
}
// generateAndSaveKeys creates a new key pair and saves it to disk
func generateAndSaveKeys(configDir, privateKeyPath, publicKeyPath string) (*KeyPair, error) {
// Create config directory if it doesn't exist
if err := os.MkdirAll(configDir, 0700); err != nil {
return nil, fmt.Errorf("cannot create config directory: %w", err)
}
// Generate new ED25519 key pair
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("cannot generate keys: %w", err)
}
// Encode keys as base64
privateKeyB64 := base64.StdEncoding.EncodeToString(privateKey)
publicKeyB64 := base64.StdEncoding.EncodeToString(publicKey)
// Save private key (mode 0600 - owner read/write only)
if err := os.WriteFile(privateKeyPath, []byte(privateKeyB64), 0600); err != nil {
return nil, fmt.Errorf("cannot write private key: %w", err)
}
// Save public key (mode 0644 - readable by all)
if err := os.WriteFile(publicKeyPath, []byte(publicKeyB64), 0644); err != nil {
return nil, fmt.Errorf("cannot write public key: %w", err)
}
fmt.Fprintf(os.Stderr, "Generated new ED25519 key pair in %s\n", configDir)
return &KeyPair{
Private: privateKey,
Public: publicKey,
}, nil
}
// PublicKeyBase64 returns the public key as base64-encoded string
func (kp *KeyPair) PublicKeyBase64() string {
return base64.StdEncoding.EncodeToString(kp.Public)
}
|