diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..e4b0a5d --- /dev/null +++ b/go.work.sum @@ -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= diff --git a/pkg/crypt/crypt.go b/pkg/crypt/crypt.go index 26d96b1..8079f4e 100644 --- a/pkg/crypt/crypt.go +++ b/pkg/crypt/crypt.go @@ -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) +} \ No newline at end of file diff --git a/pkg/crypt/std/rsa/rsa.go b/pkg/crypt/std/rsa/rsa.go index f0082cd..5c70152 100644 --- a/pkg/crypt/std/rsa/rsa.go +++ b/pkg/crypt/std/rsa/rsa.go @@ -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 +} \ No newline at end of file diff --git a/pkg/crypt/std/rsa/rsa_test.go b/pkg/crypt/std/rsa/rsa_test.go new file mode 100644 index 0000000..cd08d63 --- /dev/null +++ b/pkg/crypt/std/rsa/rsa_test.go @@ -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) +}