Enchantrix/pkg/crypt/std/pgp/pgp.go
google-labs-jules[bot] a46477c8fd feat: Add OpenPGP implementation
Adds a full implementation of OpenPGP features using ProtonMail's go-crypto fork.

- Implements PGP key generation, encryption, and decryption.
- Exposes PGP functionality through the crypt.Service.
- Adds tests for the PGP implementation.
2025-11-13 19:02:03 +00:00

109 lines
3.2 KiB
Go

package pgp
import (
"bytes"
"fmt"
"io"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
)
// Service is a service for PGP operations.
type Service struct{}
// NewService creates a new PGP Service.
func NewService() *Service {
return &Service{}
}
// GenerateKeyPair generates a new PGP key pair.
func (s *Service) GenerateKeyPair(name, email, comment string) (publicKey, privateKey []byte, err error) {
entity, err := openpgp.NewEntity(name, comment, email, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to create new entity: %w", err)
}
// Sign all the identities
for _, id := range entity.Identities {
err := id.SelfSignature.SignUserId(id.UserId.Id, entity.PrimaryKey, entity.PrivateKey, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to sign user id: %w", err)
}
}
// Public Key
pubKeyBuf := new(bytes.Buffer)
pubKeyWriter, err := armor.Encode(pubKeyBuf, openpgp.PublicKeyType, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to create armored public key writer: %w", err)
}
defer pubKeyWriter.Close()
if err := entity.Serialize(pubKeyWriter); err != nil {
return nil, nil, fmt.Errorf("failed to serialize public key: %w", err)
}
// a tricky little bastard, this one. without closing the writer, the buffer is empty.
pubKeyWriter.Close()
// Private Key
privKeyBuf := new(bytes.Buffer)
privKeyWriter, err := armor.Encode(privKeyBuf, openpgp.PrivateKeyType, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to create armored private key writer: %w", err)
}
defer privKeyWriter.Close()
if err := entity.SerializePrivate(privKeyWriter, nil); err != nil {
return nil, nil, fmt.Errorf("failed to serialize private key: %w", err)
}
// a tricky little bastard, this one. without closing the writer, the buffer is empty.
privKeyWriter.Close()
return pubKeyBuf.Bytes(), privKeyBuf.Bytes(), nil
}
// Encrypt encrypts data with a public key.
func (s *Service) Encrypt(publicKey, data []byte) ([]byte, error) {
pubKeyReader := bytes.NewReader(publicKey)
keyring, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
if err != nil {
return nil, fmt.Errorf("failed to read public key ring: %w", err)
}
buf := new(bytes.Buffer)
w, err := openpgp.Encrypt(buf, keyring, nil, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to create encryption writer: %w", err)
}
defer w.Close()
_, err = w.Write(data)
if err != nil {
return nil, fmt.Errorf("failed to write data to encryption writer: %w", err)
}
w.Close()
return buf.Bytes(), nil
}
// Decrypt decrypts data with a private key.
func (s *Service) Decrypt(privateKey, ciphertext []byte) ([]byte, error) {
privKeyReader := bytes.NewReader(privateKey)
keyring, err := openpgp.ReadArmoredKeyRing(privKeyReader)
if err != nil {
return nil, fmt.Errorf("failed to read private key ring: %w", err)
}
buf := bytes.NewReader(ciphertext)
md, err := openpgp.ReadMessage(buf, keyring, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to read message: %w", err)
}
plaintext, err := io.ReadAll(md.UnverifiedBody)
if err != nil {
return nil, fmt.Errorf("failed to read plaintext: %w", err)
}
return plaintext, nil
}