1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"bufio"
8	"fmt"
9	"io"
10	"sort"
11	"strconv"
12	"strings"
13	"time"
14
15	keybase1 "github.com/keybase/client/go/protocol/keybase1"
16	"github.com/keybase/go-crypto/openpgp/packet"
17)
18
19//=============================================================================
20
21type BucketDict struct {
22	d map[string][]*GpgPrimaryKey
23}
24
25func NewBuckDict() *BucketDict {
26	return &BucketDict{
27		d: make(map[string][]*GpgPrimaryKey),
28	}
29}
30
31func (bd *BucketDict) Add(k string, v *GpgPrimaryKey) {
32	k = strings.ToLower(k)
33	bd.d[k] = append(bd.d[k], v)
34}
35
36func (bd BucketDict) Get(k string) []*GpgPrimaryKey {
37	k = strings.ToLower(k)
38	ret, found := bd.d[k]
39	if !found {
40		ret = nil
41	}
42	return ret
43}
44
45func (bd BucketDict) Get0Or1(k string) (ret *GpgPrimaryKey, err error) {
46	v := bd.Get(k)
47	if len(v) > 1 {
48		err = GpgError{fmt.Sprintf("Wanted a unique lookup but got %d objects for key %s", len(v), k)}
49	} else if len(v) == 1 {
50		ret = v[0]
51	}
52	return
53}
54
55//=============================================================================
56
57func Uniquify(inp []string) []string {
58	m := make(map[string]bool)
59	for _, s := range inp {
60		m[strings.ToLower(s)] = true
61	}
62	ret := make([]string, 0, len(m))
63	for k := range m {
64		ret = append(ret, k)
65	}
66	return ret
67}
68
69//=============================================================================
70
71type GpgBaseKey struct {
72	Type        string
73	Trust       string
74	Bits        int
75	Algo        int
76	ID64        string
77	Created     int64
78	Expires     int64
79	fingerprint *PGPFingerprint
80}
81
82func (k GpgBaseKey) AlgoString() string {
83	switch packet.PublicKeyAlgorithm(k.Algo) {
84	case packet.PubKeyAlgoDSA:
85		return "D"
86	case packet.PubKeyAlgoRSA:
87		return "R"
88	case packet.PubKeyAlgoECDSA:
89		return "E"
90	default:
91		return "?"
92	}
93}
94
95func (k GpgBaseKey) ExpirationString() string {
96	if k.Expires == 0 {
97		return "never"
98	}
99	layout := "2006-01-02"
100	return time.Unix(k.Expires, 0).Format(layout)
101}
102
103func (k GpgBaseKey) CreatedString() string {
104	layout := "2006-01-02"
105	return time.Unix(k.Created, 0).Format(layout)
106}
107
108func (k *GpgBaseKey) ParseBase(line *GpgIndexLine) (err error) {
109	if line.Len() < 12 {
110		err = GpgIndexError{line.lineno, "Not enough fields (need 12)"}
111		return
112	}
113
114	k.Type = line.At(0)
115	k.Trust = line.At(1)
116	k.ID64 = line.At(4)
117
118	parseTimeStamp := func(s string) (ret int64, err error) {
119		// No date was specified
120		if len(s) == 0 {
121			return
122		}
123		// GPG 2.0+ format
124		if ret, err = strconv.ParseInt(s, 10, 0); err == nil {
125			return
126		}
127		var tmp time.Time
128		if tmp, err = time.Parse("2006-01-02", s); err != nil {
129			return
130		}
131		ret = tmp.Unix()
132		return
133	}
134
135	if k.Bits, err = strconv.Atoi(line.At(2)); err != nil {
136		return
137	}
138	if k.Algo, err = strconv.Atoi(line.At(3)); err != nil {
139		return
140	}
141	if k.Created, err = parseTimeStamp(line.At(5)); err != nil {
142		return
143	}
144	if k.Expires, err = parseTimeStamp(line.At(6)); err != nil {
145		return
146	}
147
148	return
149}
150
151//=============================================================================
152
153type GpgFingerprinter interface {
154	SetFingerprint(pgp *PGPFingerprint)
155}
156
157type GpgPrimaryKey struct {
158	Contextified
159	GpgBaseKey
160	subkeys    []*GpgSubKey
161	identities []*Identity
162	top        GpgFingerprinter
163}
164
165func (k *GpgPrimaryKey) IsValid(mctx MetaContext) bool {
166	if k == nil {
167		return false
168	}
169	if k.Trust == "r" {
170		return false
171	} else if k.Expires == 0 {
172		return true
173	} else {
174		expired := time.Now().After(time.Unix(k.Expires, 0))
175		if expired {
176			var fp string
177			if k.fingerprint != nil {
178				fp = " (" + k.fingerprint.ToQuads() + ")"
179			}
180			mctx.Warning("Skipping expired primary key%s", fp)
181		}
182		return !expired
183	}
184}
185
186func (k *GpgPrimaryKey) ToRow(i int) []string {
187	v := []string{
188		fmt.Sprintf("(%d)", i),
189		fmt.Sprintf("%d%s", k.Bits, k.AlgoString()),
190		k.fingerprint.ToKeyID(),
191		k.ExpirationString(),
192	}
193	for _, i := range k.identities {
194		v = append(v, i.Email)
195	}
196	return v
197}
198
199func (k *GpgBaseKey) SetFingerprint(pgp *PGPFingerprint) {
200	k.fingerprint = pgp
201}
202
203func (k *GpgPrimaryKey) Parse(l *GpgIndexLine) error {
204	if err := k.ParseBase(l); err != nil {
205		return err
206	}
207	return k.AddUID(l)
208}
209
210func NewGpgPrimaryKey(g *GlobalContext) *GpgPrimaryKey {
211	ret := &GpgPrimaryKey{Contextified: NewContextified(g)}
212	ret.top = ret
213	return ret
214}
215
216func ParseGpgPrimaryKey(g *GlobalContext, l *GpgIndexLine) (key *GpgPrimaryKey, err error) {
217	key = NewGpgPrimaryKey(g)
218	err = key.Parse(l)
219	return
220}
221
222func (k *GpgPrimaryKey) AddUID(l *GpgIndexLine) (err error) {
223	var id *Identity
224	if f := l.At(9); len(f) == 0 {
225	} else if id, err = ParseIdentity(f); err != nil {
226	} else if l.At(1) != "r" { // is not revoked
227		k.identities = append(k.identities, id)
228	}
229	if err != nil {
230		err = ErrorToGpgIndexError(l.lineno, err)
231	}
232	return
233}
234
235func (k *GpgPrimaryKey) AddFingerprint(l *GpgIndexLine) (err error) {
236	var fp *PGPFingerprint
237	if f := l.At(9); len(f) == 0 {
238		err = fmt.Errorf("no fingerprint given")
239	} else if fp, err = PGPFingerprintFromHex(f); err == nil {
240		k.top.SetFingerprint(fp)
241	}
242	if err != nil {
243		err = ErrorToGpgIndexError(l.lineno, err)
244	}
245	return
246}
247
248func (k *GpgPrimaryKey) GetFingerprint() *PGPFingerprint {
249	return k.fingerprint
250}
251
252func (k *GpgPrimaryKey) GetPGPIdentities() []keybase1.PGPIdentity {
253	ret := make([]keybase1.PGPIdentity, len(k.identities))
254	for i, ident := range k.identities {
255		ret[i] = ident.Export()
256	}
257	return ret
258}
259
260func (k *GpgPrimaryKey) GetEmails() []string {
261	ret := make([]string, len(k.identities))
262	for i, id := range k.identities {
263		ret[i] = id.Email
264	}
265	return ret
266}
267
268func (k *GpgPrimaryKey) GetAllID64s() []string {
269	var ret []string
270	add := func(fp *PGPFingerprint) {
271		if fp != nil {
272			ret = append(ret, fp.ToKeyID())
273		}
274	}
275	add(k.GetFingerprint())
276	for _, sk := range k.subkeys {
277		add(sk.fingerprint)
278	}
279	return ret
280}
281
282func (k *GpgPrimaryKey) AddSubkey(l *GpgIndexLine) (err error) {
283	var sk *GpgSubKey
284	if sk, err = ParseGpgSubKey(l); err == nil {
285		k.subkeys = append(k.subkeys, sk)
286		k.top = sk
287	}
288	return
289}
290
291func (k *GpgPrimaryKey) ToKey() *GpgPrimaryKey { return k }
292
293func (k *GpgPrimaryKey) AddLine(l *GpgIndexLine) (err error) {
294	if l.Len() < 2 {
295		err = GpgIndexError{l.lineno, "too few fields"}
296	} else {
297		f := l.At(0)
298		switch f {
299		case "fpr":
300			err = k.AddFingerprint(l)
301		case "uid":
302			err = k.AddUID(l)
303		case "uat", "grp": // ignore
304		case "sub", "ssb":
305			err = k.AddSubkey(l)
306		case "rvk": // designated revoker (ignore)
307		default:
308			err = GpgIndexError{l.lineno, fmt.Sprintf("Unknown subfield: %s", f)}
309		}
310
311	}
312	return err
313}
314
315//=============================================================================
316
317type GpgSubKey struct {
318	GpgBaseKey
319}
320
321func ParseGpgSubKey(l *GpgIndexLine) (sk *GpgSubKey, err error) {
322	sk = &GpgSubKey{}
323	err = sk.ParseBase(l)
324	return
325}
326
327//=============================================================================
328
329type GpgIndexElement interface {
330	ToKey() *GpgPrimaryKey
331}
332
333type GpgKeyIndex struct {
334	Keys                        []*GpgPrimaryKey
335	Emails, Fingerprints, ID64s *BucketDict
336}
337
338func (ki *GpgKeyIndex) Len() int {
339	return len(ki.Keys)
340}
341func (ki *GpgKeyIndex) Swap(i, j int) {
342	ki.Keys[i], ki.Keys[j] = ki.Keys[j], ki.Keys[i]
343}
344func (ki *GpgKeyIndex) Less(i, j int) bool {
345	a, b := ki.Keys[i], ki.Keys[j]
346	if len(a.identities) > len(b.identities) {
347		return true
348	}
349	if len(a.identities) < len(b.identities) {
350		return false
351	}
352	if a.Expires == 0 {
353		return true
354	}
355	if b.Expires == 0 {
356		return false
357	}
358	if a.Expires > b.Expires {
359		return true
360	}
361	return false
362}
363
364func (ki *GpgKeyIndex) GetRowFunc() func() []string {
365	i := 0
366	return func() []string {
367		if i >= len(ki.Keys) {
368			return nil
369		}
370		ret := ki.Keys[i].ToRow(i + 1)
371		i++
372		return ret
373	}
374}
375
376func (ki *GpgKeyIndex) Sort() {
377	sort.Sort(ki)
378}
379
380func NewGpgKeyIndex() *GpgKeyIndex {
381	return &GpgKeyIndex{
382		Emails:       NewBuckDict(),
383		Fingerprints: NewBuckDict(),
384		ID64s:        NewBuckDict(),
385	}
386}
387
388func (ki *GpgKeyIndex) IndexKey(k *GpgPrimaryKey) {
389	ki.Keys = append(ki.Keys, k)
390	if fp := k.GetFingerprint(); fp != nil {
391		ki.Fingerprints.Add(fp.String(), k)
392	}
393	for _, e := range Uniquify(k.GetEmails()) {
394		ki.Emails.Add(e, k)
395	}
396	for _, i := range Uniquify(k.GetAllID64s()) {
397		ki.ID64s.Add(i, k)
398	}
399}
400
401func (ki *GpgKeyIndex) PushElement(mctx MetaContext, e GpgIndexElement) {
402	if key := e.ToKey(); key.IsValid(mctx) {
403		ki.IndexKey(key)
404	}
405}
406
407func (ki *GpgKeyIndex) AllFingerprints() []PGPFingerprint {
408	var ret []PGPFingerprint
409	for _, k := range ki.Keys {
410		if fp := k.GetFingerprint(); fp != nil {
411			ret = append(ret, *fp)
412		}
413	}
414	return ret
415}
416
417//=============================================================================
418
419type GpgIndexLine struct {
420	v      []string
421	lineno int
422}
423
424func (g GpgIndexLine) Len() int        { return len(g.v) }
425func (g GpgIndexLine) At(i int) string { return g.v[i] }
426
427func ParseLine(s string, i int) (ret *GpgIndexLine, err error) {
428	s = strings.TrimSpace(s)
429	v := strings.Split(s, ":")
430	if v == nil {
431		err = GpgError{fmt.Sprintf("%d: Bad line; split failed", i)}
432	} else {
433		ret = &GpgIndexLine{v, i}
434	}
435	return
436}
437
438func (g GpgIndexLine) IsNewKey() bool {
439	return len(g.v) > 0 && (g.v[0] == "sec" || g.v[0] == "pub")
440}
441
442//=============================================================================
443
444type GpgIndexParser struct {
445	Contextified
446	warnings Warnings
447	putback  *GpgIndexLine
448	src      *bufio.Reader
449	eof      bool
450	lineno   int
451}
452
453func NewGpgIndexParser(g *GlobalContext) *GpgIndexParser {
454	return &GpgIndexParser{
455		Contextified: NewContextified(g),
456		eof:          false,
457		lineno:       0,
458		putback:      nil,
459	}
460}
461
462func (p *GpgIndexParser) Warn(w Warning) {
463	p.warnings.Push(w)
464}
465
466func (p *GpgIndexParser) ParseElement() (ret GpgIndexElement, err error) {
467	var line *GpgIndexLine
468	line, err = p.GetLine()
469	if err != nil || line == nil {
470	} else if line.IsNewKey() {
471		ret, err = p.ParseKey(line)
472	}
473	return
474}
475
476func (p *GpgIndexParser) ParseKey(l *GpgIndexLine) (ret *GpgPrimaryKey, err error) {
477	var line *GpgIndexLine
478	ret, err = ParseGpgPrimaryKey(p.G(), l)
479	done := false
480	for !done && err == nil && !p.isEOF() {
481		if line, err = p.GetLine(); line == nil || err != nil {
482		} else if line.IsNewKey() {
483			p.PutbackLine(line)
484			done = true
485		} else if e2 := ret.AddLine(line); e2 == nil {
486		} else {
487			p.warnings.Push(ErrorToWarning(e2))
488		}
489	}
490	return
491}
492
493func (p *GpgIndexParser) GetLine() (ret *GpgIndexLine, err error) {
494	if p.putback != nil {
495		ret = p.putback
496		p.putback = nil
497		return
498	}
499
500	if p.isEOF() {
501		return
502	}
503
504	s, e2 := p.src.ReadString(byte('\n'))
505	if e2 == io.EOF {
506		p.eof = true
507		return
508	}
509	if e2 != nil {
510		return nil, e2
511	}
512
513	p.lineno++
514	return ParseLine(s, p.lineno)
515}
516
517func (p *GpgIndexParser) PutbackLine(line *GpgIndexLine) {
518	p.putback = line
519}
520
521func (p GpgIndexParser) isEOF() bool { return p.eof }
522
523func (p *GpgIndexParser) Parse(mctx MetaContext, stream io.Reader) (ki *GpgKeyIndex, err error) {
524	p.src = bufio.NewReader(stream)
525	ki = NewGpgKeyIndex()
526	for err == nil && !p.isEOF() {
527		var el GpgIndexElement
528		if el, err = p.ParseElement(); err == nil && el != nil {
529			ki.PushElement(mctx, el)
530		}
531	}
532	ki.Sort()
533	return
534}
535
536//=============================================================================
537
538func ParseGpgIndexStream(mctx MetaContext, stream io.Reader) (ki *GpgKeyIndex, w Warnings, err error) {
539	eng := NewGpgIndexParser(mctx.G())
540	ki, err = eng.Parse(mctx, stream)
541	w = eng.warnings
542	return
543}
544
545//=============================================================================
546
547func (g *GpgCLI) Index(mctx MetaContext, secret bool, query string) (ki *GpgKeyIndex, w Warnings, err error) {
548	var k string
549	if secret {
550		k = "-K"
551	} else {
552		k = "-k"
553	}
554	args := []string{"--with-colons", "--fingerprint", k}
555	if len(query) > 0 {
556		args = append(args, query)
557	}
558	garg := RunGpg2Arg{
559		Arguments: args,
560		Stdout:    true,
561	}
562	res := g.Run2(mctx, garg)
563	if res.Err != nil {
564		err = res.Err
565		return
566	}
567	if ki, w, err = ParseGpgIndexStream(mctx, res.Stdout); err != nil {
568		return
569	}
570	err = res.Wait()
571	return
572}
573
574//=============================================================================
575