Files
mailgoserver/internal/pgp/pgp_test.go
T

249 lines
7.2 KiB
Go
Raw Normal View History

package pgp
import (
"bytes"
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
)
// serializeUnencryptedPrivate serializes an as-yet-unprotected private key — only
// safe to call before EncryptPrivateKeys (see the gotcha documented on
// serializePrivateWithoutSigning in identity.go). No production code path needs
// this (every stored key goes through EncryptPrivateKeys first); it exists here
// purely to simulate a genuinely unprotected "gpg --export-secret-keys" output for
// TestImportPrivateKeyUnencrypted.
func serializeUnencryptedPrivate(entity *openpgp.Entity) ([]byte, error) {
var buf bytes.Buffer
w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil)
if err != nil {
return nil, err
}
if err := entity.SerializePrivate(w, nil); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func testEntity() Entity {
return Entity{
Headers: []string{"Content-Type: text/plain; charset=utf-8"},
Body: []byte("hello world\r\nsecond line\r\n"),
}
}
func TestGenerateEncryptDecryptRoundTrip(t *testing.T) {
pubPEM, privPEM, err := GenerateKeyPair("alice@example.com", "correct horse battery staple")
if err != nil {
t.Fatalf("GenerateKeyPair: %v", err)
}
if bytes.Contains(privPEM, []byte("correct horse")) {
t.Fatal("stored private key armor should not contain the plaintext passphrase")
}
recipient, err := ParsePublicKey(pubPEM)
if err != nil {
t.Fatalf("ParsePublicKey: %v", err)
}
orig := testEntity()
encrypted, err := EncryptEntity(orig, []*openpgp.Entity{recipient})
if err != nil {
t.Fatalf("EncryptEntity: %v", err)
}
if ct := headerValue(encrypted.Headers, "Content-Type"); ct == "" {
t.Fatal("expected a Content-Type header on the encrypted entity")
}
if bytes.Contains(encrypted.Body, orig.Body) {
t.Fatal("encrypted body should not contain the plaintext")
}
identity, err := ParsePrivateKey(privPEM)
if err != nil {
t.Fatalf("ParsePrivateKey: %v", err)
}
if err := UnlockPrivateKey(identity, "correct horse battery staple"); err != nil {
t.Fatalf("UnlockPrivateKey: %v", err)
}
decrypted, err := DecryptEntity(encrypted, identity)
if err != nil {
t.Fatalf("DecryptEntity: %v", err)
}
if !bytes.Equal(decrypted.Body, orig.Body) {
t.Fatalf("body mismatch: got %q want %q", decrypted.Body, orig.Body)
}
if headerValue(decrypted.Headers, "Content-Type") != headerValue(orig.Headers, "Content-Type") {
t.Fatalf("header mismatch: got %v want %v", decrypted.Headers, orig.Headers)
}
}
func TestUnlockPrivateKeyWrongPassphraseFails(t *testing.T) {
_, privPEM, err := GenerateKeyPair("alice@example.com", "right passphrase")
if err != nil {
t.Fatal(err)
}
identity, err := ParsePrivateKey(privPEM)
if err != nil {
t.Fatal(err)
}
if err := UnlockPrivateKey(identity, "wrong passphrase"); err == nil {
t.Fatal("expected the wrong passphrase to fail")
}
}
func TestEncryptMultipleRecipientsBothCanDecrypt(t *testing.T) {
senderPub, senderPriv, err := GenerateKeyPair("sender@example.com", "sender pass")
if err != nil {
t.Fatal(err)
}
recipPub, recipPriv, err := GenerateKeyPair("recipient@example.com", "recipient pass")
if err != nil {
t.Fatal(err)
}
senderPubEntity, _ := ParsePublicKey(senderPub)
recipPubEntity, _ := ParsePublicKey(recipPub)
orig := testEntity()
encrypted, err := EncryptEntity(orig, []*openpgp.Entity{senderPubEntity, recipPubEntity})
if err != nil {
t.Fatal(err)
}
senderIdentity, _ := ParsePrivateKey(senderPriv)
if err := UnlockPrivateKey(senderIdentity, "sender pass"); err != nil {
t.Fatal(err)
}
senderCopy, err := DecryptEntity(encrypted, senderIdentity)
if err != nil {
t.Fatalf("sender DecryptEntity: %v", err)
}
if !bytes.Equal(senderCopy.Body, orig.Body) {
t.Fatal("sender's own copy did not decrypt to the original body")
}
recipIdentity, _ := ParsePrivateKey(recipPriv)
if err := UnlockPrivateKey(recipIdentity, "recipient pass"); err != nil {
t.Fatal(err)
}
recipCopy, err := DecryptEntity(encrypted, recipIdentity)
if err != nil {
t.Fatalf("recipient DecryptEntity: %v", err)
}
if !bytes.Equal(recipCopy.Body, orig.Body) {
t.Fatal("recipient's copy did not decrypt to the original body")
}
}
func TestImportPrivateKeyUnencrypted(t *testing.T) {
// Simulate a raw, not-yet-passphrase-protected export by generating a key and
// serializing it before EncryptPrivateKeys is ever called.
entity, err := openpgp.NewEntity("bob@example.com", "", "bob@example.com", defaultConfig())
if err != nil {
t.Fatal(err)
}
unprotectedArmor, err := serializeUnencryptedPrivate(entity)
if err != nil {
t.Fatal(err)
}
pubPEM, privPEM, err := ImportPrivateKey(unprotectedArmor, "new passphrase")
if err != nil {
t.Fatalf("ImportPrivateKey: %v", err)
}
identity, err := ParsePrivateKey(privPEM)
if err != nil {
t.Fatal(err)
}
if !identity.PrivateKey.Encrypted {
t.Fatal("expected the imported key to be encrypted after import")
}
if err := UnlockPrivateKey(identity, "new passphrase"); err != nil {
t.Fatalf("expected the new passphrase to unlock the imported key: %v", err)
}
recipient, err := ParsePublicKey(pubPEM)
if err != nil {
t.Fatal(err)
}
orig := testEntity()
encrypted, err := EncryptEntity(orig, []*openpgp.Entity{recipient})
if err != nil {
t.Fatal(err)
}
decrypted, err := DecryptEntity(encrypted, identity)
if err != nil {
t.Fatalf("DecryptEntity after import: %v", err)
}
if !bytes.Equal(decrypted.Body, orig.Body) {
t.Fatal("round trip through an imported unencrypted key failed")
}
}
func TestImportPrivateKeyAlreadyEncrypted(t *testing.T) {
_, existingArmor, err := GenerateKeyPair("carol@example.com", "original passphrase")
if err != nil {
t.Fatal(err)
}
if _, _, err := ImportPrivateKey(existingArmor, "wrong passphrase"); err == nil {
t.Fatal("expected import with the wrong passphrase for an already-encrypted key to fail")
}
pubPEM, privPEM, err := ImportPrivateKey(existingArmor, "original passphrase")
if err != nil {
t.Fatalf("ImportPrivateKey with the correct passphrase: %v", err)
}
identity, err := ParsePrivateKey(privPEM)
if err != nil {
t.Fatal(err)
}
if err := UnlockPrivateKey(identity, "original passphrase"); err != nil {
t.Fatalf("expected the original passphrase to still unlock after re-import: %v", err)
}
recipient, err := ParsePublicKey(pubPEM)
if err != nil {
t.Fatal(err)
}
orig := testEntity()
encrypted, err := EncryptEntity(orig, []*openpgp.Entity{recipient})
if err != nil {
t.Fatal(err)
}
decrypted, err := DecryptEntity(encrypted, identity)
if err != nil {
t.Fatalf("DecryptEntity after re-import: %v", err)
}
if !bytes.Equal(decrypted.Body, orig.Body) {
t.Fatal("round trip through a re-imported already-encrypted key failed")
}
}
func TestFingerprintIsStableAndNonEmpty(t *testing.T) {
pubPEM, _, err := GenerateKeyPair("alice@example.com", "pass")
if err != nil {
t.Fatal(err)
}
entity, err := ParsePublicKey(pubPEM)
if err != nil {
t.Fatal(err)
}
fp := Fingerprint(entity)
if len(fp) == 0 {
t.Fatal("expected a non-empty fingerprint")
}
entity2, err := ParsePublicKey(pubPEM)
if err != nil {
t.Fatal(err)
}
if Fingerprint(entity2) != fp {
t.Fatal("expected the fingerprint to be stable across re-parses of the same key")
}
}