hydroxide-push/protonmail/attachments.go

198 lines
4.9 KiB
Go
Raw Permalink 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
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-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
2017-12-03 13:27:31 +02:00
unencryptedKey *packet.EncryptedKey
2017-12-03 12:55:59 +02:00
}
2017-12-03 13:27:31 +02:00
// GenerateKey generates an encrypted key and encrypts it to the provided
// recipients. Usually, the recipient is the user himself.
2017-12-03 12:55:59 +02:00
//
2017-12-03 13:27:31 +02:00
// The returned key is NOT encrypted.
func (att *Attachment) GenerateKey(to []*openpgp.Entity) (*packet.EncryptedKey, error) {
2017-12-03 12:55:59 +02:00
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()
2017-12-03 13:27:31 +02:00
att.unencryptedKey = unencryptedKey
2017-12-03 12:55:59 +02:00
att.KeyPackets = encodedKeyPackets.String()
2017-12-03 13:27:31 +02:00
return unencryptedKey, nil
}
// 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")
}
2017-12-03 12:55:59 +02:00
// TODO: sign and store signature in att.Signature
2017-12-03 12:55:59 +02:00
hints := &openpgp.FileHints{
IsBinary: true,
FileName: att.Name,
2017-12-03 12:55:59 +02:00
}
return symetricallyEncrypt(ciphertext, att.unencryptedKey, nil, hints, config)
}
func (att *Attachment) Read(ciphertext io.Reader, keyring openpgp.KeyRing, prompt openpgp.PromptFunction) (*openpgp.MessageDetails, error) {
2018-01-08 12:46:49 +02:00
if len(att.KeyPackets) == 0 {
return &openpgp.MessageDetails{
IsEncrypted: false,
IsSigned: false,
UnverifiedBody: ciphertext,
}, nil
} else {
kpr := base64.NewDecoder(base64.StdEncoding, strings.NewReader(att.KeyPackets))
r := io.MultiReader(kpr, ciphertext)
return openpgp.ReadMessage(r, keyring, prompt, nil)
}
}
// GetAttachment downloads an attachment's payload. The returned io.ReadCloser
2018-01-08 12:46:49 +02:00
// may be encrypted, use Attachment.Read to decrypt it.
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
2017-12-03 13:27:31 +02:00
pw.CloseWithError(mw.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
}