From 16a9952616317b978ad2898aa35c44de71d27ed9 Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 13 Sep 2017 11:42:19 +0200 Subject: [PATCH] protonmail: add crypto helpers for contacts --- protonmail/contacts.go | 139 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/protonmail/contacts.go b/protonmail/contacts.go index 512c3f2..f38c285 100644 --- a/protonmail/contacts.go +++ b/protonmail/contacts.go @@ -1,19 +1,26 @@ package protonmail import ( + "bytes" + "io" "net/http" "net/url" "strconv" + "strings" + + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/packet" ) type Contact struct { - ID string - Name string - UID string - Size int + ID string + Name string + UID string + Size int CreateTime int64 ModifyTime int64 - LabelIDs []string + LabelIDs []string // Not when using ListContacts ContactEmails []*ContactEmail @@ -68,6 +75,122 @@ type ContactCard struct { 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 { ID string Cards []*ContactCard @@ -167,7 +290,7 @@ func (c *Client) GetContact(id string) (*Contact, error) { } type CreateContactResp struct { - Index int + Index int Response struct { resp Contact *Contact @@ -180,7 +303,7 @@ func (resp *CreateContactResp) Err() error { func (c *Client) CreateContacts(contacts []*ContactImport) ([]*CreateContactResp, error) { reqData := struct { - Contacts []*ContactImport + Contacts []*ContactImport Overwrite, Groups, Labels int }{contacts, 0, 0, 0} 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 { - ID string + ID string Response struct { resp }