mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 23:56:56 +00:00
refactor smtp sending
* [x] move package creation logic to `pmapi.SendMessageReq`
* [ ] write test of package creation logic
* [x] internal
* [x] plain
* [x] external encrypted
* [ ] signature ???
* [x] attachments
This commit is contained in:
@ -187,7 +187,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
||||
log.WithError(err).Error("Failed to parse message")
|
||||
return
|
||||
}
|
||||
clearBody := message.Body
|
||||
richBody := message.Body
|
||||
|
||||
externalID := message.Header.Get("Message-Id")
|
||||
externalID = strings.Trim(externalID, "<>")
|
||||
@ -256,7 +256,6 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
||||
atts = append(atts, message.Attachments...)
|
||||
// Decrypt attachment keys, because we will need to re-encrypt them with the recipients' public keys.
|
||||
attkeys := make(map[string]*crypto.SessionKey)
|
||||
attkeysEncoded := make(map[string]pmapi.AlgoKey)
|
||||
|
||||
for _, att := range atts {
|
||||
var keyPackets []byte
|
||||
@ -266,23 +265,9 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
||||
if attkeys[att.ID], err = kr.DecryptSessionKey(keyPackets); err != nil {
|
||||
return errors.Wrap(err, "decrypting attachment session key")
|
||||
}
|
||||
attkeysEncoded[att.ID] = pmapi.AlgoKey{
|
||||
Key: attkeys[att.ID].GetBase64Key(),
|
||||
Algorithm: attkeys[att.ID].Algo,
|
||||
}
|
||||
}
|
||||
|
||||
plainSharedScheme := 0
|
||||
htmlSharedScheme := 0
|
||||
mimeSharedType := 0
|
||||
|
||||
plainAddressMap := make(map[string]*pmapi.MessageAddress)
|
||||
htmlAddressMap := make(map[string]*pmapi.MessageAddress)
|
||||
mimeAddressMap := make(map[string]*pmapi.MessageAddress)
|
||||
|
||||
var plainKey, htmlKey, mimeKey *crypto.SessionKey
|
||||
var plainData, htmlData, mimeData []byte
|
||||
|
||||
req := pmapi.NewSendMessageReq(kr, mimeBody, plainBody, richBody, attkeys)
|
||||
containsUnencryptedRecipients := false
|
||||
|
||||
for _, email := range to {
|
||||
@ -300,59 +285,13 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
||||
|
||||
var signature int
|
||||
if sendPreferences.Sign {
|
||||
signature = pmapi.YesSignature
|
||||
signature = pmapi.SignatureDetached
|
||||
} else {
|
||||
signature = pmapi.NoSignature
|
||||
signature = pmapi.SignatureNone
|
||||
}
|
||||
if sendPreferences.Scheme == pmapi.PGPMIMEPackage || sendPreferences.Scheme == pmapi.ClearMIMEPackage {
|
||||
if mimeKey == nil {
|
||||
if mimeKey, mimeData, err = encryptSymmetric(kr, mimeBody, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if sendPreferences.Scheme == pmapi.PGPMIMEPackage {
|
||||
mimeBodyPacket, _, err := createPackets(sendPreferences.PublicKey, mimeKey, map[string]*crypto.SessionKey{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mimeAddressMap[email] = &pmapi.MessageAddress{Type: sendPreferences.Scheme, BodyKeyPacket: mimeBodyPacket, Signature: signature}
|
||||
} else {
|
||||
mimeAddressMap[email] = &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
|
||||
}
|
||||
mimeSharedType |= sendPreferences.Scheme
|
||||
} else {
|
||||
switch sendPreferences.MIMEType {
|
||||
case pmapi.ContentTypePlainText:
|
||||
if plainKey == nil {
|
||||
if plainKey, plainData, err = encryptSymmetric(kr, plainBody, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newAddress := &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
|
||||
if sendPreferences.Encrypt && sendPreferences.PublicKey != nil {
|
||||
newAddress.BodyKeyPacket, newAddress.AttachmentKeyPackets, err = createPackets(sendPreferences.PublicKey, plainKey, attkeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
plainAddressMap[email] = newAddress
|
||||
plainSharedScheme |= sendPreferences.Scheme
|
||||
case pmapi.ContentTypeHTML:
|
||||
if htmlKey == nil {
|
||||
if htmlKey, htmlData, err = encryptSymmetric(kr, clearBody, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newAddress := &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
|
||||
if sendPreferences.Encrypt && sendPreferences.PublicKey != nil {
|
||||
newAddress.BodyKeyPacket, newAddress.AttachmentKeyPackets, err = createPackets(sendPreferences.PublicKey, htmlKey, attkeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
htmlAddressMap[email] = newAddress
|
||||
htmlSharedScheme |= sendPreferences.Scheme
|
||||
}
|
||||
|
||||
if err := req.AddRecipient(email, sendPreferences.Scheme, sendPreferences.PublicKey, signature, sendPreferences.MIMEType, sendPreferences.Encrypt); err != nil {
|
||||
return errors.Wrap(err, "failed to add recipient")
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,31 +309,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
|
||||
}
|
||||
}
|
||||
|
||||
req := &pmapi.SendMessageReq{}
|
||||
|
||||
plainPkg := buildPackage(plainAddressMap, plainSharedScheme, pmapi.ContentTypePlainText, plainData, plainKey, attkeysEncoded)
|
||||
if plainPkg != nil {
|
||||
req.Packages = append(req.Packages, plainPkg)
|
||||
}
|
||||
|
||||
htmlPkg := buildPackage(htmlAddressMap, htmlSharedScheme, pmapi.ContentTypeHTML, htmlData, htmlKey, attkeysEncoded)
|
||||
if htmlPkg != nil {
|
||||
req.Packages = append(req.Packages, htmlPkg)
|
||||
}
|
||||
|
||||
if len(mimeAddressMap) > 0 {
|
||||
pkg := &pmapi.MessagePackage{
|
||||
Body: base64.StdEncoding.EncodeToString(mimeData),
|
||||
Addresses: mimeAddressMap,
|
||||
MIMEType: pmapi.ContentTypeMultipartMixed,
|
||||
Type: mimeSharedType,
|
||||
BodyKey: pmapi.AlgoKey{
|
||||
Key: mimeKey.GetBase64Key(),
|
||||
Algorithm: mimeKey.Algo,
|
||||
},
|
||||
}
|
||||
req.Packages = append(req.Packages, pkg)
|
||||
}
|
||||
req.PreparePackages()
|
||||
|
||||
return su.storeUser.SendMessage(message.ID, req)
|
||||
}
|
||||
|
||||
@ -18,11 +18,7 @@
|
||||
package smtp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"regexp"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals // Used like a constant
|
||||
@ -35,85 +31,3 @@ var mailFormat = regexp.MustCompile(`.+@.+\..+`)
|
||||
func looksLikeEmail(e string) bool {
|
||||
return mailFormat.MatchString(e)
|
||||
}
|
||||
|
||||
func createPackets(
|
||||
pubkey *crypto.KeyRing,
|
||||
bodyKey *crypto.SessionKey,
|
||||
attkeys map[string]*crypto.SessionKey,
|
||||
) (bodyPacket string, attachmentPackets map[string]string, err error) {
|
||||
// Encrypt message body keys.
|
||||
packetBytes, err := pubkey.EncryptSessionKey(bodyKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bodyPacket = base64.StdEncoding.EncodeToString(packetBytes)
|
||||
|
||||
// Encrypt attachment keys.
|
||||
attachmentPackets = make(map[string]string)
|
||||
for id, attkey := range attkeys {
|
||||
var packets []byte
|
||||
if packets, err = pubkey.EncryptSessionKey(attkey); err != nil {
|
||||
return
|
||||
}
|
||||
attachmentPackets[id] = base64.StdEncoding.EncodeToString(packets)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func encryptSymmetric(
|
||||
kr *crypto.KeyRing,
|
||||
textToEncrypt string,
|
||||
canonicalizeText bool, // nolint[unparam]
|
||||
) (key *crypto.SessionKey, symEncryptedData []byte, err error) {
|
||||
// We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
|
||||
firstKey, err := kr.FirstKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pgpMessage, err := firstKey.Encrypt(crypto.NewPlainMessageFromString(textToEncrypt), kr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pgpSplitMessage, err := pgpMessage.SeparateKeyAndData(len(textToEncrypt), 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
key, err = kr.DecryptSessionKey(pgpSplitMessage.GetBinaryKeyPacket())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
symEncryptedData = pgpSplitMessage.GetBinaryDataPacket()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func buildPackage(
|
||||
addressMap map[string]*pmapi.MessageAddress,
|
||||
sharedScheme int,
|
||||
mimeType string,
|
||||
bodyData []byte,
|
||||
bodyKey *crypto.SessionKey,
|
||||
attKeys map[string]pmapi.AlgoKey,
|
||||
) (pkg *pmapi.MessagePackage) {
|
||||
if len(addressMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pkg = &pmapi.MessagePackage{
|
||||
Body: base64.StdEncoding.EncodeToString(bodyData),
|
||||
Addresses: addressMap,
|
||||
MIMEType: mimeType,
|
||||
Type: sharedScheme,
|
||||
}
|
||||
|
||||
if sharedScheme|pmapi.ClearPackage > 0 {
|
||||
pkg.BodyKey.Key = bodyKey.GetBase64Key()
|
||||
pkg.BodyKey.Algorithm = bodyKey.Algo
|
||||
pkg.AttachmentKeys = attKeys
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user