smtp: upload attachments

This commit is contained in:
emersion 2017-12-03 11:55:59 +01:00
parent 0e1b866880
commit 72b56494bc
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
5 changed files with 159 additions and 51 deletions

View File

@ -1,13 +1,18 @@
package protonmail
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"net/http"
"io"
"mime"
"mime/multipart"
"net/http"
"strconv"
"strings"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
type AttachmentKey struct {
@ -28,6 +33,51 @@ type Attachment struct {
Signature string
}
// Encrypt generates an encrypted key for the provided recipients and encrypts
// to w the data that will be written to the returned io.WriteCloser.
//
// signed is ignored for now.
func (att *Attachment) Encrypt(ciphertext io.Writer, to []*openpgp.Entity, signed *openpgp.Entity) (cleartext io.WriteCloser, err error) {
config := &packet.Config{}
var encodedKeyPackets bytes.Buffer
keyPackets := base64.NewEncoder(base64.StdEncoding, &encodedKeyPackets)
unencryptedKey, err := generateUnencryptedKey(packet.CipherAES256, config)
if err != nil {
return nil, err
}
for _, pub := range to {
encKey, ok := encryptionKey(pub, config.Now())
if !ok {
return nil, errors.New("cannot encrypt an attachment to key id " + strconv.FormatUint(pub.PrimaryKey.KeyId, 16) + " because it has no encryption keys")
}
err := packet.SerializeEncryptedKey(keyPackets, encKey.PublicKey, unencryptedKey.CipherFunc, unencryptedKey.Key, config)
if err != nil {
return nil, err
}
}
keyPackets.Close()
att.KeyPackets = encodedKeyPackets.String()
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, unencryptedKey.CipherFunc, unencryptedKey.Key, config)
if err != nil {
return nil, err
}
// TODO: sign, see https://github.com/golang/crypto/blob/master/openpgp/write.go#L287
literalData, err := packet.SerializeLiteral(encryptedData, true, att.Name, 0)
if err != nil {
return nil, err
}
return literalData, nil
}
// GetAttachment downloads an attachment's payload. The returned io.ReadCloser
// may be encrypted.
func (c *Client) GetAttachment(id string) (io.ReadCloser, error) {
@ -88,7 +138,7 @@ func (c *Client) CreateAttachment(att *Attachment, r io.Reader) (created *Attach
}
}
if w, err := mw.CreateFormFile("DataPackets", "DataPackets.pgp"); err != nil {
if w, err := mw.CreateFormFile("DataPacket", "DataPacket.pgp"); err != nil {
pw.CloseWithError(err)
return
} else if _, err := io.Copy(w, r); err != nil {
@ -109,8 +159,7 @@ func (c *Client) CreateAttachment(att *Attachment, r io.Reader) (created *Attach
return nil, err
}
params := map[string]string{"boundary": mw.Boundary()}
req.Header.Set("Content-Type", mime.FormatMediaType("multipart/form-data", params))
req.Header.Set("Content-Type", mw.FormDataContentType())
var respData struct {
resp

View File

@ -1,9 +1,11 @@
package protonmail
import (
"io"
"time"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// primaryIdentity returns the Identity marked as primary or the first identity
@ -89,3 +91,15 @@ func signingKey(e *openpgp.Entity, now time.Time) (openpgp.Key, bool) {
return openpgp.Key{}, false
}
func generateUnencryptedKey(cipher packet.CipherFunction, config *packet.Config) (*packet.EncryptedKey, error) {
symKey := make([]byte, cipher.KeySize())
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return nil, err
}
return &packet.EncryptedKey{
CipherFunc: cipher,
Key: symKey,
}, nil
}

View File

@ -63,7 +63,7 @@ type Message struct {
SpamScore int
AddressID string
Body string
MIMEType string
MIMEType string `json:",omitempty"`
CCList []*MessageAddress
BCCList []*MessageAddress
Header string
@ -246,19 +246,6 @@ func NewMessagePackageSet(attachmentKeys map[string]*packet.EncryptedKey) *Messa
}
}
func (set *MessagePackageSet) generateBodyKey(cipher packet.CipherFunction, config *packet.Config) error {
symKey := make([]byte, cipher.KeySize())
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return err
}
set.bodyKey = &packet.EncryptedKey{
CipherFunc: cipher,
Key: symKey,
}
return nil
}
type outgoingMessageWriter struct {
cleartext io.WriteCloser
ciphertext io.WriteCloser
@ -282,14 +269,20 @@ func (w *outgoingMessageWriter) Close() error {
return nil
}
func (set *MessagePackageSet) Encrypt(mimeType string) (io.WriteCloser, error) {
// Encrypt encrypts the data that will be written to the returned
// io.WriteCloser.
//
// The signed parameter is ignored for now.
func (set *MessagePackageSet) Encrypt(mimeType string, signed *openpgp.Entity) (io.WriteCloser, error) {
set.MIMEType = mimeType
config := &packet.Config{}
if err := set.generateBodyKey(packet.CipherAES256, config); err != nil {
key, err := generateUnencryptedKey(packet.CipherAES256, config)
if err != nil {
return nil, err
}
set.bodyKey = key
var encoded bytes.Buffer
ciphertext := base64.NewEncoder(base64.StdEncoding, &encoded)

View File

@ -101,8 +101,24 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
AddressID: fromAddr.ID,
}
// Create an empty draft
plaintext, err := msg.Encrypt([]*openpgp.Entity{privateKey}, privateKey)
if err != nil {
return err
}
if err := plaintext.Close(); err != nil {
return err
}
// TODO: parentID from In-Reply-To
msg, err = u.c.CreateDraftMessage(msg, "")
if err != nil {
return fmt.Errorf("cannot create draft message: %v", err)
}
var body *bytes.Buffer
var bodyType string
var attachments []*protonmail.Attachment
for {
p, err := mr.NextPart()
@ -129,7 +145,42 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
return err
}
case mail.AttachmentHeader:
// TODO
t, _, err := h.ContentType()
if err != nil {
break
}
filename, err := h.Filename()
if err != nil {
break
}
att := &protonmail.Attachment{
MessageID: msg.ID,
Name: filename,
MIMEType: t,
ContentID: h.Get("Content-Id"),
// TODO: Header
}
var b bytes.Buffer
cleartext, err := att.Encrypt(&b, []*openpgp.Entity{privateKey}, 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)
}
att, err = u.c.CreateAttachment(att, &b)
if err != nil {
return fmt.Errorf("cannot upload attachment: %v", err)
}
attachments = append(attachments, att)
}
}
@ -139,7 +190,7 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
msg.MIMEType = bodyType
plaintext, err := msg.Encrypt([]*openpgp.Entity{privateKey}, privateKey)
plaintext, err = msg.Encrypt([]*openpgp.Entity{privateKey}, privateKey)
if err != nil {
return err
}
@ -150,10 +201,9 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
return err
}
// TODO: parentID from In-Reply-To
msg, err = u.c.CreateDraftMessage(msg, "")
msg, err = u.c.UpdateDraftMessage(msg)
if err != nil {
return fmt.Errorf("cannot create draft message: %v", err)
return fmt.Errorf("cannot update draft message: %v", err)
}
outgoing := &protonmail.OutgoingMessage{ID: msg.ID}
@ -186,9 +236,10 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
}
if len(plaintextRecipients) > 0 {
// TODO: attachments
plaintextSet := protonmail.NewMessagePackageSet(nil)
plaintext, err := plaintextSet.Encrypt(bodyType)
plaintext, err := plaintextSet.Encrypt(bodyType, privateKey)
if err != nil {
return err
}
@ -210,9 +261,10 @@ func (u *user) Send(from string, to []string, r io.Reader) error {
}
if len(encryptedRecipients) > 0 {
// TODO: attachments
encryptedSet := protonmail.NewMessagePackageSet(nil)
plaintext, err := encryptedSet.Encrypt(bodyType)
plaintext, err := encryptedSet.Encrypt(bodyType, privateKey)
if err != nil {
return err
}