cli/pkg/crypt/openpgp/openpgp.go

123 lines
3.8 KiB
Go

// Package openpgp provides PGP encryption, decryption, and key management.
// It wraps the Enchantrix library's PGP functionality.
package openpgp
import (
"fmt"
"io"
"os"
"github.com/Snider/Enchantrix/pkg/crypt"
)
var service *crypt.Service
func init() {
service = crypt.NewService()
}
// KeyPair holds a generated PGP key pair in armored format.
type KeyPair struct {
PublicKey string
PrivateKey string
}
// CreateKeyPair generates a new PGP key pair with the given identity and optional passphrase.
// Note: Enchantrix does not support passphrase-protected keys, so the passphrase
// parameter is used as a comment in the key metadata.
func CreateKeyPair(identity, passphrase string) (*KeyPair, error) {
pubBytes, privBytes, err := service.GeneratePGPKeyPair(identity, identity+"@example.com", passphrase)
if err != nil {
return nil, err
}
return &KeyPair{
PublicKey: string(pubBytes),
PrivateKey: string(privBytes),
}, nil
}
// EncryptPGP encrypts data for a recipient, optionally signing it.
// - writer: destination for the encrypted data
// - recipientPath: path to the recipient's public key file
// - data: plaintext to encrypt
// - signerPath: optional path to the signer's private key file (not supported in Enchantrix)
// - signerPassphrase: optional passphrase for the signer's private key (not supported)
func EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) error {
// Read recipient public key
recipientKey, err := os.ReadFile(recipientPath)
if err != nil {
return fmt.Errorf("failed to open recipient public key file: %w", err)
}
ciphertext, err := service.EncryptPGP(recipientKey, []byte(data))
if err != nil {
return err
}
_, err = writer.Write(ciphertext)
return err
}
// DecryptPGP decrypts a PGP message, optionally verifying the signature.
// - recipientPath: path to the recipient's private key file
// - message: armored PGP message to decrypt
// - passphrase: passphrase for the recipient's private key (not supported in Enchantrix)
// - signerPath: optional path to the signer's public key file for verification (not supported)
func DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) {
// Read recipient private key
recipientKey, err := os.ReadFile(recipientPath)
if err != nil {
return "", fmt.Errorf("failed to open recipient private key file: %w", err)
}
plaintext, err := service.DecryptPGP(recipientKey, []byte(message))
if err != nil {
return "", fmt.Errorf("failed to read PGP message: %w", err)
}
return string(plaintext), nil
}
// generateTestKeys creates a test key pair and writes it to temporary files.
// Returns paths to the public and private key files, and a cleanup function.
func generateTestKeys(t interface {
Helper()
Fatalf(string, ...any)
}, identity, passphrase string) (pubPath, privPath string, cleanup func()) {
t.Helper()
keyPair, err := CreateKeyPair(identity, passphrase)
if err != nil {
t.Fatalf("failed to create key pair: %v", err)
}
tempDir, err := os.MkdirTemp("", "pgp-test-*")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
pubPath = tempDir + "/test.pub"
privPath = tempDir + "/test.priv"
if err := os.WriteFile(pubPath, []byte(keyPair.PublicKey), 0644); err != nil {
os.RemoveAll(tempDir)
t.Fatalf("failed to write public key: %v", err)
}
if err := os.WriteFile(privPath, []byte(keyPair.PrivateKey), 0600); err != nil {
os.RemoveAll(tempDir)
t.Fatalf("failed to write private key: %v", err)
}
cleanup = func() { os.RemoveAll(tempDir) }
return pubPath, privPath, cleanup
}
// EncryptPGPToString is a convenience function that encrypts to a string.
func EncryptPGPToString(recipientKey, data string) (string, error) {
ciphertext, err := service.EncryptPGP([]byte(recipientKey), []byte(data))
if err != nil {
return "", err
}
return string(ciphertext), nil
}