Added helpers with encrypted signatures and unarmored binary ciphertexts (#83)

* added signcryption for binary ciphertexts

* fixing merge issues

* removed newlines before error handling

Co-authored-by: marin thiercelin <marin.thiercelin@pm.me>
This commit is contained in:
marinthiercelin 2020-10-29 06:20:39 -07:00 committed by GitHub
parent 53a85837e0
commit 062cca9201
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 268 additions and 76 deletions

View file

@ -91,7 +91,6 @@ func DecryptMessageArmored(
privateKey string, passphrase []byte, ciphertext string, privateKey string, passphrase []byte, ciphertext string,
) (string, error) { ) (string, error) {
message, err := decryptMessageArmored(privateKey, passphrase, ciphertext) message, err := decryptMessageArmored(privateKey, passphrase, ciphertext)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -175,7 +174,6 @@ func EncryptBinaryMessageArmored(key string, data []byte) (string, error) {
// and its passphrase. // and its passphrase.
func DecryptBinaryMessageArmored(privateKey string, passphrase []byte, ciphertext string) ([]byte, error) { func DecryptBinaryMessageArmored(privateKey string, passphrase []byte, ciphertext string) ([]byte, error) {
message, err := decryptMessageArmored(privateKey, passphrase, ciphertext) message, err := decryptMessageArmored(privateKey, passphrase, ciphertext)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -189,51 +187,22 @@ func DecryptBinaryMessageArmored(privateKey string, passphrase []byte, ciphertex
func encryptSignArmoredDetached( func encryptSignArmoredDetached(
publicKey, privateKey string, publicKey, privateKey string,
passphrase, plainData []byte, passphrase, plainData []byte,
) (ciphertext, encryptedSignature string, err error) { ) (ciphertextArmored, encryptedSignatureArmored string, err error) {
var message = crypto.NewPlainMessage(plainData) var message = crypto.NewPlainMessage(plainData)
// We generate the session key // We encrypt and signcrypt
sessionKey, err := crypto.GenerateSessionKey() ciphertext, encryptedSignatureArmored, err := encryptSignObjDetached(publicKey, privateKey, passphrase, message)
if err != nil {
return "", "", errors.Wrap(err, "gopenpgp: unable to generate session key")
}
// We encrypt the message with the session key
messageDataPacket, err := sessionKey.Encrypt(message)
if err != nil {
return "", "", errors.Wrap(err, "gopenpgp: unable to encrypt message")
}
// We sign the message
detachedSignature, err := signDetached(privateKey, passphrase, message)
if err != nil {
return "", "", errors.Wrap(err, "gopenpgp: unable to sign the message")
}
// We encrypt the signature with the session key
signaturePlaintext := crypto.NewPlainMessage(detachedSignature.GetBinary())
signatureDataPacket, err := sessionKey.Encrypt(signaturePlaintext)
if err != nil {
return "", "", errors.Wrap(err, "gopenpgp: unable to encrypt signature")
}
// We encrypt the session key
keyPacket, err := EncryptSessionKey(publicKey, sessionKey)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
// We join the key packets and datapackets and armor the message // We armor the ciphertext and signature
ciphertext, err = crypto.NewPGPSplitMessage(keyPacket, messageDataPacket).GetArmored() ciphertextArmored, err = ciphertext.GetArmored()
if err != nil { if err != nil {
return "", "", errors.Wrap(err, "gopenpgp: unable to armor message") return "", "", errors.Wrap(err, "gopenpgp: unable to armor the ciphertext")
}
encryptedSignature, err = crypto.NewPGPSplitMessage(keyPacket, signatureDataPacket).GetArmored()
if err != nil {
return "", "", errors.Wrap(err, "gopenpgp: unable to armor signature")
} }
return ciphertext, encryptedSignature, nil return ciphertextArmored, encryptedSignatureArmored, nil
} }
// DecryptVerifyArmoredDetached decrypts an armored pgp message // DecryptVerifyArmoredDetached decrypts an armored pgp message
@ -244,32 +213,66 @@ func encryptSignArmoredDetached(
func DecryptVerifyArmoredDetached( func DecryptVerifyArmoredDetached(
publicKey, privateKey string, publicKey, privateKey string,
passphrase []byte, passphrase []byte,
ciphertext string, ciphertextArmored string,
encryptedSignature string, encryptedSignatureArmored string,
) (plainData []byte, err error) { ) (plainData []byte, err error) {
var message *crypto.PlainMessage // Some type casting
ciphertext, err := crypto.NewPGPMessageFromArmored(ciphertextArmored)
// We decrypt the message if err != nil {
if message, err = decryptMessageArmored(privateKey, passphrase, ciphertext); err != nil { return nil, errors.Wrap(err, "gopenpgp: unable to unarmor ciphertext")
return nil, err
} }
// We decrypt the signature // We decrypt and verify the encrypted signature
signatureMessage, err := decryptMessageArmored(privateKey, passphrase, encryptedSignature) message, err := decryptVerifyObjDetached(publicKey, privateKey, passphrase, ciphertext, encryptedSignatureArmored)
if err != nil { if err != nil {
return nil, err return nil, err
} }
detachedSignature := crypto.NewPGPSignature(signatureMessage.GetBinary()) return message.GetBinary(), nil
}
// We verify the signature // encryptSignBinaryDetached takes a public key for encryption,
var check bool // a private key and its passphrase for signature, and the plaintext data
if check, err = verifyDetached(publicKey, message, detachedSignature); err != nil { // Returns encrypted binary data and a detached armored encrypted signature.
func encryptSignBinaryDetached(
publicKey, privateKey string,
passphrase, plainData []byte,
) (encryptedData []byte, encryptedSignatureArmored string, err error) {
// Some type casting
message := crypto.NewPlainMessage(plainData)
// We encrypt and signcrypt
ciphertext, encryptedSignatureArmored, err := encryptSignObjDetached(publicKey, privateKey, passphrase, message)
if err != nil {
return nil, "", err
}
// We get the encrypted data
encryptedData = ciphertext.GetBinary()
return encryptedData, encryptedSignatureArmored, nil
}
// DecryptVerifyBinaryDetached decrypts binary encrypted data
// and verify a detached armored encrypted signature
// given a publicKey, and a privateKey with its passphrase.
// Returns the plain data or an error on
// signature verification failure.
func DecryptVerifyBinaryDetached(
publicKey, privateKey string,
passphrase []byte,
encryptedData []byte,
encryptedSignatureArmored string,
) (plainData []byte, err error) {
// Some type casting
ciphertext := crypto.NewPGPMessage(encryptedData)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to parse ciphertext")
}
// We decrypt and verify the encrypted signature
message, err := decryptVerifyObjDetached(publicKey, privateKey, passphrase, ciphertext, encryptedSignatureArmored)
if err != nil {
return nil, err return nil, err
} }
if !check {
return nil, errors.New("gopenpgp: unable to verify message")
}
return message.GetBinary(), nil return message.GetBinary(), nil
} }
@ -311,7 +314,10 @@ func EncryptSessionKey(
return nil, err return nil, err
} }
encryptedSessionKey, err = publicKeyRing.EncryptSessionKey(sessionKey) encryptedSessionKey, err = publicKeyRing.EncryptSessionKey(sessionKey)
return if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt sessionKey")
}
return encryptedSessionKey, nil
} }
// DecryptSessionKey decrypts a session key // DecryptSessionKey decrypts a session key
@ -347,48 +353,61 @@ func DecryptSessionKey(
} }
func encryptMessageArmored(key string, message *crypto.PlainMessage) (string, error) { func encryptMessageArmored(key string, message *crypto.PlainMessage) (string, error) {
publicKeyRing, err := createPublicKeyRing(key) ciphertext, err := encryptMessage(key, message)
if err != nil { if err != nil {
return "", err return "", err
} }
pgpMessage, err := publicKeyRing.Encrypt(message, nil) ciphertextArmored, err := ciphertext.GetArmored()
if err != nil { if err != nil {
return "", errors.Wrap(err, "gopenpgp: unable to encrypt message") return "", errors.Wrap(err, "gopenpgp: unable to armor ciphertext")
} }
ciphertext, err := pgpMessage.GetArmored() return ciphertextArmored, nil
}
func decryptMessageArmored(privateKey string, passphrase []byte, ciphertextArmored string) (*crypto.PlainMessage, error) {
ciphertext, err := crypto.NewPGPMessageFromArmored(ciphertextArmored)
if err != nil { if err != nil {
return "", errors.Wrap(err, "gopenpgp: unable to armor message") return nil, errors.Wrap(err, "gopenpgp: unable to parse ciphertext")
}
return decryptMessage(privateKey, passphrase, ciphertext)
}
func encryptMessage(key string, message *crypto.PlainMessage) (*crypto.PGPMessage, error) {
publicKeyRing, err := createPublicKeyRing(key)
if err != nil {
return nil, err
}
ciphertext, err := publicKeyRing.Encrypt(message, nil)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt message")
} }
return ciphertext, nil return ciphertext, nil
} }
func decryptMessageArmored(privateKey string, passphrase []byte, ciphertext string) (*crypto.PlainMessage, error) { func decryptMessage(privateKey string, passphrase []byte, ciphertext *crypto.PGPMessage) (*crypto.PlainMessage, error) {
privateKeyObj, err := crypto.NewKeyFromArmored(privateKey) privateKeyObj, err := crypto.NewKeyFromArmored(privateKey)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to parse private key") return nil, errors.Wrap(err, "gopenpgp: unable to parse the private key")
} }
privateKeyUnlocked, err := privateKeyObj.Unlock(passphrase) privateKeyUnlocked, err := privateKeyObj.Unlock(passphrase)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to unlock private key") return nil, errors.Wrap(err, "gopenpgp: unable to unlock key")
} }
defer privateKeyUnlocked.ClearPrivateParams() defer privateKeyUnlocked.ClearPrivateParams()
privateKeyRing, err := crypto.NewKeyRing(privateKeyUnlocked) privateKeyRing, err := crypto.NewKeyRing(privateKeyUnlocked)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to create new keyring") return nil, errors.Wrap(err, "gopenpgp: unable to create the private key ring")
} }
pgpMessage, err := crypto.NewPGPMessageFromArmored(ciphertext) message, err := privateKeyRing.Decrypt(ciphertext, nil, 0)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to unarmor ciphertext")
}
message, err := privateKeyRing.Decrypt(pgpMessage, nil, 0)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt message") return nil, errors.Wrap(err, "gopenpgp: unable to decrypt message")
} }
@ -498,3 +517,84 @@ func createPublicKeyRing(publicKey string) (*crypto.KeyRing, error) {
return publicKeyRing, nil return publicKeyRing, nil
} }
func encryptSignObjDetached(
publicKey, privateKey string,
passphrase []byte,
message *crypto.PlainMessage,
) (ciphertext *crypto.PGPSplitMessage, encryptedSignatureArmored string, err error) {
// We generate the session key
sessionKey, err := crypto.GenerateSessionKey()
if err != nil {
return nil, "", errors.Wrap(err, "gopenpgp: unable to create new session key")
}
// We encrypt the message with the session key
messageDataPacket, err := sessionKey.Encrypt(message)
if err != nil {
return nil, "", errors.Wrap(err, "gopenpgp: unable to encrypt message")
}
// We sign the message
detachedSignature, err := signDetached(privateKey, passphrase, message)
if err != nil {
return nil, "", errors.Wrap(err, "gopenpgp: unable to sign message")
}
// We encrypt the signature with the session key
signaturePlaintext := crypto.NewPlainMessage(detachedSignature.GetBinary())
signatureDataPacket, err := sessionKey.Encrypt(signaturePlaintext)
if err != nil {
return nil, "", errors.Wrap(err, "gopenpgp: unable to encrypt detached signature")
}
// We encrypt the session key
keyPacket, err := EncryptSessionKey(publicKey, sessionKey)
if err != nil {
return nil, "", errors.Wrap(err, "gopenpgp: unable to encrypt the session key")
}
// We join the key packets and datapackets
ciphertext = crypto.NewPGPSplitMessage(keyPacket, messageDataPacket)
encryptedSignature := crypto.NewPGPSplitMessage(keyPacket, signatureDataPacket)
encryptedSignatureArmored, err = encryptedSignature.GetArmored()
if err != nil {
return nil, "", errors.Wrap(err, "gopenpgp: unable to armor encrypted signature")
}
return ciphertext, encryptedSignatureArmored, nil
}
func decryptVerifyObjDetached(
publicKey, privateKey string,
passphrase []byte,
ciphertext *crypto.PGPMessage,
encryptedSignatureArmored string,
) (message *crypto.PlainMessage, err error) {
// We decrypt the message
if message, err = decryptMessage(privateKey, passphrase, ciphertext); err != nil {
return nil, err
}
encryptedSignature, err := crypto.NewPGPMessageFromArmored(encryptedSignatureArmored)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to parse encrypted signature")
}
// We decrypt the signature
signatureMessage, err := decryptMessage(privateKey, passphrase, encryptedSignature)
if err != nil {
return nil, err
}
detachedSignature := crypto.NewPGPSignature(signatureMessage.GetBinary())
// We verify the signature
var check bool
if check, err = verifyDetached(publicKey, message, detachedSignature); err != nil {
return nil, err
}
if !check {
return nil, errors.New("gopenpgp: unable to verify message")
}
return message, nil
}

