package proton import ( "bytes" "errors" "strings" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/bradenaw/juniper/xslices" "github.com/emersion/go-vcard" ) const ( FieldPMScheme = "X-PM-SCHEME" FieldPMSign = "X-PM-SIGN" FieldPMEncrypt = "X-PM-ENCRYPT" FieldPMEncryptUntrusted = "X-PM-ENCRYPT-UNTRUSTED" FieldPMMIMEType = "X-PM-MIMETYPE" ) type Cards []*Card func (c *Cards) Merge(kr *crypto.KeyRing) (vcard.Card, error) { merged := newVCard() for _, card := range *c { dec, err := card.decode(kr) if err != nil { return nil, err } for k, fields := range dec { for _, f := range fields { merged.Add(k, f) } } } return merged, nil } func (c *Cards) Get(cardType CardType) (*Card, bool) { for _, card := range *c { if card.Type == cardType { return card, true } } return nil, false } type Card struct { Type CardType Data string Signature string } type CardType int const ( CardTypeClear CardType = iota CardTypeEncrypted CardTypeSigned ) func NewCard(kr *crypto.KeyRing, cardType CardType) (*Card, error) { card := &Card{Type: cardType} if err := card.encode(kr, newVCard()); err != nil { return nil, err } return card, nil } func newVCard() vcard.Card { card := make(vcard.Card) card.AddValue(vcard.FieldVersion, "4.0") return card } func (c Card) Get(kr *crypto.KeyRing, key string) ([]*vcard.Field, error) { dec, err := c.decode(kr) if err != nil { return nil, err } return dec[key], nil } func (c *Card) Set(kr *crypto.KeyRing, key string, value *vcard.Field) error { dec, err := c.decode(kr) if err != nil { return err } if field := dec.Get(key); field != nil { field.Value = value.Value field.Params = value.Params field.Group = value.Group return c.encode(kr, dec) } dec.Add(key, value) return c.encode(kr, dec) } func (c *Card) Add(kr *crypto.KeyRing, key string, value *vcard.Field) error { dec, err := c.decode(kr) if err != nil { return err } dec.Add(key, value) return c.encode(kr, dec) } func (c *Card) ChangeType(kr *crypto.KeyRing, cardType CardType) error { dec, err := c.decode(kr) if err != nil { return err } c.Type = cardType return c.encode(kr, dec) } // GetGroup returns a type to manipulate the group defined by the given key/value pair. func (c Card) GetGroup(kr *crypto.KeyRing, groupKey, groupValue string) (CardGroup, error) { group, err := c.getGroup(kr, groupKey, groupValue) if err != nil { return CardGroup{}, err } return CardGroup{Card: c, kr: kr, group: group}, nil } // DeleteGroup removes all values in the group defined by the given key/value pair. func (c *Card) DeleteGroup(kr *crypto.KeyRing, groupKey, groupValue string) error { group, err := c.getGroup(kr, groupKey, groupValue) if err != nil { return err } return c.deleteGroup(kr, group) } type CardGroup struct { Card kr *crypto.KeyRing group string } // Get returns the values in the group with the given key. func (g CardGroup) Get(key string) ([]string, error) { dec, err := g.decode(g.kr) if err != nil { return nil, err } var fields []*vcard.Field for _, field := range dec[key] { if field.Group != g.group { continue } fields = append(fields, field) } return xslices.Map(fields, func(field *vcard.Field) string { return field.Value }), nil } // Set sets the value in the group. func (g *CardGroup) Set(key, value string, params vcard.Params) error { dec, err := g.decode(g.kr) if err != nil { return err } for _, field := range dec[key] { if field.Group != g.group { continue } field.Value = value return g.encode(g.kr, dec) } dec.Add(key, &vcard.Field{ Value: value, Group: g.group, Params: params, }) return g.encode(g.kr, dec) } // Add adds a value to the group. func (g *CardGroup) Add(key, value string, params vcard.Params) error { dec, err := g.decode(g.kr) if err != nil { return err } dec.Add(key, &vcard.Field{ Value: value, Group: g.group, Params: params, }) return g.encode(g.kr, dec) } // Remove removes the value in the group with the given key/value. func (g *CardGroup) Remove(key, value string) error { dec, err := g.decode(g.kr) if err != nil { return err } fields, ok := dec[key] if !ok { return errors.New("no such key") } var rest []*vcard.Field for _, field := range fields { if field.Group != g.group { rest = append(rest, field) } else if field.Value != value { rest = append(rest, field) } } if len(rest) > 0 { dec[key] = rest } else { delete(dec, key) } return g.encode(g.kr, dec) } // RemoveAll removes all values in the group with the given key. func (g *CardGroup) RemoveAll(key string) error { dec, err := g.decode(g.kr) if err != nil { return err } fields, ok := dec[key] if !ok { return errors.New("no such key") } var rest []*vcard.Field for _, field := range fields { if field.Group != g.group { rest = append(rest, field) } } if len(rest) > 0 { dec[key] = rest } else { delete(dec, key) } return g.encode(g.kr, dec) } func (c Card) getGroup(kr *crypto.KeyRing, groupKey, groupValue string) (string, error) { fields, err := c.Get(kr, groupKey) if err != nil { return "", err } for _, field := range fields { if field.Value != groupValue { continue } return field.Group, nil } return "", errors.New("no such field") } func (c *Card) deleteGroup(kr *crypto.KeyRing, group string) error { dec, err := c.decode(kr) if err != nil { return err } for key, fields := range dec { var rest []*vcard.Field for _, field := range fields { if field.Group != group { rest = append(rest, field) } } if len(rest) > 0 { dec[key] = rest } else { delete(dec, key) } } return c.encode(kr, dec) } func (c Card) decode(kr *crypto.KeyRing) (vcard.Card, error) { if c.Type&CardTypeEncrypted != 0 { enc, err := crypto.NewPGPMessageFromArmored(c.Data) if err != nil { return nil, err } dec, err := kr.Decrypt(enc, nil, crypto.GetUnixTime()) if err != nil { return nil, err } c.Data = dec.GetString() } if c.Type&CardTypeSigned != 0 { sig, err := crypto.NewPGPSignatureFromArmored(c.Signature) if err != nil { return nil, err } if err := kr.VerifyDetached(crypto.NewPlainMessageFromString(c.Data), sig, crypto.GetUnixTime()); err != nil { return nil, err } } return vcard.NewDecoder(strings.NewReader(c.Data)).Decode() } func (c *Card) encode(kr *crypto.KeyRing, card vcard.Card) error { buf := new(bytes.Buffer) if err := vcard.NewEncoder(buf).Encode(card); err != nil { return err } if c.Type&CardTypeSigned != 0 { sig, err := kr.SignDetached(crypto.NewPlainMessageFromString(buf.String())) if err != nil { return err } if c.Signature, err = sig.GetArmored(); err != nil { return err } } if c.Type&CardTypeEncrypted != 0 { enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(buf.String()), nil) if err != nil { return err } if c.Data, err = enc.GetArmored(); err != nil { return err } } else { c.Data = buf.String() } return nil }