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:
299
cmd/pgp.go
Normal file
299
cmd/pgp.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user