smtp: full attachments support
This commit is contained in:
parent
72b56494bc
commit
90d494b130
|
@ -31,13 +31,15 @@ type Attachment struct {
|
||||||
KeyPackets string // encrypted with the user's key, base64-encoded
|
KeyPackets string // encrypted with the user's key, base64-encoded
|
||||||
//Headers map[string]string
|
//Headers map[string]string
|
||||||
Signature string
|
Signature string
|
||||||
|
|
||||||
|
unencryptedKey *packet.EncryptedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt generates an encrypted key for the provided recipients and encrypts
|
// GenerateKey generates an encrypted key and encrypts it to the provided
|
||||||
// to w the data that will be written to the returned io.WriteCloser.
|
// recipients. Usually, the recipient is the user himself.
|
||||||
//
|
//
|
||||||
// signed is ignored for now.
|
// The returned key is NOT encrypted.
|
||||||
func (att *Attachment) Encrypt(ciphertext io.Writer, to []*openpgp.Entity, signed *openpgp.Entity) (cleartext io.WriteCloser, err error) {
|
func (att *Attachment) GenerateKey(to []*openpgp.Entity) (*packet.EncryptedKey, error) {
|
||||||
config := &packet.Config{}
|
config := &packet.Config{}
|
||||||
|
|
||||||
var encodedKeyPackets bytes.Buffer
|
var encodedKeyPackets bytes.Buffer
|
||||||
|
@ -61,9 +63,26 @@ func (att *Attachment) Encrypt(ciphertext io.Writer, to []*openpgp.Entity, signe
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPackets.Close()
|
keyPackets.Close()
|
||||||
|
att.unencryptedKey = unencryptedKey
|
||||||
att.KeyPackets = encodedKeyPackets.String()
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -148,10 +167,7 @@ func (c *Client) CreateAttachment(att *Attachment, r io.Reader) (created *Attach
|
||||||
|
|
||||||
// TODO: Signature
|
// TODO: Signature
|
||||||
|
|
||||||
if err := mw.Close(); err != nil {
|
pw.CloseWithError(mw.Close())
|
||||||
pw.CloseWithError(err)
|
|
||||||
}
|
|
||||||
pw.Close()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
req, err := c.newRequest(http.MethodPost, "/attachments", pr)
|
req, err := c.newRequest(http.MethodPost, "/attachments", pr)
|
||||||
|
|
51
smtp/smtp.go
51
smtp/smtp.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/emersion/go-message/mail"
|
"github.com/emersion/go-message/mail"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"golang.org/x/crypto/openpgp"
|
"golang.org/x/crypto/openpgp"
|
||||||
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
|
|
||||||
"github.com/emersion/hydroxide/auth"
|
"github.com/emersion/hydroxide/auth"
|
||||||
"github.com/emersion/hydroxide/protonmail"
|
"github.com/emersion/hydroxide/protonmail"
|
||||||
|
@ -42,6 +43,7 @@ type user struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) Send(from string, to []string, r io.Reader) error {
|
func (u *user) Send(from string, to []string, r io.Reader) error {
|
||||||
|
// Parse the incoming MIME message header
|
||||||
mr, err := mail.CreateReader(r)
|
mr, err := mail.CreateReader(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
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 body *bytes.Buffer
|
||||||
var bodyType string
|
var bodyType string
|
||||||
var attachments []*protonmail.Attachment
|
attachmentKeys := make(map[string]*packet.EncryptedKey)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
p, err := mr.NextPart()
|
p, err := mr.NextPart()
|
||||||
|
@ -163,24 +169,32 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
|
||||||
// TODO: Header
|
// TODO: Header
|
||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
attKey, err := att.GenerateKey([]*openpgp.Entity{privateKey})
|
||||||
cleartext, err := att.Encrypt(&b, []*openpgp.Entity{privateKey}, privateKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot encrypt attachment: %v", err)
|
return fmt.Errorf("cannot generate attachment key: %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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot upload attachment: %v", err)
|
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")
|
return errors.New("message doesn't contain a body part")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encrypt the body and update the draft
|
||||||
msg.MIMEType = bodyType
|
msg.MIMEType = bodyType
|
||||||
|
|
||||||
plaintext, err = msg.Encrypt([]*openpgp.Entity{privateKey}, privateKey)
|
plaintext, err = msg.Encrypt([]*openpgp.Entity{privateKey}, privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
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 := make([]*mail.Address, 0, len(toList)+len(ccList)+len(bccList))
|
||||||
recipients = append(recipients, toList...)
|
recipients = append(recipients, toList...)
|
||||||
|
@ -235,9 +249,11 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
|
||||||
encryptedRecipients[rcpt.Address] = pub
|
encryptedRecipients[rcpt.Address] = pub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and send the outgoing message
|
||||||
|
outgoing := &protonmail.OutgoingMessage{ID: msg.ID}
|
||||||
|
|
||||||
if len(plaintextRecipients) > 0 {
|
if len(plaintextRecipients) > 0 {
|
||||||
// TODO: attachments
|
plaintextSet := protonmail.NewMessagePackageSet(attachmentKeys)
|
||||||
plaintextSet := protonmail.NewMessagePackageSet(nil)
|
|
||||||
|
|
||||||
plaintext, err := plaintextSet.Encrypt(bodyType, privateKey)
|
plaintext, err := plaintextSet.Encrypt(bodyType, privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -261,8 +277,7 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(encryptedRecipients) > 0 {
|
if len(encryptedRecipients) > 0 {
|
||||||
// TODO: attachments
|
encryptedSet := protonmail.NewMessagePackageSet(attachmentKeys)
|
||||||
encryptedSet := protonmail.NewMessagePackageSet(nil)
|
|
||||||
|
|
||||||
plaintext, err := encryptedSet.Encrypt(bodyType, privateKey)
|
plaintext, err := encryptedSet.Encrypt(bodyType, privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue