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") } }