Files
sgard/cmd/sgard/encrypt.go
Kyle Isom 76a53320c1 Step 19: Encryption CLI, slot management, proto updates.
CLI: sgard encrypt init [--fido2], add-fido2 [--label], remove-slot,
list-slots, change-passphrase. sgard add --encrypt flag with
passphrase prompt for DEK unlock.

Garden: RemoveSlot (refuses last slot), ListSlots, ChangePassphrase
(re-wraps DEK with new passphrase, fresh salt).

Proto: ManifestEntry gains encrypted + plaintext_hash fields. New
KekSlot and Encryption messages. Manifest gains encryption field.

server/convert.go: full round-trip conversion for encryption section
including KekSlot map.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:25:20 -07:00

167 lines
3.6 KiB
Go

package main
import (
"fmt"
"sort"
"github.com/kisom/sgard/garden"
"github.com/spf13/cobra"
)
var encryptCmd = &cobra.Command{
Use: "encrypt",
Short: "Manage encryption keys and slots",
}
var fido2InitFlag bool
var encryptInitCmd = &cobra.Command{
Use: "init",
Short: "Initialize encryption (creates DEK and passphrase slot)",
RunE: func(cmd *cobra.Command, args []string) error {
g, err := garden.Open(repoFlag)
if err != nil {
return err
}
passphrase, err := promptPassphrase()
if err != nil {
return err
}
if err := g.EncryptInit(passphrase); err != nil {
return err
}
fmt.Println("Encryption initialized with passphrase slot.")
if fido2InitFlag {
fmt.Println("FIDO2 support requires a hardware device implementation.")
fmt.Println("Run 'sgard encrypt add-fido2' when a FIDO2 device is available.")
}
return nil
},
}
var fido2LabelFlag string
var addFido2Cmd = &cobra.Command{
Use: "add-fido2",
Short: "Add a FIDO2 KEK slot",
RunE: func(cmd *cobra.Command, args []string) error {
g, err := garden.Open(repoFlag)
if err != nil {
return err
}
if !g.HasEncryption() {
return fmt.Errorf("encryption not initialized; run sgard encrypt init first")
}
if err := g.UnlockDEK(promptPassphrase); err != nil {
return err
}
// Real FIDO2 device implementation would go here.
// For now, this is a placeholder that explains the requirement.
return fmt.Errorf("FIDO2 hardware support not yet implemented; requires libfido2 binding")
},
}
var removeSlotCmd = &cobra.Command{
Use: "remove-slot <name>",
Short: "Remove a KEK slot",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
g, err := garden.Open(repoFlag)
if err != nil {
return err
}
if err := g.RemoveSlot(args[0]); err != nil {
return err
}
fmt.Printf("Removed slot %q.\n", args[0])
return nil
},
}
var listSlotsCmd = &cobra.Command{
Use: "list-slots",
Short: "List all KEK slots",
RunE: func(cmd *cobra.Command, args []string) error {
g, err := garden.Open(repoFlag)
if err != nil {
return err
}
slots := g.ListSlots()
if len(slots) == 0 {
fmt.Println("No encryption configured.")
return nil
}
// Sort for consistent output.
names := make([]string, 0, len(slots))
for name := range slots {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%-30s %s\n", name, slots[name])
}
return nil
},
}
var changePassphraseCmd = &cobra.Command{
Use: "change-passphrase",
Short: "Change the passphrase for the passphrase KEK slot",
RunE: func(cmd *cobra.Command, args []string) error {
g, err := garden.Open(repoFlag)
if err != nil {
return err
}
if !g.HasEncryption() {
return fmt.Errorf("encryption not initialized")
}
// Unlock with current passphrase.
fmt.Println("Enter current passphrase:")
if err := g.UnlockDEK(promptPassphrase); err != nil {
return err
}
// Get new passphrase.
fmt.Println("Enter new passphrase:")
newPassphrase, err := promptPassphrase()
if err != nil {
return err
}
if err := g.ChangePassphrase(newPassphrase); err != nil {
return err
}
fmt.Println("Passphrase changed.")
return nil
},
}
func init() {
encryptInitCmd.Flags().BoolVar(&fido2InitFlag, "fido2", false, "also set up FIDO2 (placeholder)")
addFido2Cmd.Flags().StringVar(&fido2LabelFlag, "label", "", "slot label (default: fido2/<hostname>)")
encryptCmd.AddCommand(encryptInitCmd)
encryptCmd.AddCommand(addFido2Cmd)
encryptCmd.AddCommand(removeSlotCmd)
encryptCmd.AddCommand(listSlotsCmd)
encryptCmd.AddCommand(changePassphraseCmd)
rootCmd.AddCommand(encryptCmd)
}