View file

@ -292,3 +292,64 @@ func TestEncryptDecryptSessionKey(t *testing.T) {
t.Error("Decrypted session key is not equal to the original session key") t.Error("Decrypted session key is not equal to the original session key")
} }
} }
func TestEncryptSignBinaryDetached(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)
}
encryptedData, armoredSignature, err := EncryptSignBinaryDetached(
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 := DecryptVerifyBinaryDetached(
publicKeyString,
privateKeyString,
testMailboxPassword,
encryptedData,
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 := EncryptSignBinaryDetached(
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 = DecryptVerifyBinaryDetached(
publicKeyString,
privateKeyString,
testMailboxPassword,
encryptedData,
modifiedSignature,
)
if err == nil {
t.Fatal("Expected an error while decrypting and verifying with a wrong signature")
}
}

View file

@ -84,10 +84,10 @@ func GetJsonSHA256Fingerprints(publicKey string) ([]byte, error) {
} }
type EncryptSignArmoredDetachedMobileResult struct { type EncryptSignArmoredDetachedMobileResult struct {
Ciphertext, EncryptedSignature string CiphertextArmored, EncryptedSignatureArmored string
} }
// EncryptSignArmoredDetachedMobile wraps the EncryptSignArmoredDetached method // EncryptSignArmoredDetachedMobile wraps the encryptSignArmoredDetached method
// to have only one return argument for mobile. // to have only one return argument for mobile.
func EncryptSignArmoredDetachedMobile( func EncryptSignArmoredDetachedMobile(
publicKey, privateKey string, publicKey, privateKey string,
@ -99,7 +99,28 @@ func EncryptSignArmoredDetachedMobile(
} }
return &EncryptSignArmoredDetachedMobileResult{ return &EncryptSignArmoredDetachedMobileResult{
Ciphertext: ciphertext, CiphertextArmored: ciphertext,
EncryptedSignature: encryptedSignature, EncryptedSignatureArmored: encryptedSignature,
}, nil
}
type EncryptSignBinaryDetachedMobileResult struct {
EncryptedData []byte
EncryptedSignatureArmored string
}
// EncryptSignBinaryDetachedMobile wraps the encryptSignBinaryDetached method
// to have only one return argument for mobile.
func EncryptSignBinaryDetachedMobile(
publicKey, privateKey string,
passphrase, plainData []byte,
) (wrappedTuple *EncryptSignBinaryDetachedMobileResult, err error) {
ciphertext, encryptedSignature, err := encryptSignBinaryDetached(publicKey, privateKey, passphrase, plainData)
if err != nil {
return nil, err
}
return &EncryptSignBinaryDetachedMobileResult{
EncryptedData: ciphertext,
EncryptedSignatureArmored: encryptedSignature,
}, nil }, nil
} }

View file

@ -55,6 +55,16 @@ func EncryptSignAttachment(
func EncryptSignArmoredDetached( func EncryptSignArmoredDetached(
publicKey, privateKey string, publicKey, privateKey string,
passphrase, plainData []byte, passphrase, plainData []byte,
) (ciphertext, encryptedSignature string, err error) { ) (ciphertextArmored, encryptedSignatureArmored string, err error) {
return encryptSignArmoredDetached(publicKey, privateKey, passphrase, plainData) return encryptSignArmoredDetached(publicKey, privateKey, passphrase, plainData)
} }
// EncryptSignBinaryDetached takes a public key for encryption,
// a private key and its passphrase for signature, and the plaintext data
// Returns encrypted binary data and a detached armored encrypted signature.
func EncryptSignBinaryDetached(
publicKey, privateKey string,
passphrase, plainData []byte,
) (encryptedData []byte, encryptedSignatureArmored string, err error) {
return encryptSignBinaryDetached(publicKey, privateKey, passphrase, plainData)
}