protonmail: add some messages + public keys endpoints

This commit is contained in:
emersion 2017-09-20 10:09:31 +02:00
parent 26817a27c7
commit c6b479b01a
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
3 changed files with 145 additions and 1 deletions

View File

@ -8,10 +8,11 @@ import (
"io" "io"
"os" "os"
"github.com/emersion/hydroxide/protonmail"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
"github.com/emersion/hydroxide/protonmail"
) )
const authFile = "auth.json" const authFile = "auth.json"

View File

@ -1,5 +1,14 @@
package protonmail package protonmail
import (
"errors"
"net/http"
"net/url"
"strings"
"golang.org/x/crypto/openpgp"
)
type Key struct { type Key struct {
ID string ID string
Version int Version int
@ -8,3 +17,55 @@ type Key struct {
Fingerprint string Fingerprint string
Activation interface{} // TODO Activation interface{} // TODO
} }
type RecipientType int
const (
RecipientInternal RecipientType = 1
RecipientExternal = 2
)
type PublicKeyResp struct {
RecipientType RecipientType
MIMEType string
Keys []*PublicKey
}
type PublicKey struct {
Send int
PublicKey string
}
func (pub *PublicKey) Entity() (*openpgp.Entity, error) {
keyRing, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pub.PublicKey))
if err != nil {
return nil, err
}
if len(keyRing) == 0 {
return nil, errors.New("public key is empty")
}
return keyRing[0], nil
}
// GetPublicKeys retrieves public keys for a user. The first key in
// PublicKeyResp.Keys can be used for sending.
func (c *Client) GetPublicKeys(email string) (*PublicKeyResp, error) {
v := url.Values{}
v.Set("Email", email)
// TODO: Fingerprint
req, err := c.newRequest(http.MethodGet, "/keys?"+v.Encode(), nil)
if err != nil {
return nil, err
}
var respData struct {
resp
*PublicKeyResp
}
if err := c.doJSON(req, &respData); err != nil {
return nil, err
}
return respData.PublicKeyResp, nil
}

View File

@ -1,7 +1,13 @@
package protonmail package protonmail
import ( import (
"bytes"
"io"
"net/http" "net/http"
"strings"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
) )
type MessageType int type MessageType int
@ -15,6 +21,20 @@ const (
type MessageEncryption int type MessageEncryption int
const (
MessageUnencrypted MessageEncryption = iota
MessageEncryptedInternal
MessageEncryptedExternal
MessageEncryptedOutside
_
_
_
MessageEncryptedInlinePGP
MessageEncryptedPGPMIME
_
_
)
type MessageAddress struct { type MessageAddress struct {
Address string Address string
Name string Name string
@ -48,6 +68,66 @@ type Message struct {
ExternalID string ExternalID string
} }
func (msg *Message) Read(keyring openpgp.KeyRing, prompt openpgp.PromptFunction) (*openpgp.MessageDetails, error) {
switch msg.IsEncrypted {
case MessageUnencrypted:
return &openpgp.MessageDetails{
IsEncrypted: false,
IsSigned: false,
UnverifiedBody: strings.NewReader(msg.Body),
}, nil
default:
block, err := armor.Decode(strings.NewReader(msg.Body))
if err != nil {
return nil, err
}
return openpgp.ReadMessage(block.Body, keyring, prompt, nil)
}
}
type messageWriter struct {
plaintext io.WriteCloser
ciphertext io.WriteCloser
b *bytes.Buffer
msg *Message
}
func (w *messageWriter) Write(p []byte) (n int, err error) {
return w.plaintext.Write(p)
}
func (w *messageWriter) Close() error {
if err := w.plaintext.Close(); err != nil {
return err
}
if err := w.ciphertext.Close(); err != nil {
return err
}
w.msg.Body = w.b.String()
return nil
}
func (msg *Message) Encrypt(to []*openpgp.Entity, signed *openpgp.Entity) (plaintext io.WriteCloser, err error) {
var b bytes.Buffer
ciphertext, err := armor.Encode(&b, "PGP MESSAGE", nil)
if err != nil {
return nil, err
}
plaintext, err = openpgp.Encrypt(ciphertext, to, signed, nil, nil)
if err != nil {
return nil, err
}
return &messageWriter{
plaintext: plaintext,
ciphertext: ciphertext,
b: &b,
msg: msg,
}, nil
}
func (c *Client) GetMessage(id string) (*Message, error) { func (c *Client) GetMessage(id string) (*Message, error) {
req, err := c.newRequest(http.MethodGet, "/messages/"+id, nil) req, err := c.newRequest(http.MethodGet, "/messages/"+id, nil)
if err != nil { if err != nil {
@ -65,6 +145,8 @@ func (c *Client) GetMessage(id string) (*Message, error) {
return respData.Message, nil return respData.Message, nil
} }
// CreateDraftMessage creates a new draft message. ToList, CCList, BCCList,
// Subject, Body and AddressID are required in msg.
func (c *Client) CreateDraftMessage(msg *Message, parentID string) (*Message, error) { func (c *Client) CreateDraftMessage(msg *Message, parentID string) (*Message, error) {
reqData := struct { reqData := struct {
Message *Message Message *Message