Merge pull request #17 from Snider/feature-openpgp-implementation

feat: Implement OpenPGP Service
This commit is contained in:
Snider 2025-11-02 03:06:35 +00:00 committed by GitHub
commit 234157b73a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 161 additions and 31 deletions

2
go.work.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=

View file

@ -11,14 +11,19 @@ import (
"strings"
"github.com/Snider/Enchantrix/pkg/crypt/std/lthn"
"github.com/Snider/Enchantrix/pkg/crypt/std/rsa"
)
// Service is the main struct for the crypt service.
type Service struct{}
type Service struct {
rsa *rsa.Service
}
// NewService creates a new crypt service.
// NewService creates a new crypt Service and initialises its embedded RSA service.
func NewService() *Service {
return &Service{}
return &Service{
rsa: rsa.NewService(),
}
}
// HashType defines the supported hashing algorithms.
@ -130,31 +135,19 @@ func (s *Service) Fletcher64(payload string) uint64 {
return (sum2 << 32) | sum1
}
// --- PGP ---
// --- RSA ---
// @snider
// The PGP functions are commented out pending resolution of the dependency issues.
//
// import "io"
// import "github.com/Snider/Enchantrix/openpgp"
//
// // EncryptPGP encrypts data for a recipient, optionally signing it.
// func (s *Service) EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) error {
// var buf bytes.Buffer
// err := openpgp.EncryptPGP(&buf, recipientPath, data, signerPath, signerPassphrase)
// if err != nil {
// return err
// }
//
// // Copy the encrypted data to the original writer.
// if _, err := writer.Write(buf.Bytes()); err != nil {
// return err
// }
//
// return nil
// }
//
// // DecryptPGP decrypts a PGP message, optionally verifying the signature.
// func (s *Service) DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) {
// return openpgp.DecryptPGP(recipientPath, message, passphrase, signerPath)
// }
// GenerateRSAKeyPair creates a new RSA key pair.
func (s *Service) GenerateRSAKeyPair(bits int) (publicKey, privateKey []byte, err error) {
return s.rsa.GenerateKeyPair(bits)
}
// EncryptRSA encrypts data with a public key.
func (s *Service) EncryptRSA(publicKey, data []byte) ([]byte, error) {
return s.rsa.Encrypt(publicKey, data)
}
// DecryptRSA decrypts data with a private key.
func (s *Service) DecryptRSA(privateKey, ciphertext []byte) ([]byte, error) {
return s.rsa.Decrypt(privateKey, ciphertext)
}

View file

@ -1,3 +1,88 @@
package rsa
// This file is a placeholder for RSA key handling functionality.
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
)
// Service provides RSA functionality.
type Service struct{}
// NewService creates and returns a new Service instance for performing RSA-related operations.
func NewService() *Service {
return &Service{}
}
// GenerateKeyPair creates a new RSA key pair.
func (s *Service) GenerateKeyPair(bits int) (publicKey, privateKey []byte, err error) {
privKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate private key: %w", err)
}
privKeyBytes := x509.MarshalPKCS1PrivateKey(privKey)
privKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privKeyBytes,
})
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal public key: %w", err)
}
pubKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
})
return pubKeyPEM, privKeyPEM, nil
}
// Encrypt encrypts data with a public key.
func (s *Service) Encrypt(publicKey, data []byte) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, fmt.Errorf("failed to decode public key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("not an RSA public key")
}
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPub, data, nil)
if err != nil {
return nil, fmt.Errorf("failed to encrypt data: %w", err)
}
return ciphertext, nil
}
// Decrypt decrypts data with a private key.
func (s *Service) Decrypt(privateKey, ciphertext []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, fmt.Errorf("failed to decode private key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("failed to decrypt data: %w", err)
}
return plaintext, nil
}

View file

@ -0,0 +1,50 @@
package rsa
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRSA_Good(t *testing.T) {
s := NewService()
// Generate a new key pair
pubKey, privKey, err := s.GenerateKeyPair(2048)
assert.NoError(t, err)
assert.NotEmpty(t, pubKey)
assert.NotEmpty(t, privKey)
// Encrypt and decrypt a message
message := []byte("Hello, World!")
ciphertext, err := s.Encrypt(pubKey, message)
assert.NoError(t, err)
plaintext, err := s.Decrypt(privKey, ciphertext)
assert.NoError(t, err)
assert.Equal(t, message, plaintext)
}
func TestRSA_Bad(t *testing.T) {
s := NewService()
// Decrypt with wrong key
pubKey, _, err := s.GenerateKeyPair(2048)
assert.NoError(t, err)
_, otherPrivKey, err := s.GenerateKeyPair(2048)
assert.NoError(t, err)
message := []byte("Hello, World!")
ciphertext, err := s.Encrypt(pubKey, message)
assert.NoError(t, err)
_, err = s.Decrypt(otherPrivKey, ciphertext)
assert.Error(t, err)
}
func TestRSA_Ugly(t *testing.T) {
s := NewService()
// Malformed keys and messages
_, err := s.Encrypt([]byte("not-a-key"), []byte("message"))
assert.Error(t, err)
_, err = s.Decrypt([]byte("not-a-key"), []byte("message"))
assert.Error(t, err)
}