smtp: full attachments support

This commit is contained in:
emersion 2017-12-03 12:27:31 +01:00
parent 72b56494bc
commit 90d494b130
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
2 changed files with 58 additions and 27 deletions

View File

@ -31,13 +31,15 @@ type Attachment struct {
KeyPackets string // encrypted with the user's key, base64-encoded
//Headers map[string]string
Signature string
unencryptedKey *packet.EncryptedKey
}
// Encrypt generates an encrypted key for the provided recipients and encrypts
// to w the data that will be written to the returned io.WriteCloser.
// GenerateKey generates an encrypted key and encrypts it to the provided
// recipients. Usually, the recipient is the user himself.
//
// signed is ignored for now.
func (att *Attachment) Encrypt(ciphertext io.Writer, to []*openpgp.Entity, signed *openpgp.Entity) (cleartext io.WriteCloser, err error) {
// The returned key is NOT encrypted.
func (att *Attachment) GenerateKey(to []*openpgp.Entity) (*packet.EncryptedKey, error) {
config := &packet.Config{}
var encodedKeyPackets bytes.Buffer
@ -61,9 +63,26 @@ func (att *Attachment) Encrypt(ciphertext io.Writer, to []*openpgp.Entity, signe
}
keyPackets.Close()
att.unencryptedKey = unencryptedKey
att.KeyPackets = encodedKeyPackets.String()
return unencryptedKey, nil
}
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, unencryptedKey.CipherFunc, unencryptedKey.Key, config)
// Encrypt encrypts to w the data that will be written to the returned
// io.WriteCloser.
//
// Prior to calling Encrypt, an attachment key must have been generated with
// GenerateKey.
//
// signed is ignored for now.
func (att *Attachment) Encrypt(ciphertext io.Writer, signed *openpgp.Entity) (cleartext io.WriteCloser, err error) {
config := &packet.Config{}
if att.unencryptedKey == nil {
return nil, errors.New("cannot encrypt attachment: no attachment key available")
}
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, att.unencryptedKey.CipherFunc, att.unencryptedKey.Key, config)
if err != nil {
return nil, err
}
@ -148,10 +167,7 @@ func (c *Client) CreateAttachment(att *Attachment, r io.Reader) (created *Attach
// TODO: Signature
if err := mw.Close(); err != nil {
pw.CloseWithError(err)
}
pw.Close()
pw.CloseWithError(mw.Close())
}()
req, err := c.newRequest(http.MethodPost, "/attachments", pr)

View File

@ -9,6 +9,7 @@ import (
"github.com/emersion/go-message/mail"
"github.com/emersion/go-smtp"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
"github.com/emersion/hydroxide/auth"
"github.com/emersion/hydroxide/protonmail"
@ -42,6 +43,7 @@ type user struct {
}
func (u *user) Send(from string, to []string, r io.Reader) error {
// Parse the incoming MIME message header
mr, err := mail.CreateReader(r)
if err != nil {
return err
@ -116,9 +118,13 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
return fmt.Errorf("cannot create draft message: %v", err)
}
// Parse the incoming MIME message body
// Save the message text into a buffer
// Upload attachments
var body *bytes.Buffer
var bodyType string
var attachments []*protonmail.Attachment
attachmentKeys := make(map[string]*packet.EncryptedKey)
for {
p, err := mr.NextPart()
@ -163,24 +169,32 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
// TODO: Header
}
var b bytes.Buffer
cleartext, err := att.Encrypt(&b, []*openpgp.Entity{privateKey}, privateKey)
attKey, err := att.GenerateKey([]*openpgp.Entity{privateKey})
if err != nil {
return fmt.Errorf("cannot encrypt attachment: %v", err)
}
if _, err := io.Copy(cleartext, p.Body); err != nil {
return fmt.Errorf("cannot encrypt attachment: %v", err)
}
if err := cleartext.Close(); err != nil {
return fmt.Errorf("cannot encrypt attachment: %v", err)
return fmt.Errorf("cannot generate attachment key: %v", err)
}
att, err = u.c.CreateAttachment(att, &b)
pr, pw := io.Pipe()
go func() {
cleartext, err := att.Encrypt(pw, privateKey)
if err != nil {
pw.CloseWithError(err)
return
}
if _, err := io.Copy(cleartext, p.Body); err != nil {
pw.CloseWithError(err)
return
}
pw.CloseWithError(cleartext.Close())
}()
att, err = u.c.CreateAttachment(att, pr)
if err != nil {
return fmt.Errorf("cannot upload attachment: %v", err)
}
attachments = append(attachments, att)
attachmentKeys[att.ID] = attKey
}
}
@ -188,8 +202,8 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
return errors.New("message doesn't contain a body part")
}
// Encrypt the body and update the draft
msg.MIMEType = bodyType
plaintext, err = msg.Encrypt([]*openpgp.Entity{privateKey}, privateKey)
if err != nil {
return err
@ -206,7 +220,7 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
return fmt.Errorf("cannot update draft message: %v", err)
}
outgoing := &protonmail.OutgoingMessage{ID: msg.ID}
// Split internal recipients and plaintext recipients
recipients := make([]*mail.Address, 0, len(toList)+len(ccList)+len(bccList))
recipients = append(recipients, toList...)
@ -235,9 +249,11 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
encryptedRecipients[rcpt.Address] = pub
}
// Create and send the outgoing message
outgoing := &protonmail.OutgoingMessage{ID: msg.ID}
if len(plaintextRecipients) > 0 {
// TODO: attachments
plaintextSet := protonmail.NewMessagePackageSet(nil)
plaintextSet := protonmail.NewMessagePackageSet(attachmentKeys)
plaintext, err := plaintextSet.Encrypt(bodyType, privateKey)
if err != nil {
@ -261,8 +277,7 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
}
if len(encryptedRecipients) > 0 {
// TODO: attachments
encryptedSet := protonmail.NewMessagePackageSet(nil)
encryptedSet := protonmail.NewMessagePackageSet(attachmentKeys)
plaintext, err := encryptedSet.Encrypt(bodyType, privateKey)
if err != nil {