package main import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "encoding/hex" "flag" "fmt" "io" "os" "path/filepath" "strings" ) func runKeygen(args []string) error { fs := flag.NewFlagSet("keygen", flag.ExitOnError) out := fs.String("out", "", "output private key path") force := fs.Bool("force", false, "overwrite output file if it exists") if err := fs.Parse(args); err != nil { return err } if strings.TrimSpace(*out) == "" { return fmt.Errorf("--out is required") } fingerprint, err := writeMirrorPrivateKey(*out, *force, rand.Reader) if err != nil { return err } fmt.Printf("generated key fingerprint=%s path=%s\n", fingerprint, *out) return nil } func writeMirrorPrivateKey(out string, force bool, random io.Reader) (string, error) { seed := make([]byte, ed25519.SeedSize) if _, err := io.ReadFull(random, seed); err != nil { return "", fmt.Errorf("generate seed: %w", err) } fingerprint := keyFingerprint(ed25519.NewKeyFromSeed(seed).Public().(ed25519.PublicKey)) if err := writeAtomicFile(out, append([]byte(hex.EncodeToString(seed)), '\n'), force, 0o600); err != nil { return "", err } return fingerprint, nil } func writeAtomicFile(path string, data []byte, force bool, mode os.FileMode) error { if path == "" { return fmt.Errorf("output path is required") } if _, err := os.Stat(path); err == nil && !force { return fmt.Errorf("output path already exists: %s (use --force to overwrite)", path) } else if err != nil && !os.IsNotExist(err) { return fmt.Errorf("stat output path: %w", err) } dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("mkdir output dir: %w", err) } tmp, err := os.CreateTemp(dir, ".plutia-keygen-*") if err != nil { return fmt.Errorf("create temp key file: %w", err) } tmpPath := tmp.Name() closed := false defer func() { if !closed { _ = tmp.Close() } _ = os.Remove(tmpPath) }() if err := tmp.Chmod(mode); err != nil { return fmt.Errorf("chmod temp key file: %w", err) } if _, err := tmp.Write(data); err != nil { return fmt.Errorf("write temp key file: %w", err) } if err := tmp.Sync(); err != nil { return fmt.Errorf("sync temp key file: %w", err) } if err := tmp.Close(); err != nil { return fmt.Errorf("close temp key file: %w", err) } closed = true if force { if err := os.Remove(path); err != nil && !os.IsNotExist(err) { return fmt.Errorf("remove existing output file: %w", err) } } if err := os.Rename(tmpPath, path); err != nil { return fmt.Errorf("rename temp key file: %w", err) } if err := os.Chmod(path, mode); err != nil { return fmt.Errorf("chmod output key file: %w", err) } return nil } func keyFingerprint(pub ed25519.PublicKey) string { sum := sha256.Sum256(pub) return "ed25519:" + hex.EncodeToString(sum[:8]) }