hydroxide-push/protonmail/attachments.go

174 lines
4.1 KiB
Go
Raw Normal View History

package protonmail
import (
2017-12-03 12:55:59 +02:00
"bytes"
"encoding/base64"
2017-12-03 12:55:59 +02:00
"errors"
"fmt"
"io"
"mime/multipart"
2017-12-03 12:55:59 +02:00
"net/http"
"strconv"
"strings"
2017-12-03 12:55:59 +02:00
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
type AttachmentKey struct {
ID string
Key string
Algo string
}
type Attachment struct {
2017-09-19 15:54:47 +03:00
ID string
MessageID string
2017-09-19 15:54:47 +03:00
Name string
Size int
MIMEType string
ContentID string
KeyPackets string // encrypted with the user's key, base64-encoded
//Headers map[string]string
2017-12-03 12:55:59 +02:00
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) {
req, err := c.newRequest(http.MethodGet, "/attachments/"+id, nil)
if err != nil {
return nil, err
}
resp, err := c.do(req)
if err != nil {
return nil, err
}
if resp.StatusCode/100 != 2 {
return nil, fmt.Errorf("cannot get attachment %q: %v %v", id, resp.Status, resp.StatusCode)
}
return resp.Body, nil
}
// CreateAttachment uploads a new attachment. r must be an PGP data packet
// encrypted with att.KeyPackets.
func (c *Client) CreateAttachment(att *Attachment, r io.Reader) (created *Attachment, err error) {
pr, pw := io.Pipe()
mw := multipart.NewWriter(pw)
go func() {
if err := mw.WriteField("Filename", att.Name); err != nil {
pw.CloseWithError(err)
return
}
if err := mw.WriteField("MessageID", att.MessageID); err != nil {
pw.CloseWithError(err)
return
}
if err := mw.WriteField("MIMEType", att.MIMEType); err != nil {
pw.CloseWithError(err)
return
}
if att.ContentID != "" {
if err := mw.WriteField("ContentID", att.ContentID); err != nil {
pw.CloseWithError(err)
return
}
}
if w, err := mw.CreateFormFile("KeyPackets", "KeyPackets.pgp"); err != nil {
pw.CloseWithError(err)
return
} else {
kpr := base64.NewDecoder(base64.StdEncoding, strings.NewReader(att.KeyPackets))
if _, err := io.Copy(w, kpr); err != nil {
pw.CloseWithError(err)
return
}
}
2017-12-03 12:55:59 +02:00
if w, err := mw.CreateFormFile("DataPacket", "DataPacket.pgp"); err != nil {
pw.CloseWithError(err)
return
} else if _, err := io.Copy(w, r); err != nil {
pw.CloseWithError(err)
return
}
// TODO: Signature
if err := mw.Close(); err != nil {
pw.CloseWithError(err)
}
pw.Close()
}()
req, err := c.newRequest(http.MethodPost, "/attachments", pr)
if err != nil {
return nil, err
}
2017-12-03 12:55:59 +02:00
req.Header.Set("Content-Type", mw.FormDataContentType())
var respData struct {
resp
Attachment *Attachment
}
if err := c.doJSON(req, &respData); err != nil {
return nil, err
}
return respData.Attachment, nil
}