smtp: upload attachments
This commit is contained in:
parent
0e1b866880
commit
72b56494bc
|
@ -135,8 +135,8 @@ func main() {
|
|||
|
||||
be := smtpbackend.New(sessions)
|
||||
s := smtp.NewServer(be)
|
||||
s.Addr = "127.0.0.1:"+port
|
||||
s.Domain = "localhost" // TODO: make this configurable
|
||||
s.Addr = "127.0.0.1:" + port
|
||||
s.Domain = "localhost" // TODO: make this configurable
|
||||
s.AllowInsecureAuth = true // TODO: remove this
|
||||
|
||||
log.Println("Starting SMTP server at", s.Addr)
|
||||
|
|
|
@ -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 {
|
||||
|
@ -25,7 +30,52 @@ type Attachment struct {
|
|||
ContentID string
|
||||
KeyPackets string // encrypted with the user's key, base64-encoded
|
||||
//Headers map[string]string
|
||||
Signature string
|
||||
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
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -214,56 +214,43 @@ const (
|
|||
type MessagePackage struct {
|
||||
Type MessagePackageType
|
||||
|
||||
BodyKeyPacket string
|
||||
BodyKeyPacket string
|
||||
AttachmentKeyPackets map[string]string
|
||||
Signature int
|
||||
Signature int
|
||||
|
||||
// Only if encrypted for outside
|
||||
PasswordHint string
|
||||
Auth interface{} // TODO
|
||||
Token string
|
||||
EncToken string
|
||||
Auth interface{} // TODO
|
||||
Token string
|
||||
EncToken string
|
||||
}
|
||||
|
||||
type MessagePackageSet struct {
|
||||
Type MessagePackageType // OR of each Type
|
||||
Type MessagePackageType // OR of each Type
|
||||
Addresses map[string]*MessagePackage
|
||||
MIMEType string
|
||||
Body string // Body data packet
|
||||
MIMEType string
|
||||
Body string // Body data packet
|
||||
|
||||
// Only if cleartext is sent
|
||||
BodyKey string
|
||||
BodyKey string
|
||||
AttachmentKeys map[string]string
|
||||
|
||||
bodyKey *packet.EncryptedKey
|
||||
bodyKey *packet.EncryptedKey
|
||||
attachmentKeys map[string]*packet.EncryptedKey
|
||||
}
|
||||
|
||||
func NewMessagePackageSet(attachmentKeys map[string]*packet.EncryptedKey) *MessagePackageSet {
|
||||
return &MessagePackageSet{
|
||||
Addresses: make(map[string]*MessagePackage),
|
||||
Addresses: make(map[string]*MessagePackage),
|
||||
attachmentKeys: attachmentKeys,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
cleartext io.WriteCloser
|
||||
ciphertext io.WriteCloser
|
||||
encoded *bytes.Buffer
|
||||
set *MessagePackageSet
|
||||
encoded *bytes.Buffer
|
||||
set *MessagePackageSet
|
||||
}
|
||||
|
||||
func (w *outgoingMessageWriter) Write(p []byte) (int, error) {
|
||||
|
@ -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)
|
||||
|
@ -307,10 +300,10 @@ func (set *MessagePackageSet) Encrypt(mimeType string) (io.WriteCloser, error) {
|
|||
}
|
||||
|
||||
return &outgoingMessageWriter{
|
||||
cleartext: literalData,
|
||||
cleartext: literalData,
|
||||
ciphertext: ciphertext,
|
||||
encoded: &encoded,
|
||||
set: set,
|
||||
encoded: &encoded,
|
||||
set: set,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -368,8 +361,8 @@ func (set *MessagePackageSet) AddInternal(addr string, pub *openpgp.Entity) erro
|
|||
|
||||
set.Type |= MessagePackageInternal
|
||||
set.Addresses[addr] = &MessagePackage{
|
||||
Type: MessagePackageInternal,
|
||||
BodyKeyPacket: bodyKey,
|
||||
Type: MessagePackageInternal,
|
||||
BodyKeyPacket: bodyKey,
|
||||
AttachmentKeyPackets: attachmentKeys,
|
||||
}
|
||||
return nil
|
||||
|
|
68
smtp/smtp.go
68
smtp/smtp.go
|
@ -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,15 +201,14 @@ 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}
|
||||
|
||||
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, ccList...)
|
||||
recipients = append(recipients, bccList...)
|
||||
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue