diff --git a/helper/helper.go b/helper/helper.go index d9e6b20..b189b26 100644 --- a/helper/helper.go +++ b/helper/helper.go @@ -168,49 +168,18 @@ func DecryptVerifyAttachment( passphrase, keyPacket, dataPacket []byte, armoredSignature string, ) (plainData []byte, err error) { - var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key - var publicKeyRing, privateKeyRing *crypto.KeyRing - var detachedSignature *crypto.PGPSignature - var message *crypto.PlainMessage - - var packets = crypto.NewPGPSplitMessage(keyPacket, dataPacket) - - if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil { - return nil, err - } - if publicKeyObj.IsPrivate() { - publicKeyObj, err = publicKeyObj.ToPublic() - if err != nil { - return nil, err - } - } - - if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil { + // We decrypt the attachment + message, err := decryptAttachment(privateKey, passphrase, keyPacket, dataPacket) + if err != nil { return nil, err } - if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil { + // We verify the signature + var check bool + if check, err = verifyDetachedArmored(publicKey, message, armoredSignature); err != nil { return nil, err } - - if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil { - return nil, err - } - defer unlockedKeyObj.ClearPrivateParams() - - if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil { - return nil, err - } - - if detachedSignature, err = crypto.NewPGPSignatureFromArmored(armoredSignature); err != nil { - return nil, err - } - - if message, err = privateKeyRing.DecryptAttachment(packets); err != nil { - return nil, err - } - - if publicKeyRing.VerifyDetached(message, detachedSignature, crypto.GetUnixTime()) != nil { + if !check { return nil, errors.New("gopenpgp: unable to verify attachment") } @@ -235,6 +204,58 @@ func DecryptBinaryMessageArmored(privateKey string, passphrase []byte, ciphertex return message.GetBinary(), nil } +// EncryptSignArmoredDetached takes a public key for encryption, +// a private key and its passphrase for signature, and the plaintext data +// Returns an armored ciphertext and a detached armored signature. +func EncryptSignArmoredDetached( + publicKey, privateKey string, + passphrase, plainData []byte, +) (ciphertext, signature string, err error) { + var message *crypto.PlainMessage = crypto.NewPlainMessage(plainData) + + // We encrypt the message + if ciphertext, err = encryptMessageArmored(publicKey, message); err != nil { + return "", "", err + } + + // We sign the message + if signature, err = signDetachedArmored(privateKey, passphrase, message); err != nil { + return "", "", err + } + + return ciphertext, signature, nil +} + +// DecryptVerifyArmoredDetached decrypts an armored pgp message +// and verify a detached armored signature +// given a publicKey, and a privateKey with its passphrase. +// Returns the plain data or an error on +// signature verification failure. +func DecryptVerifyArmoredDetached( + publicKey, privateKey string, + passphrase []byte, + ciphertext string, + armoredSignature string, +) (plainData []byte, err error) { + var message *crypto.PlainMessage + + // We decrypt the message + if message, err = decryptMessageArmored(privateKey, passphrase, ciphertext); err != nil { + return nil, err + } + + // We verify the signature + var check bool + if check, err = verifyDetachedArmored(publicKey, message, armoredSignature); err != nil { + return nil, err + } + if !check { + return nil, errors.New("gopenpgp: unable to verify message") + } + + return message.GetBinary(), nil +} + func encryptMessageArmored(key string, message *crypto.PlainMessage) (string, error) { publicKey, err := crypto.NewKeyFromArmored(key) if publicKey.IsPrivate() { @@ -304,3 +325,96 @@ func decryptMessageArmored(privateKey string, passphrase []byte, ciphertext stri return message, nil } + +func signDetachedArmored(privateKey string, passphrase []byte, message *crypto.PlainMessage) (signature string, err error) { + privateKeyObj, err := crypto.NewKeyFromArmored(privateKey) + + if err != nil { + return "", err + } + + privateKeyUnlocked, err := privateKeyObj.Unlock(passphrase) + + if err != nil { + return "", err + } + + defer privateKeyUnlocked.ClearPrivateParams() + + privateKeyRing, err := crypto.NewKeyRing(privateKeyUnlocked) + + if err != nil { + return "", err + } + + detachedSignature, err := privateKeyRing.SignDetached(message) + + if err != nil { + return "", err + } + + armoredSignature, err := detachedSignature.GetArmored() + + if err != nil { + return "", err + } + + return armoredSignature, nil +} + +func verifyDetachedArmored(publicKey string, message *crypto.PlainMessage, armoredSignature string) (check bool, err error) { + var publicKeyObj *crypto.Key + var publicKeyRing *crypto.KeyRing + var detachedSignature *crypto.PGPSignature + // We prepare the public key for signature verification + if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil { + return false, err + } + if publicKeyObj.IsPrivate() { + publicKeyObj, err = publicKeyObj.ToPublic() + if err != nil { + return false, err + } + } + if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil { + return false, err + } + + // We verify the signature + if detachedSignature, err = crypto.NewPGPSignatureFromArmored(armoredSignature); err != nil { + return false, err + } + if publicKeyRing.VerifyDetached(message, detachedSignature, crypto.GetUnixTime()) != nil { + return false, nil + } + return true, nil +} + +func decryptAttachment( + privateKey string, + passphrase, keyPacket, dataPacket []byte, +) (message *crypto.PlainMessage, err error) { + var privateKeyObj, unlockedKeyObj *crypto.Key + var privateKeyRing *crypto.KeyRing + + packets := crypto.NewPGPSplitMessage(keyPacket, dataPacket) + + // prepare the private key for decryption + if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil { + return nil, err + } + if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil { + return nil, err + } + defer unlockedKeyObj.ClearPrivateParams() + + if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil { + return nil, err + } + + if message, err = privateKeyRing.DecryptAttachment(packets); err != nil { + return nil, err + } + + return message, nil +} diff --git a/helper/helper_test.go b/helper/helper_test.go index fb163be..a4146fb 100644 --- a/helper/helper_test.go +++ b/helper/helper_test.go @@ -1,6 +1,7 @@ package helper import ( + "bytes" "testing" "github.com/ProtonMail/gopenpgp/v2/crypto" @@ -154,3 +155,64 @@ func TestArmoredBinaryMessageEncryption(t *testing.T) { assert.Exactly(t, plainData, decrypted) } + +func TestEncryptSignArmoredDetached(t *testing.T) { + plainData := []byte("Secret message") + privateKeyString := readTestFile("keyring_privateKey", false) + privateKey, err := crypto.NewKeyFromArmored(privateKeyString) + if err != nil { + t.Fatal("Error reading the test private key: ", err) + } + publicKeyString, err := privateKey.GetArmoredPublicKey() + if err != nil { + t.Fatal("Error reading the test public key: ", err) + } + armoredCiphertext, armoredSignature, err := EncryptSignArmoredDetached( + publicKeyString, + privateKeyString, + testMailboxPassword, // Password defined in base_test + plainData, + ) + if err != nil { + t.Fatal("Expected no error while encrypting and signing, got:", err) + } + + decrypted, err := DecryptVerifyArmoredDetached( + publicKeyString, + privateKeyString, + testMailboxPassword, + armoredCiphertext, + armoredSignature, + ) + + if err != nil { + t.Fatal("Expected no error while decrypting and verifying, got:", err) + } + + if !bytes.Equal(decrypted, plainData) { + t.Error("Decrypted is not equal to the plaintext") + } + + _, modifiedSignature, err := EncryptSignArmoredDetached( + publicKeyString, + privateKeyString, + testMailboxPassword, // Password defined in base_test + []byte("Different message"), + ) + + if err != nil { + t.Fatal("Expected no error while encrypting and signing, got:", err) + } + + _, err = DecryptVerifyArmoredDetached( + publicKeyString, + privateKeyString, + testMailboxPassword, + armoredCiphertext, + modifiedSignature, + ) + + if err == nil { + t.Fatal("Expected an error while decrypting and verifying with a wrong signature") + } +} diff --git a/helper/mobile.go b/helper/mobile.go index 93ecbeb..ab73335 100644 --- a/helper/mobile.go +++ b/helper/mobile.go @@ -79,3 +79,20 @@ func GetJsonSHA256Fingerprints(publicKey string) ([]byte, error) { return json.Marshal(key.GetSHA256Fingerprints()) } + +type EncryptSignArmoredDetachedMobileResult struct { + Ciphertext, Signature string +} + +//EncryptSignArmoredDetachedMobile wraps the EncryptSignArmoredDetached method +//to have only one return argument for mobile. +func EncryptSignArmoredDetachedMobile( + publicKey, privateKey string, + passphrase, plainData []byte, +) (wrappedTuple *EncryptSignArmoredDetachedMobileResult, err error) { + ciphertext, signature, err := EncryptSignArmoredDetached(publicKey, privateKey, passphrase, plainData) + if err != nil { + return nil, err + } + return &EncryptSignArmoredDetachedMobileResult{ciphertext, signature}, nil +}