2025-10-26 00:02:40 +01:00
|
|
|
package openpgp
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
2025-10-28 21:42:29 +00:00
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
2025-10-26 00:02:40 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// generateTestKeys creates a new PGP entity and saves the public and private keys to temporary files.
|
|
|
|
|
func generateTestKeys(t *testing.T, name, passphrase string) (string, string, func()) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
tempDir, err := os.MkdirTemp("", "pgp-keys-*")
|
|
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("test setup: failed to create temp dir for keys: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-28 21:42:29 +00:00
|
|
|
config := &packet.Config{
|
|
|
|
|
RSABits: 2048, // Use a reasonable key size for tests
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-28 21:42:29 +00:00
|
|
|
entity, err := openpgp.NewEntity(name, "", name, config)
|
|
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("test setup: failed to create new PGP entity: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Save Public Key ---
|
|
|
|
|
pubKeyPath := filepath.Join(tempDir, name+".pub")
|
|
|
|
|
pubKeyFile, err := os.Create(pubKeyPath)
|
|
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("test setup: failed to create public key file: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
2025-10-28 21:42:29 +00:00
|
|
|
pubKeyWriter, err := armor.Encode(pubKeyFile, openpgp.PublicKeyType, nil)
|
2025-10-26 00:02:40 +01:00
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("test setup: failed to create armored writer for public key: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
2025-10-28 21:42:29 +00:00
|
|
|
if err := entity.Serialize(pubKeyWriter); err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("test setup: failed to serialize public key: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := pubKeyWriter.Close(); err != nil {
|
|
|
|
|
t.Fatalf("test setup: failed to close public key writer: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := pubKeyFile.Close(); err != nil {
|
|
|
|
|
t.Fatalf("test setup: failed to close public key file: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 14:18:37 +00:00
|
|
|
// --- Save Private Key (unencrypted for test setup) ---
|
2025-10-26 00:02:40 +01:00
|
|
|
privKeyPath := filepath.Join(tempDir, name+".asc")
|
|
|
|
|
privKeyFile, err := os.Create(privKeyPath)
|
|
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("test setup: failed to create private key file: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
2025-10-28 21:42:29 +00:00
|
|
|
privKeyWriter, err := armor.Encode(privKeyFile, openpgp.PrivateKeyType, nil)
|
2025-10-26 00:02:40 +01:00
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("test setup: failed to create armored writer for private key: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
2025-10-28 21:42:29 +00:00
|
|
|
|
2025-10-30 14:18:37 +00:00
|
|
|
// Serialize the whole entity with an unencrypted private key.
|
|
|
|
|
if err := entity.SerializePrivate(privKeyWriter, nil); err != nil {
|
|
|
|
|
t.Fatalf("test setup: failed to serialize private key: %v", err)
|
2025-10-28 21:42:29 +00:00
|
|
|
}
|
2025-10-30 14:18:37 +00:00
|
|
|
if err := privKeyWriter.Close(); err != nil {
|
|
|
|
|
t.Fatalf("test setup: failed to close private key writer: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := privKeyFile.Close(); err != nil {
|
|
|
|
|
t.Fatalf("test setup: failed to close private key file: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup := func() { os.RemoveAll(tempDir) }
|
|
|
|
|
return pubKeyPath, privKeyPath, cleanup
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEncryptDecryptPGP(t *testing.T) {
|
|
|
|
|
recipientPub, recipientPriv, cleanup := generateTestKeys(t, "recipient", "recipient-pass")
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
originalMessage := "This is a secret message."
|
|
|
|
|
|
|
|
|
|
// --- Test Encryption ---
|
|
|
|
|
var encryptedBuf bytes.Buffer
|
2025-10-30 14:18:37 +00:00
|
|
|
err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, nil, nil)
|
2025-10-26 00:02:40 +01:00
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("EncryptPGP() failed unexpectedly: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
2025-10-30 14:18:37 +00:00
|
|
|
encryptedMessage := encryptedBuf.String()
|
2025-10-26 00:02:40 +01:00
|
|
|
|
|
|
|
|
if !strings.Contains(encryptedMessage, "-----BEGIN PGP MESSAGE-----") {
|
|
|
|
|
t.Errorf("Encrypted message does not appear to be PGP armored")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Test Decryption ---
|
|
|
|
|
decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", nil)
|
|
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("DecryptPGP() failed unexpectedly: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if decryptedMessage != originalMessage {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Errorf("Decrypted message mismatch: got=%q, want=%q", decryptedMessage, originalMessage)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSignAndVerifyPGP(t *testing.T) {
|
|
|
|
|
recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass")
|
|
|
|
|
defer rCleanup()
|
|
|
|
|
|
|
|
|
|
signerPub, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass")
|
|
|
|
|
defer sCleanup()
|
|
|
|
|
|
|
|
|
|
originalMessage := "This is a signed and verified message."
|
|
|
|
|
|
|
|
|
|
// --- Encrypt and Sign ---
|
|
|
|
|
var encryptedBuf bytes.Buffer
|
|
|
|
|
signerPass := "signer-pass"
|
2025-10-30 14:18:37 +00:00
|
|
|
err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass)
|
2025-10-26 00:02:40 +01:00
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("EncryptPGP() with signing failed unexpectedly: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
2025-10-30 14:18:37 +00:00
|
|
|
encryptedMessage := encryptedBuf.String()
|
2025-10-26 00:02:40 +01:00
|
|
|
|
|
|
|
|
// --- Decrypt and Verify ---
|
|
|
|
|
decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &signerPub)
|
|
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("DecryptPGP() with verification failed unexpectedly: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if decryptedMessage != originalMessage {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Errorf("Decrypted message mismatch after signing: got=%q, want=%q", decryptedMessage, originalMessage)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestVerificationFailure(t *testing.T) {
|
|
|
|
|
recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass")
|
|
|
|
|
defer rCleanup()
|
|
|
|
|
|
|
|
|
|
_, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass")
|
|
|
|
|
defer sCleanup()
|
|
|
|
|
|
|
|
|
|
// Generate a third, unexpected key to test verification failure
|
|
|
|
|
unexpectedSignerPub, _, uCleanup := generateTestKeys(t, "unexpected", "unexpected-pass")
|
|
|
|
|
defer uCleanup()
|
|
|
|
|
|
|
|
|
|
originalMessage := "This message should fail verification."
|
|
|
|
|
|
|
|
|
|
// --- Encrypt and Sign with the actual signer key ---
|
|
|
|
|
var encryptedBuf bytes.Buffer
|
|
|
|
|
signerPass := "signer-pass"
|
2025-10-30 14:18:37 +00:00
|
|
|
err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass)
|
2025-10-26 00:02:40 +01:00
|
|
|
if err != nil {
|
2025-10-30 14:18:37 +00:00
|
|
|
t.Fatalf("EncryptPGP() with signing failed unexpectedly: %v", err)
|
2025-10-26 00:02:40 +01:00
|
|
|
}
|
2025-10-30 14:18:37 +00:00
|
|
|
encryptedMessage := encryptedBuf.String()
|
2025-10-26 00:02:40 +01:00
|
|
|
|
|
|
|
|
// --- Attempt to Decrypt and Verify with the WRONG public key ---
|
|
|
|
|
_, err = DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &unexpectedSignerPub)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("DecryptPGP() did not fail, but verification with an incorrect key was expected to fail.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !strings.Contains(err.Error(), "signature from unexpected key") {
|
|
|
|
|
t.Errorf("Expected error to contain 'signature from unexpected key', but got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|