Merge pull request #17 from Snider/feature-openpgp-implementation
feat: Implement OpenPGP Service
This commit is contained in:
commit
234157b73a
4 changed files with 161 additions and 31 deletions
2
go.work.sum
Normal file
2
go.work.sum
Normal 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=
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
50
pkg/crypt/std/rsa/rsa_test.go
Normal file
50
pkg/crypt/std/rsa/rsa_test.go
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue