protonmail: add crypto helpers for contacts

This commit is contained in:
emersion 2017-09-13 11:42:19 +02:00
parent 0447370b16
commit 16a9952616
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
1 changed files with 131 additions and 8 deletions

View File

@ -1,19 +1,26 @@
package protonmail package protonmail
import ( import (
"bytes"
"io"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
) )
type Contact struct { type Contact struct {
ID string ID string
Name string Name string
UID string UID string
Size int Size int
CreateTime int64 CreateTime int64
ModifyTime int64 ModifyTime int64
LabelIDs []string LabelIDs []string
// Not when using ListContacts // Not when using ListContacts
ContactEmails []*ContactEmail ContactEmails []*ContactEmail
@ -68,6 +75,122 @@ type ContactCard struct {
Signature string Signature string
} }
func NewEncryptedContactCard(r io.Reader, to []*openpgp.Entity, signer *openpgp.Entity) (*ContactCard, error) {
// TODO: sign and encrypt at the same time
var msg, armored bytes.Buffer
if signer != nil {
// We'll sign the message later, keep a copy of it
r = io.TeeReader(r, &msg)
}
ciphertext, err := armor.Encode(&armored, "PGP MESSAGE", nil)
if err != nil {
return nil, err
}
cleartext, err := openpgp.Encrypt(ciphertext, to, nil, nil, nil)
if err != nil {
return nil, err
}
if _, err := io.Copy(cleartext, r); err != nil {
return nil, err
}
if err := cleartext.Close(); err != nil {
return nil, err
}
if err := ciphertext.Close(); err != nil {
return nil, err
}
card := &ContactCard{
Type: ContactCardEncrypted,
Data: armored.String(),
}
if signer != nil {
var sig bytes.Buffer
if err := openpgp.ArmoredDetachSignText(&sig, signer, &msg, nil); err != nil {
return nil, err
}
card.Type = ContactCardEncryptedAndSigned
card.Signature = sig.String()
}
return card, nil
}
func NewSignedContactCard(r io.Reader, signer *openpgp.Entity) (*ContactCard, error) {
var msg, sig bytes.Buffer
r = io.TeeReader(r, &msg)
if err := openpgp.ArmoredDetachSignText(&sig, signer, r, nil); err != nil {
return nil, err
}
return &ContactCard{
Type: ContactCardSigned,
Data: msg.String(),
Signature: sig.String(),
}, nil
}
func entityPrimaryKey(e *openpgp.Entity) *openpgp.Key {
var selfSig *packet.Signature
for _, ident := range e.Identities {
if selfSig == nil {
selfSig = ident.SelfSignature
} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
selfSig = ident.SelfSignature
break
}
}
return &openpgp.Key{e, e.PrimaryKey, e.PrivateKey, selfSig}
}
func (card *ContactCard) Read(keyring openpgp.KeyRing) (*openpgp.MessageDetails, error) {
if !card.Type.Encrypted() {
md := &openpgp.MessageDetails{
IsEncrypted: false,
IsSigned: false,
UnverifiedBody: strings.NewReader(card.Data),
}
if !card.Type.Signed() {
return md, nil
}
signed := strings.NewReader(card.Data)
signature := strings.NewReader(card.Signature)
signer, err := openpgp.CheckArmoredDetachedSignature(keyring, signed, signature)
md.IsSigned = true
md.SignatureError = err
if signer != nil {
md.SignedByKeyId = signer.PrimaryKey.KeyId
md.SignedBy = entityPrimaryKey(signer)
}
return md, nil
}
ciphertextBlock, err := armor.Decode(strings.NewReader(card.Data))
if err != nil {
return nil, err
}
var r io.Reader = ciphertextBlock.Body
if card.Type.Signed() {
sigBlock, err := armor.Decode(strings.NewReader(card.Signature))
if err != nil {
return nil, err
}
r = io.MultiReader(r, sigBlock.Body)
}
return openpgp.ReadMessage(r, keyring, nil, nil)
}
type ContactExport struct { type ContactExport struct {
ID string ID string
Cards []*ContactCard Cards []*ContactCard
@ -167,7 +290,7 @@ func (c *Client) GetContact(id string) (*Contact, error) {
} }
type CreateContactResp struct { type CreateContactResp struct {
Index int Index int
Response struct { Response struct {
resp resp
Contact *Contact Contact *Contact
@ -180,7 +303,7 @@ func (resp *CreateContactResp) Err() error {
func (c *Client) CreateContacts(contacts []*ContactImport) ([]*CreateContactResp, error) { func (c *Client) CreateContacts(contacts []*ContactImport) ([]*CreateContactResp, error) {
reqData := struct { reqData := struct {
Contacts []*ContactImport Contacts []*ContactImport
Overwrite, Groups, Labels int Overwrite, Groups, Labels int
}{contacts, 0, 0, 0} }{contacts, 0, 0, 0}
req, err := c.newJSONRequest(http.MethodPost, "/contacts", &reqData) req, err := c.newJSONRequest(http.MethodPost, "/contacts", &reqData)
@ -217,7 +340,7 @@ func (c *Client) UpdateContact(id string, contact *ContactImport) (*Contact, err
} }
type DeleteContactResp struct { type DeleteContactResp struct {
ID string ID string
Response struct { Response struct {
resp resp
} }