Merge pull request #33 from Snider/feature-pgp-implementation
Feature pgp implementation
This commit is contained in:
commit
d649e9e69e
18 changed files with 839 additions and 342 deletions
|
|
@ -1,8 +1,13 @@
|
|||
# Enchantrix
|
||||
|
||||
[](https://goreportcard.com/report/github.com/Snider/Enchantrix)
|
||||
[](https://godoc.org/github.com/Snider/Enchantrix)
|
||||
[](https://github.com/Snider/Enchantrix/actions/workflows/go.yml)
|
||||
[](https://codecov.io/github/Snider/Enchantrix)
|
||||
[](https://github.com/Snider/Enchantrix/releases/latest)
|
||||
[](https://github.com/Snider/Enchantrix/blob/main/LICENCE)
|
||||
|
||||
Enchantrix is a Go-based encryption library for the Core framework, designed to provide a secure and easy-to-use framework for handling sensitive data in Web3 applications. It will feature Poly-ChaCha stream proxying and a custom `.trix` file format for encrypted data.
|
||||
Enchantrix is a Go-based encryption library designed to provide a secure and easy-to-use framework for handling sensitive data in Web3 applications. It will feature Poly-ChaCha stream proxying and a custom `.trix` file format for encrypted data.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
|
|||
|
|
@ -2,105 +2,148 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// executeCommand executes the root command with the given arguments and returns the output.
|
||||
func executeCommand(args ...string) (string, error) {
|
||||
b := new(bytes.Buffer)
|
||||
rootCmd.SetOut(b)
|
||||
rootCmd.SetErr(b)
|
||||
rootCmd.SetArgs(args)
|
||||
err := rootCmd.Execute()
|
||||
return b.String(), err
|
||||
func TestMain_Good(t *testing.T) {
|
||||
// Redirect stdout to a buffer
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Run the main function
|
||||
main()
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
// Read the output from the buffer
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
|
||||
// Check that the output contains the help message
|
||||
assert.Contains(t, buf.String(), "Usage:")
|
||||
}
|
||||
|
||||
// executeCommandWithStdin executes the root command with the given arguments and stdin,
|
||||
// and returns the output.
|
||||
func executeCommandWithStdin(stdin string, args ...string) (string, error) {
|
||||
b := new(bytes.Buffer)
|
||||
rootCmd.SetOut(b)
|
||||
rootCmd.SetErr(b)
|
||||
rootCmd.SetIn(strings.NewReader(stdin))
|
||||
rootCmd.SetArgs(args)
|
||||
err := rootCmd.Execute()
|
||||
// reset stdin
|
||||
rootCmd.SetIn(os.Stdin)
|
||||
return b.String(), err
|
||||
}
|
||||
|
||||
func TestRootCommand(t *testing.T) {
|
||||
output, err := executeCommand()
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, output, "trix [command]")
|
||||
}
|
||||
|
||||
func TestEncodeDecodeCommand(t *testing.T) {
|
||||
// 1. Create original payload
|
||||
originalPayload := "hello world"
|
||||
inputFile, _ := os.CreateTemp("", "input")
|
||||
defer os.Remove(inputFile.Name())
|
||||
inputFile.Write([]byte(originalPayload))
|
||||
inputFile.Close()
|
||||
|
||||
// 2. Encode it to a file
|
||||
encodedFile, _ := os.CreateTemp("", "encoded")
|
||||
defer os.Remove(encodedFile.Name())
|
||||
_, err := executeCommand("encode", "-i", inputFile.Name(), "-o", encodedFile.Name(), "-m", "magc", "reverse")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 3. Decode it back
|
||||
decodedFile, _ := os.CreateTemp("", "decoded")
|
||||
defer os.Remove(decodedFile.Name())
|
||||
_, err = executeCommand("decode", "-i", encodedFile.Name(), "-o", decodedFile.Name(), "-m", "magc", "reverse")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 4. Verify content
|
||||
finalPayload, err := os.ReadFile(decodedFile.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, originalPayload, string(finalPayload))
|
||||
}
|
||||
|
||||
func TestHashCommand(t *testing.T) {
|
||||
// Test with input file
|
||||
inputFile, _ := os.CreateTemp("", "input")
|
||||
defer os.Remove(inputFile.Name())
|
||||
inputFile.Write([]byte("hello"))
|
||||
inputFile.Close()
|
||||
output, err := executeCommand("hash", "md5", "-i", inputFile.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "5d41402abc4b2a76b9719d911017c592", strings.TrimSpace(output))
|
||||
|
||||
// Test with stdin
|
||||
output, err = executeCommandWithStdin("hello", "hash", "md5")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "5d41402abc4b2a76b9719d911017c592", strings.TrimSpace(output))
|
||||
|
||||
// Test error cases
|
||||
_, err = executeCommand("hash")
|
||||
assert.Error(t, err)
|
||||
_, err = executeCommand("hash", "invalid-algo")
|
||||
assert.Error(t, err)
|
||||
_, err = executeCommand("hash", "md5", "-i", "nonexistent-file")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMainFunction(t *testing.T) {
|
||||
// This test is to ensure the main function is covered
|
||||
// We run it in a separate process to avoid os.Exit calls
|
||||
if os.Getenv("GO_TEST_MAIN") == "1" {
|
||||
main()
|
||||
return
|
||||
func TestMain_Bad(t *testing.T) {
|
||||
oldExit := exit
|
||||
defer func() { exit = oldExit }()
|
||||
var exitCode int
|
||||
exit = func(code int) {
|
||||
exitCode = code
|
||||
}
|
||||
cmd := exec.Command(os.Args[0], "-test.run=TestMainFunction")
|
||||
cmd.Env = append(os.Environ(), "GO_TEST_MAIN=1")
|
||||
err := cmd.Run()
|
||||
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
|
||||
t.Fatalf("main function exited with error: %v", err)
|
||||
rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
return errors.New("test error")
|
||||
}
|
||||
// The rootCmd needs to be reset so that the test can be run again
|
||||
defer func() { rootCmd = &cobra.Command{
|
||||
Use: "trix",
|
||||
Short: "A tool for encoding and decoding .trix files",
|
||||
Long: `trix is a command-line tool for working with the .trix file format, which is used for storing encrypted data.`,
|
||||
}
|
||||
}()
|
||||
main()
|
||||
assert.Equal(t, 1, exitCode)
|
||||
}
|
||||
|
||||
func TestHandleSigil_Good(t *testing.T) {
|
||||
// Create a dummy command
|
||||
cmd := &cobra.Command{}
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.SetOut(buf)
|
||||
|
||||
// Run the handleSigil function
|
||||
err := handleSigil(cmd, "base64", "hello")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the output is the base64 encoded string
|
||||
assert.Equal(t, "aGVsbG8=", strings.TrimSpace(buf.String()))
|
||||
}
|
||||
|
||||
func TestHandleSigil_Bad(t *testing.T) {
|
||||
cmd := &cobra.Command{}
|
||||
err := handleSigil(cmd, "bad-sigil", "hello")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRunEncodeAndDecode_Good(t *testing.T) {
|
||||
// Encode
|
||||
encodeCmd := &cobra.Command{}
|
||||
encodeBuf := new(bytes.Buffer)
|
||||
encodeCmd.SetOut(encodeBuf)
|
||||
encodeCmd.SetIn(strings.NewReader("hello"))
|
||||
encodeCmd.Flags().StringP("input", "i", "-", "Input file or string (or stdin)")
|
||||
encodeCmd.Flags().StringP("output", "o", "-", "Output file")
|
||||
encodeCmd.Flags().StringP("magic", "m", "TEST", "Magic number (4 bytes)")
|
||||
err := runEncode(encodeCmd, []string{"base64"})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, encodeBuf.String())
|
||||
|
||||
// Decode
|
||||
decodeCmd := &cobra.Command{}
|
||||
decodeBuf := new(bytes.Buffer)
|
||||
decodeCmd.SetOut(decodeBuf)
|
||||
decodeCmd.SetIn(encodeBuf) // Use the output of the encode as the input for the decode
|
||||
decodeCmd.Flags().StringP("input", "i", "-", "Input file or string (or stdin)")
|
||||
decodeCmd.Flags().StringP("output", "o", "-", "Output file")
|
||||
decodeCmd.Flags().StringP("magic", "m", "TEST", "Magic number (4 bytes)")
|
||||
err = runDecode(decodeCmd, []string{"base64"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello", strings.TrimSpace(decodeBuf.String()))
|
||||
}
|
||||
|
||||
func TestRunEncode_Bad(t *testing.T) {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().StringP("magic", "m", "bad", "Magic number (4 bytes)")
|
||||
err := runEncode(cmd, []string{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRunDecode_Bad(t *testing.T) {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().StringP("magic", "m", "bad", "Magic number (4 bytes)")
|
||||
err := runDecode(cmd, []string{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRunHash_Good(t *testing.T) {
|
||||
cmd := &cobra.Command{}
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetIn(strings.NewReader("hello"))
|
||||
cmd.Flags().StringP("input", "i", "-", "Input file or string (or stdin)")
|
||||
|
||||
// Run the runHash function
|
||||
err := runHash(cmd, []string{"sha256"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the output is not empty
|
||||
assert.NotEmpty(t, buf.String())
|
||||
}
|
||||
|
||||
func TestRunHash_Bad(t *testing.T) {
|
||||
cmd := &cobra.Command{}
|
||||
err := runHash(cmd, []string{"bad-hash"})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCreateSigilRunE_Good(t *testing.T) {
|
||||
cmd := &cobra.Command{}
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetIn(strings.NewReader("hello"))
|
||||
cmd.Flags().StringP("input", "i", "-", "Input file or string (or stdin)")
|
||||
|
||||
// Run the createSigilRunE function
|
||||
runE := createSigilRunE("base64")
|
||||
err := runE(cmd, []string{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
27
examples/checksums/main.go
Normal file
27
examples/checksums/main.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- Checksum Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// Luhn
|
||||
luhnPayloadGood := "49927398716"
|
||||
luhnPayloadBad := "49927398717"
|
||||
fmt.Printf("Luhn Checksum:\n")
|
||||
fmt.Printf(" - Payload '%s' is valid: %v\n", luhnPayloadGood, cryptService.Luhn(luhnPayloadGood))
|
||||
fmt.Printf(" - Payload '%s' is valid: %v\n", luhnPayloadBad, cryptService.Luhn(luhnPayloadBad))
|
||||
|
||||
// Fletcher
|
||||
fletcherPayload := "abcde"
|
||||
fmt.Printf("\nFletcher Checksums (Payload: \"%s\"):\n", fletcherPayload)
|
||||
fmt.Printf(" - Fletcher16: %d\n", cryptService.Fletcher16(fletcherPayload))
|
||||
fmt.Printf(" - Fletcher32: %d\n", cryptService.Fletcher32(fletcherPayload))
|
||||
fmt.Printf(" - Fletcher64: %d\n", cryptService.Fletcher64(fletcherPayload))
|
||||
fmt.Println()
|
||||
}
|
||||
21
examples/coverage_report/main.go
Normal file
21
examples/coverage_report/main.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- Test Coverage Demo ---")
|
||||
fmt.Println("")
|
||||
fmt.Println("This example demonstrates how to generate and interpret a test coverage report.")
|
||||
fmt.Println("")
|
||||
fmt.Println("1. Generate a coverage profile:")
|
||||
fmt.Println(" go test ./... -coverprofile=coverage.out")
|
||||
fmt.Println("")
|
||||
fmt.Println("2. View the coverage report in your browser:")
|
||||
fmt.Println(" go tool cover -html=coverage.out")
|
||||
fmt.Println("")
|
||||
fmt.Println("3. View the coverage report in your terminal:")
|
||||
fmt.Println(" go tool cover -func=coverage.out")
|
||||
fmt.Println("")
|
||||
}
|
||||
57
examples/examples_test.go
Normal file
57
examples/examples_test.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package examples_test
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExample_Checksums(t *testing.T) {
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = "./checksums"
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err, string(out))
|
||||
}
|
||||
|
||||
func TestExample_Hash(t *testing.T) {
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = "./hash"
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err, string(out))
|
||||
}
|
||||
|
||||
func TestExample_PGPEncryptDecrypt(t *testing.T) {
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = "./pgp_encrypt_decrypt"
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err, string(out))
|
||||
}
|
||||
|
||||
func TestExample_PGPGenerateKeys(t *testing.T) {
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = "./pgp_generate_keys"
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err, string(out))
|
||||
}
|
||||
|
||||
func TestExample_PGPSignVerify(t *testing.T) {
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = "./pgp_sign_verify"
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err, string(out))
|
||||
}
|
||||
|
||||
func TestExample_PGPSymmetricEncrypt(t *testing.T) {
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = "./pgp_symmetric_encrypt"
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err, string(out))
|
||||
}
|
||||
|
||||
func TestExample_RSA(t *testing.T) {
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = "./rsa"
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err, string(out))
|
||||
}
|
||||
28
examples/hash/main.go
Normal file
28
examples/hash/main.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- Hashing Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
payload := "Enchantrix"
|
||||
|
||||
hashTypes := []crypt.HashType{
|
||||
crypt.LTHN,
|
||||
crypt.MD5,
|
||||
crypt.SHA1,
|
||||
crypt.SHA256,
|
||||
crypt.SHA512,
|
||||
}
|
||||
|
||||
fmt.Printf("Payload to hash: \"%s\"\n", payload)
|
||||
for _, hashType := range hashTypes {
|
||||
hash := cryptService.Hash(hashType, payload)
|
||||
fmt.Printf(" - %-6s: %s\n", hashType, hash)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
250
examples/main.go
250
examples/main.go
|
|
@ -1,250 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
"github.com/Snider/Enchantrix/pkg/crypt/std/chachapoly"
|
||||
"github.com/Snider/Enchantrix/pkg/enchantrix"
|
||||
"github.com/Snider/Enchantrix/pkg/trix"
|
||||
)
|
||||
|
||||
func main() {
|
||||
demoTrix()
|
||||
demoHashing()
|
||||
demoChecksums()
|
||||
demoRSA()
|
||||
demoSigils()
|
||||
}
|
||||
|
||||
func demoTrix() {
|
||||
fmt.Println("--- Trix & Sigil Chaining Demo ---")
|
||||
|
||||
// 1. Original plaintext (JSON data) and encryption key
|
||||
type Message struct {
|
||||
Author string `json:"author"`
|
||||
Time int64 `json:"time"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
originalMessage := Message{Author: "Jules", Time: time.Now().Unix(), Body: "This is a super secret message!"}
|
||||
plaintext, err := json.Marshal(originalMessage)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal JSON: %v", err)
|
||||
}
|
||||
key := make([]byte, 32) // In a real application, use a secure key
|
||||
for i := range key {
|
||||
key[i] = 1
|
||||
}
|
||||
|
||||
fmt.Printf("Original Payload (JSON):\n%s\n\n", plaintext)
|
||||
|
||||
// 2. Create a Trix container with the plaintext and attach a chain of sigils
|
||||
sigilChain := []string{"json-indent", "gzip", "base64", "reverse"}
|
||||
trixContainer := &trix.Trix{
|
||||
Header: map[string]interface{}{},
|
||||
Payload: plaintext,
|
||||
InSigils: sigilChain,
|
||||
}
|
||||
|
||||
// 3. Pack the Trix container to apply the sigil transformations
|
||||
fmt.Println("Packing payload with sigils:", sigilChain)
|
||||
if err := trixContainer.Pack(); err != nil {
|
||||
log.Fatalf("Failed to pack trix container: %v", err)
|
||||
}
|
||||
fmt.Printf("Packed (obfuscated) payload is now non-human-readable bytes.\n\n")
|
||||
|
||||
// 4. Encrypt the packed payload
|
||||
ciphertext, err := chachapoly.Encrypt(trixContainer.Payload, key)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to encrypt: %v", err)
|
||||
}
|
||||
trixContainer.Payload = ciphertext // Update the payload with the ciphertext
|
||||
|
||||
// 5. Add encryption metadata and checksum to the header
|
||||
nonce := ciphertext[:24]
|
||||
trixContainer.Header = map[string]interface{}{
|
||||
"content_type": "application/json",
|
||||
"encryption_algorithm": "chacha20poly1305",
|
||||
"nonce": base64.StdEncoding.EncodeToString(nonce),
|
||||
"created_at": time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
trixContainer.ChecksumAlgo = crypt.SHA512
|
||||
fmt.Printf("Checksum will be calculated with %s and added to the header.\n", trixContainer.ChecksumAlgo)
|
||||
|
||||
// 6. Encode the .trix container into its binary format
|
||||
magicNumber := "MyT1"
|
||||
encodedTrix, err := trix.Encode(trixContainer, magicNumber, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to encode .trix container: %v", err)
|
||||
}
|
||||
fmt.Println("Successfully created .trix container.")
|
||||
|
||||
// --- DECODING ---
|
||||
fmt.Println("--- DECODING ---")
|
||||
|
||||
// 7. Decode the .trix container
|
||||
decodedTrix, err := trix.Decode(encodedTrix, magicNumber, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to decode .trix container: %v", err)
|
||||
}
|
||||
fmt.Println("Successfully decoded .trix container. Checksum verified.")
|
||||
fmt.Printf("Decoded Header: %+v\n", decodedTrix.Header)
|
||||
|
||||
// 8. Decrypt the payload
|
||||
decryptedPayload, err := chachapoly.Decrypt(decodedTrix.Payload, key)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to decrypt: %v", err)
|
||||
}
|
||||
decodedTrix.Payload = decryptedPayload
|
||||
fmt.Println("Payload decrypted.")
|
||||
|
||||
// 9. Unpack the Trix container to reverse the sigil transformations
|
||||
decodedTrix.InSigils = trixContainer.InSigils // Re-attach sigils for unpacking
|
||||
fmt.Println("Unpacking payload by reversing sigils:", decodedTrix.InSigils)
|
||||
if err := decodedTrix.Unpack(); err != nil {
|
||||
log.Fatalf("Failed to unpack trix container: %v", err)
|
||||
}
|
||||
fmt.Printf("Unpacked (original) payload:\n%s\n", decodedTrix.Payload)
|
||||
|
||||
// 10. Verify the result
|
||||
// To properly verify, we need to compact the indented JSON before comparing
|
||||
var compactedPayload bytes.Buffer
|
||||
if err := json.Compact(&compactedPayload, decodedTrix.Payload); err != nil {
|
||||
log.Fatalf("Failed to compact final payload for verification: %v", err)
|
||||
}
|
||||
|
||||
if bytes.Equal(plaintext, compactedPayload.Bytes()) {
|
||||
fmt.Println("\nSuccess! The message was decrypted and unpacked correctly.")
|
||||
} else {
|
||||
fmt.Println("\nFailure! The final payload does not match the original.")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func demoHashing() {
|
||||
fmt.Println("--- Hashing Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
payload := "Enchantrix"
|
||||
|
||||
hashTypes := []crypt.HashType{
|
||||
crypt.LTHN,
|
||||
crypt.MD5,
|
||||
crypt.SHA1,
|
||||
crypt.SHA256,
|
||||
crypt.SHA512,
|
||||
}
|
||||
|
||||
fmt.Printf("Payload to hash: \"%s\"\n", payload)
|
||||
for _, hashType := range hashTypes {
|
||||
hash := cryptService.Hash(hashType, payload)
|
||||
fmt.Printf(" - %-6s: %s\n", hashType, hash)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func demoChecksums() {
|
||||
fmt.Println("--- Checksum Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// Luhn
|
||||
luhnPayloadGood := "49927398716"
|
||||
luhnPayloadBad := "49927398717"
|
||||
fmt.Printf("Luhn Checksum:\n")
|
||||
fmt.Printf(" - Payload '%s' is valid: %v\n", luhnPayloadGood, cryptService.Luhn(luhnPayloadGood))
|
||||
fmt.Printf(" - Payload '%s' is valid: %v\n", luhnPayloadBad, cryptService.Luhn(luhnPayloadBad))
|
||||
|
||||
// Fletcher
|
||||
fletcherPayload := "abcde"
|
||||
fmt.Printf("\nFletcher Checksums (Payload: \"%s\"):\n", fletcherPayload)
|
||||
fmt.Printf(" - Fletcher16: %d\n", cryptService.Fletcher16(fletcherPayload))
|
||||
fmt.Printf(" - Fletcher32: %d\n", cryptService.Fletcher32(fletcherPayload))
|
||||
fmt.Printf(" - Fletcher64: %d\n", cryptService.Fletcher64(fletcherPayload))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func demoRSA() {
|
||||
fmt.Println("--- RSA Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// 1. Generate RSA key pair
|
||||
fmt.Println("Generating 2048-bit RSA key pair...")
|
||||
publicKey, privateKey, err := cryptService.GenerateRSAKeyPair(2048)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate RSA key pair: %v", err)
|
||||
}
|
||||
fmt.Println("Key pair generated successfully.")
|
||||
|
||||
// 2. Encrypt a message
|
||||
message := []byte("This is a secret message for RSA.")
|
||||
fmt.Printf("\nOriginal message: %s\n", message)
|
||||
ciphertext, err := cryptService.EncryptRSA(publicKey, message, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to encrypt with RSA: %v", err)
|
||||
}
|
||||
fmt.Printf("Encrypted ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext))
|
||||
|
||||
// 3. Decrypt the message
|
||||
decrypted, err := cryptService.DecryptRSA(privateKey, ciphertext, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to decrypt with RSA: %v", err)
|
||||
}
|
||||
fmt.Printf("Decrypted message: %s\n", decrypted)
|
||||
|
||||
// 4. Verify
|
||||
if string(message) == string(decrypted) {
|
||||
fmt.Println("\nSuccess! RSA decrypted message matches the original.")
|
||||
} else {
|
||||
fmt.Println("\nFailure! RSA decrypted message does not match the original.")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func demoSigils() {
|
||||
fmt.Println("--- Standalone Sigil Demo ---")
|
||||
data := []byte(`{"message": "hello world"}`)
|
||||
fmt.Printf("Original data: %s\n", data)
|
||||
|
||||
// A chain of sigils to apply
|
||||
sigils := []string{"gzip", "base64"}
|
||||
fmt.Printf("Applying sigil chain: %v\n", sigils)
|
||||
|
||||
var transformedData = data
|
||||
for _, name := range sigils {
|
||||
s, err := enchantrix.NewSigil(name)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create sigil %s: %v", name, err)
|
||||
}
|
||||
transformedData, err = s.In(transformedData)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to apply sigil %s 'In': %v", name, err)
|
||||
}
|
||||
fmt.Printf(" -> After '%s': %s\n", name, transformedData)
|
||||
}
|
||||
|
||||
fmt.Println("\nReversing sigil chain...")
|
||||
// Reverse the transformations
|
||||
for i := len(sigils) - 1; i >= 0; i-- {
|
||||
name := sigils[i]
|
||||
s, err := enchantrix.NewSigil(name)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create sigil %s: %v", name, err)
|
||||
}
|
||||
transformedData, err = s.Out(transformedData)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to apply sigil %s 'Out': %v", name, err)
|
||||
}
|
||||
fmt.Printf(" -> After '%s' Out: %s\n", name, transformedData)
|
||||
}
|
||||
|
||||
if string(data) == string(transformedData) {
|
||||
fmt.Println("Success! Data returned to original state.")
|
||||
} else {
|
||||
fmt.Println("Failure! Data did not return to original state.")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
45
examples/pgp_encrypt_decrypt/main.go
Normal file
45
examples/pgp_encrypt_decrypt/main.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- PGP Encryption & Decryption Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// 1. Generate PGP key pair
|
||||
fmt.Println("Generating PGP key pair...")
|
||||
publicKey, privateKey, err := cryptService.GeneratePGPKeyPair("test", "test@example.com", "test key")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate PGP key pair: %v", err)
|
||||
}
|
||||
fmt.Println("Key pair generated successfully.")
|
||||
|
||||
// 2. Encrypt a message
|
||||
message := []byte("This is a secret message for PGP.")
|
||||
fmt.Printf("\nOriginal message: %s\n", message)
|
||||
ciphertext, err := cryptService.EncryptPGP(publicKey, message)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to encrypt with PGP: %v", err)
|
||||
}
|
||||
fmt.Printf("Encrypted ciphertext (armored):\n%s\n", ciphertext)
|
||||
|
||||
// 3. Decrypt the message
|
||||
decrypted, err := cryptService.DecryptPGP(privateKey, ciphertext)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to decrypt with PGP: %v", err)
|
||||
}
|
||||
fmt.Printf("Decrypted message: %s\n", decrypted)
|
||||
|
||||
// 4. Verify
|
||||
if string(message) == string(decrypted) {
|
||||
fmt.Println("\nSuccess! PGP decrypted message matches the original.")
|
||||
} else {
|
||||
fmt.Println("\nFailure! PGP decrypted message does not match the original.")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
23
examples/pgp_generate_keys/main.go
Normal file
23
examples/pgp_generate_keys/main.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- PGP Key Generation Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// 1. Generate PGP key pair
|
||||
fmt.Println("Generating PGP key pair...")
|
||||
publicKey, privateKey, err := cryptService.GeneratePGPKeyPair("test", "test@example.com", "test key")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate PGP key pair: %v", err)
|
||||
}
|
||||
fmt.Println("Key pair generated successfully.")
|
||||
fmt.Printf("\nPublic Key:\n%s\n", publicKey)
|
||||
fmt.Printf("\nPrivate Key:\n%s\n", privateKey)
|
||||
}
|
||||
38
examples/pgp_sign_verify/main.go
Normal file
38
examples/pgp_sign_verify/main.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- PGP Signing & Verification Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// 1. Generate PGP key pair
|
||||
fmt.Println("Generating PGP key pair...")
|
||||
publicKey, privateKey, err := cryptService.GeneratePGPKeyPair("test", "test@example.com", "test key")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate PGP key pair: %v", err)
|
||||
}
|
||||
fmt.Println("Key pair generated successfully.")
|
||||
|
||||
// 2. Sign a message
|
||||
message := []byte("This is a message to be signed.")
|
||||
fmt.Printf("\nOriginal message: %s\n", message)
|
||||
signature, err := cryptService.SignPGP(privateKey, message)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to sign with PGP: %v", err)
|
||||
}
|
||||
fmt.Printf("Signature (armored):\n%s\n", signature)
|
||||
|
||||
// 3. Verify the signature
|
||||
err = cryptService.VerifyPGP(publicKey, message, signature)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to verify signature: %v", err)
|
||||
}
|
||||
fmt.Println("Signature verified successfully!")
|
||||
fmt.Println()
|
||||
}
|
||||
24
examples/pgp_symmetric_encrypt/main.go
Normal file
24
examples/pgp_symmetric_encrypt/main.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- PGP Symmetric Encryption Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// 1. Encrypt a message with a passphrase
|
||||
message := []byte("This is a secret message for symmetric PGP encryption.")
|
||||
passphrase := []byte("my-secret-passphrase")
|
||||
fmt.Printf("\nOriginal message: %s\n", message)
|
||||
ciphertext, err := cryptService.SymmetricallyEncryptPGP(passphrase, message)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to encrypt with PGP: %v", err)
|
||||
}
|
||||
fmt.Printf("Encrypted ciphertext (armored):\n%s\n", ciphertext)
|
||||
fmt.Println()
|
||||
}
|
||||
46
examples/rsa/main.go
Normal file
46
examples/rsa/main.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- RSA Demo ---")
|
||||
cryptService := crypt.NewService()
|
||||
|
||||
// 1. Generate RSA key pair
|
||||
fmt.Println("Generating 2048-bit RSA key pair...")
|
||||
publicKey, privateKey, err := cryptService.GenerateRSAKeyPair(2048)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate RSA key pair: %v", err)
|
||||
}
|
||||
fmt.Println("Key pair generated successfully.")
|
||||
|
||||
// 2. Encrypt a message
|
||||
message := []byte("This is a secret message for RSA.")
|
||||
fmt.Printf("\nOriginal message: %s\n", message)
|
||||
ciphertext, err := cryptService.EncryptRSA(publicKey, message, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to encrypt with RSA: %v", err)
|
||||
}
|
||||
fmt.Printf("Encrypted ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext))
|
||||
|
||||
// 3. Decrypt the message
|
||||
decrypted, err := cryptService.DecryptRSA(privateKey, ciphertext, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to decrypt with RSA: %v", err)
|
||||
}
|
||||
fmt.Printf("Decrypted message: %s\n", decrypted)
|
||||
|
||||
// 4. Verify
|
||||
if string(message) == string(decrypted) {
|
||||
fmt.Println("\nSuccess! RSA decrypted message matches the original.")
|
||||
} else {
|
||||
fmt.Println("\nFailure! RSA decrypted message does not match the original.")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -9,6 +9,8 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -1,3 +1,7 @@
|
|||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
|
|||
|
|
@ -11,18 +11,21 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/Snider/Enchantrix/pkg/crypt/std/lthn"
|
||||
"github.com/Snider/Enchantrix/pkg/crypt/std/pgp"
|
||||
"github.com/Snider/Enchantrix/pkg/crypt/std/rsa"
|
||||
)
|
||||
|
||||
// Service is the main struct for the crypt service.
|
||||
type Service struct {
|
||||
rsa *rsa.Service
|
||||
pgp *pgp.Service
|
||||
}
|
||||
|
||||
// NewService creates a new crypt Service and initialises its embedded RSA service.
|
||||
// NewService creates a new crypt Service and initialises its embedded services.
|
||||
func NewService() *Service {
|
||||
return &Service{
|
||||
rsa: rsa.NewService(),
|
||||
pgp: pgp.NewService(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,3 +174,48 @@ func (s *Service) DecryptRSA(privateKey, ciphertext, label []byte) ([]byte, erro
|
|||
s.ensureRSA()
|
||||
return s.rsa.Decrypt(privateKey, ciphertext, label)
|
||||
}
|
||||
|
||||
// --- PGP ---
|
||||
|
||||
// ensurePGP initializes the PGP service if it is not already.
|
||||
func (s *Service) ensurePGP() {
|
||||
if s.pgp == nil {
|
||||
s.pgp = pgp.NewService()
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratePGPKeyPair creates a new PGP key pair.
|
||||
func (s *Service) GeneratePGPKeyPair(name, email, comment string) (publicKey, privateKey []byte, err error) {
|
||||
s.ensurePGP()
|
||||
return s.pgp.GenerateKeyPair(name, email, comment)
|
||||
}
|
||||
|
||||
// EncryptPGP encrypts data with a public key.
|
||||
func (s *Service) EncryptPGP(publicKey, data []byte) ([]byte, error) {
|
||||
s.ensurePGP()
|
||||
return s.pgp.Encrypt(publicKey, data)
|
||||
}
|
||||
|
||||
// DecryptPGP decrypts data with a private key.
|
||||
func (s *Service) DecryptPGP(privateKey, ciphertext []byte) ([]byte, error) {
|
||||
s.ensurePGP()
|
||||
return s.pgp.Decrypt(privateKey, ciphertext)
|
||||
}
|
||||
|
||||
// SignPGP creates a detached signature for a message.
|
||||
func (s *Service) SignPGP(privateKey, data []byte) ([]byte, error) {
|
||||
s.ensurePGP()
|
||||
return s.pgp.Sign(privateKey, data)
|
||||
}
|
||||
|
||||
// VerifyPGP verifies a detached signature for a message.
|
||||
func (s *Service) VerifyPGP(publicKey, data, signature []byte) error {
|
||||
s.ensurePGP()
|
||||
return s.pgp.Verify(publicKey, data, signature)
|
||||
}
|
||||
|
||||
// SymmetricallyEncryptPGP encrypts data with a passphrase.
|
||||
func (s *Service) SymmetricallyEncryptPGP(passphrase, data []byte) ([]byte, error) {
|
||||
s.ensurePGP()
|
||||
return s.pgp.SymmetricallyEncrypt(passphrase, data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,49 @@ func TestRSA_Good(t *testing.T) {
|
|||
assert.Equal(t, message, plaintext)
|
||||
}
|
||||
|
||||
// --- PGP Tests ---
|
||||
|
||||
func TestPGP_Good(t *testing.T) {
|
||||
pubKey, privKey, err := service.GeneratePGPKeyPair("test", "test@test.com", "test comment")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pubKey)
|
||||
assert.NotNil(t, privKey)
|
||||
|
||||
// Test encryption and decryption
|
||||
message := []byte("secret message")
|
||||
ciphertext, err := service.EncryptPGP(pubKey, message)
|
||||
assert.NoError(t, err)
|
||||
plaintext, err := service.DecryptPGP(privKey, ciphertext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, message, plaintext)
|
||||
|
||||
// Test signing and verification
|
||||
signature, err := service.SignPGP(privKey, message)
|
||||
assert.NoError(t, err)
|
||||
err = service.VerifyPGP(pubKey, message, signature)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test symmetric encryption
|
||||
passphrase := []byte("my-secret-passphrase")
|
||||
ciphertext, err = service.SymmetricallyEncryptPGP(passphrase, message)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ciphertext)
|
||||
}
|
||||
|
||||
// --- IsHashAlgo Tests ---
|
||||
|
||||
func TestIsHashAlgo_Good(t *testing.T) {
|
||||
assert.True(t, service.IsHashAlgo("lthn"))
|
||||
assert.True(t, service.IsHashAlgo("sha512"))
|
||||
assert.True(t, service.IsHashAlgo("sha256"))
|
||||
assert.True(t, service.IsHashAlgo("sha1"))
|
||||
assert.True(t, service.IsHashAlgo("md5"))
|
||||
}
|
||||
|
||||
func TestIsHashAlgo_Bad(t *testing.T) {
|
||||
assert.False(t, service.IsHashAlgo("not-a-hash"))
|
||||
}
|
||||
|
||||
func TestRSA_Bad(t *testing.T) {
|
||||
// Test with a key size that is too small
|
||||
_, _, err := service.GenerateRSAKeyPair(1024)
|
||||
|
|
|
|||
165
pkg/crypt/std/pgp/pgp.go
Normal file
165
pkg/crypt/std/pgp/pgp.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
// Sign creates a detached signature for a message.
|
||||
func (s *Service) Sign(privateKey, data []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)
|
||||
}
|
||||
|
||||
signer := keyring[0]
|
||||
if signer.PrivateKey == nil {
|
||||
return nil, fmt.Errorf("private key not found in keyring")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = openpgp.ArmoredDetachSign(buf, signer, bytes.NewReader(data), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign message: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Verify verifies a detached signature for a message.
|
||||
func (s *Service) Verify(publicKey, data, signature []byte) error {
|
||||
pubKeyReader := bytes.NewReader(publicKey)
|
||||
keyring, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read public key ring: %w", err)
|
||||
}
|
||||
|
||||
_, err = openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(data), bytes.NewReader(signature), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to verify signature: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SymmetricallyEncrypt encrypts data with a passphrase.
|
||||
func (s *Service) SymmetricallyEncrypt(passphrase, data []byte) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
w, err := openpgp.SymmetricallyEncrypt(buf, passphrase, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create symmetric encryption writer: %w", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
_, err = w.Write(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write data to symmetric encryption writer: %w", err)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
128
pkg/crypt/std/pgp/pgp_test.go
Normal file
128
pkg/crypt/std/pgp/pgp_test.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
|
||||
package pgp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestService_GenerateKeyPair_Good(t *testing.T) {
|
||||
s := NewService()
|
||||
pub, priv, err := s.GenerateKeyPair("test", "test@test.com", "test")
|
||||
require.NoError(t, err, "failed to generate key pair")
|
||||
assert.NotNil(t, pub, "public key is nil")
|
||||
assert.NotNil(t, priv, "private key is nil")
|
||||
}
|
||||
|
||||
func TestService_Encrypt_Good(t *testing.T) {
|
||||
s := NewService()
|
||||
pub, _, err := s.GenerateKeyPair("test", "test@test.com", "test")
|
||||
require.NoError(t, err, "failed to generate key pair")
|
||||
assert.NotNil(t, pub, "public key is nil")
|
||||
|
||||
data := []byte("hello world")
|
||||
encrypted, err := s.Encrypt(pub, data)
|
||||
require.NoError(t, err, "failed to encrypt data")
|
||||
assert.NotNil(t, encrypted, "encrypted data is nil")
|
||||
}
|
||||
|
||||
func TestService_Encrypt_Bad(t *testing.T) {
|
||||
s := NewService()
|
||||
_, err := s.Encrypt([]byte("bad key"), []byte("hello world"))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestService_Decrypt_Good(t *testing.T) {
|
||||
s := NewService()
|
||||
pub, priv, err := s.GenerateKeyPair("test", "test@test.com", "test")
|
||||
require.NoError(t, err, "failed to generate key pair")
|
||||
assert.NotNil(t, pub, "public key is nil")
|
||||
assert.NotNil(t, priv, "private key is nil")
|
||||
|
||||
data := []byte("hello world")
|
||||
encrypted, err := s.Encrypt(pub, data)
|
||||
require.NoError(t, err, "failed to encrypt data")
|
||||
assert.NotNil(t, encrypted, "encrypted data is nil")
|
||||
|
||||
decrypted, err := s.Decrypt(priv, encrypted)
|
||||
require.NoError(t, err, "failed to decrypt data")
|
||||
assert.Equal(t, data, decrypted, "decrypted data does not match original")
|
||||
}
|
||||
|
||||
func TestService_Decrypt_Bad(t *testing.T) {
|
||||
s := NewService()
|
||||
_, err := s.Decrypt([]byte("bad key"), []byte("hello world"))
|
||||
assert.Error(t, err)
|
||||
|
||||
pub, _, err := s.GenerateKeyPair("test", "test@test.com", "test")
|
||||
require.NoError(t, err)
|
||||
_, priv2, err := s.GenerateKeyPair("test2", "test2@test.com", "test2")
|
||||
require.NoError(t, err)
|
||||
encrypted, err := s.Encrypt(pub, []byte("hello world"))
|
||||
require.NoError(t, err)
|
||||
_, err = s.Decrypt(priv2, encrypted)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = s.Decrypt(priv2, []byte("bad encrypted data"))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestService_Sign_Good(t *testing.T) {
|
||||
s := NewService()
|
||||
_, priv, err := s.GenerateKeyPair("test", "test@test.com", "test")
|
||||
require.NoError(t, err, "failed to generate key pair")
|
||||
assert.NotNil(t, priv, "private key is nil")
|
||||
|
||||
data := []byte("hello world")
|
||||
signature, err := s.Sign(priv, data)
|
||||
require.NoError(t, err, "failed to sign data")
|
||||
assert.NotNil(t, signature, "signature is nil")
|
||||
}
|
||||
|
||||
func TestService_Sign_Bad(t *testing.T) {
|
||||
s := NewService()
|
||||
_, err := s.Sign([]byte("bad key"), []byte("hello world"))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestService_Verify_Good(t *testing.T) {
|
||||
s := NewService()
|
||||
pub, priv, err := s.GenerateKeyPair("test", "test@test.com", "test")
|
||||
require.NoError(t, err, "failed to generate key pair")
|
||||
assert.NotNil(t, pub, "public key is nil")
|
||||
assert.NotNil(t, priv, "private key is nil")
|
||||
|
||||
data := []byte("hello world")
|
||||
signature, err := s.Sign(priv, data)
|
||||
require.NoError(t, err, "failed to sign data")
|
||||
assert.NotNil(t, signature, "signature is nil")
|
||||
|
||||
err = s.Verify(pub, data, signature)
|
||||
require.NoError(t, err, "failed to verify signature")
|
||||
}
|
||||
|
||||
func TestService_Verify_Bad(t *testing.T) {
|
||||
s := NewService()
|
||||
err := s.Verify([]byte("bad key"), []byte("hello world"), []byte("bad signature"))
|
||||
assert.Error(t, err)
|
||||
|
||||
_, priv, err := s.GenerateKeyPair("test", "test@test.com", "test")
|
||||
require.NoError(t, err)
|
||||
pub2, _, err := s.GenerateKeyPair("test2", "test2@test.com", "test2")
|
||||
require.NoError(t, err)
|
||||
signature, err := s.Sign(priv, []byte("hello world"))
|
||||
require.NoError(t, err)
|
||||
err = s.Verify(pub2, []byte("hello world"), signature)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestService_SymmetricallyEncrypt_Good(t *testing.T) {
|
||||
s := NewService()
|
||||
passphrase := []byte("hello world")
|
||||
data := []byte("hello world")
|
||||
encrypted, err := s.SymmetricallyEncrypt(passphrase, data)
|
||||
require.NoError(t, err, "failed to encrypt data")
|
||||
assert.NotNil(t, encrypted, "encrypted data is nil")
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue