feat: implement Milestone 3 integration points

Add comprehensive integration capabilities to Pop CLI:

- Multi-account support with named profiles
- Webhook management with signature verification
- External PGP key management (import/export/encrypt/decrypt/sign/verify)
- CLI plugin system for extensibility
- Complete documentation in README.md

All compilation errors fixed and build verified CLEAN.

Security review delegated to FRE-5202.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-12 17:31:58 -04:00
parent e7e77fcc20
commit bf26cd3ed6
16 changed files with 3566 additions and 85 deletions

299
cmd/pgp.go Normal file
View File

@@ -0,0 +1,299 @@
package cmd
import (
"fmt"
"os"
"github.com/frenocorp/pop/internal/pgp"
"github.com/spf13/cobra"
)
func pgpCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "pgp",
Short: "Manage PGP keys",
Long: `Import, export, list, and manage PGP keys for ProtonMail encryption.`,
}
cmd.AddCommand(pgpListCmd())
cmd.AddCommand(pgpImportCmd())
cmd.AddCommand(pgpExportCmd())
cmd.AddCommand(pgpRemoveCmd())
cmd.AddCommand(pgpEncryptCmd())
cmd.AddCommand(pgpDecryptCmd())
cmd.AddCommand(pgpSignCmd())
cmd.AddCommand(pgpVerifyCmd())
return cmd
}
func pgpListCmd() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all imported PGP keys",
RunE: func(cmd *cobra.Command, args []string) error {
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
keys, err := store.ListKeys()
if err != nil {
return fmt.Errorf("failed to list keys: %w", err)
}
if len(keys) == 0 {
fmt.Println("No PGP keys imported.")
return nil
}
for _, key := range keys {
fmt.Printf("Key ID: %s\n Fingerprint: %s\n Emails: %v\n Trust: %s\n Encrypt: %t Sign: %t\n\n",
key.KeyID, key.Fingerprint, key.Emails, key.TrustLevel, key.CanEncrypt, key.CanSign)
}
return nil
},
}
}
func pgpImportCmd() *cobra.Command {
var trustLevel, filePath string
cmd := &cobra.Command{
Use: "import <file>",
Short: "Import a PGP key from file",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
filePath = args[0]
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
key, err := store.ImportKeyFromFile(filePath, trustLevel)
if err != nil {
return fmt.Errorf("failed to import key: %w", err)
}
fmt.Printf("Imported key:\n ID: %s\n Fingerprint: %s\n Emails: %v\n",
key.KeyID, key.Fingerprint, key.Emails)
return nil
},
}
cmd.Flags().StringVar(&trustLevel, "trust", "unknown", "Trust level for the key")
return cmd
}
func pgpExportCmd() *cobra.Command {
var outputPath string
cmd := &cobra.Command{
Use: "export <key_id>",
Short: "Export a PGP key",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
keyID := args[0]
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
if outputPath == "" {
outputPath = keyID + ".asc"
}
if err := store.ExportKey(keyID, outputPath); err != nil {
return fmt.Errorf("failed to export key: %w", err)
}
fmt.Printf("Exported key to: %s\n", outputPath)
return nil
},
}
cmd.Flags().StringVarP(&outputPath, "output", "o", "", "Output file path")
return cmd
}
func pgpRemoveCmd() *cobra.Command {
return &cobra.Command{
Use: "remove <key_id>",
Short: "Remove a PGP key",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
keyID := args[0]
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
if err := store.RemoveKey(keyID); err != nil {
return fmt.Errorf("failed to remove key: %w", err)
}
fmt.Printf("Removed key: %s\n", keyID)
return nil
},
}
}
func pgpEncryptCmd() *cobra.Command {
var plaintext, keyID string
cmd := &cobra.Command{
Use: "encrypt <key_id> --plaintext \"text\"",
Short: "Encrypt plaintext with a public key",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
keyID = args[0]
}
if plaintext == "" {
return fmt.Errorf("plaintext is required (--plaintext)")
}
if keyID == "" {
return fmt.Errorf("key ID is required (positional argument)")
}
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
encrypted, err := store.EncryptData(keyID, plaintext)
if err != nil {
return fmt.Errorf("failed to encrypt: %w", err)
}
fmt.Println(encrypted)
return nil
},
}
cmd.Flags().StringVarP(&plaintext, "plaintext", "p", "", "Plaintext to encrypt")
cmd.MarkFlagRequired("plaintext")
return cmd
}
func pgpDecryptCmd() *cobra.Command {
var keyID, passphrase, encryptedData string
cmd := &cobra.Command{
Use: "decrypt <key_id>",
Short: "Decrypt PGP-encrypted data",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
keyID = args[0]
}
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
decrypted, err := store.DecryptData(keyID, encryptedData, passphrase)
if err != nil {
return fmt.Errorf("failed to decrypt: %w", err)
}
fmt.Println(decrypted)
return nil
},
}
cmd.Flags().StringVarP(&encryptedData, "encrypted", "e", "", "Encrypted data (armored)")
cmd.Flags().StringVarP(&passphrase, "passphrase", "P", "", "Passphrase for private key")
cmd.MarkFlagRequired("encrypted")
return cmd
}
func pgpSignCmd() *cobra.Command {
var plaintext, keyID, passphrase string
cmd := &cobra.Command{
Use: "sign <key_id> --plaintext \"text\"",
Short: "Sign plaintext with a private key",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
keyID = args[0]
}
if plaintext == "" {
return fmt.Errorf("plaintext is required (--plaintext)")
}
if keyID == "" {
return fmt.Errorf("key ID is required (positional argument)")
}
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
signature, err := store.SignData(keyID, plaintext, passphrase)
if err != nil {
return fmt.Errorf("failed to sign: %w", err)
}
fmt.Println(signature)
return nil
},
}
cmd.Flags().StringVarP(&plaintext, "plaintext", "p", "", "Plaintext to sign")
cmd.Flags().StringVarP(&passphrase, "passphrase", "P", "", "Passphrase for private key")
cmd.MarkFlagRequired("plaintext")
return cmd
}
func pgpVerifyCmd() *cobra.Command {
var keyID, message, signature string
cmd := &cobra.Command{
Use: "verify <key_id>",
Short: "Verify a detached signature",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
keyID = args[0]
}
store, err := pgp.NewKeyStore()
if err != nil {
return fmt.Errorf("failed to create PGP store: %w", err)
}
verified, err := store.VerifySignature(keyID, message, signature)
if err != nil {
return fmt.Errorf("verification failed: %w", err)
}
if verified {
fmt.Println("Signature is valid.")
} else {
fmt.Println("Signature is INVALID.")
}
return nil
},
}
cmd.Flags().StringVarP(&message, "message", "m", "", "Message to verify")
cmd.Flags().StringVarP(&signature, "signature", "s", "", "Detached signature")
cmd.MarkFlagRequired("message")
cmd.MarkFlagRequired("signature")
return cmd
}
func init() {
_ = os.Getenv
}