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	"encoding/hex"
8	"fmt"
9	"regexp"
10	"sort"
11	"strconv"
12	"strings"
13	"time"
14
15	keybase1 "github.com/keybase/client/go/protocol/keybase1"
16)
17
18type AssertionExpression interface {
19	String() string
20	MatchSet(ps ProofSet) bool
21	HasOr() bool
22	NeedsParens() bool
23	CollectUrls([]AssertionURL) []AssertionURL
24	ToSocialAssertion() (keybase1.SocialAssertion, error)
25}
26
27type AssertionOr struct {
28	symbol string // the divider symbol used e.g. "," or "||"
29	terms  []AssertionExpression
30}
31
32func (a AssertionOr) HasOr() bool { return true }
33
34func (a AssertionOr) MatchSet(ps ProofSet) bool {
35	for _, t := range a.terms {
36		if t.MatchSet(ps) {
37			return true
38		}
39	}
40	return false
41}
42
43func (a AssertionOr) NeedsParens() bool {
44	for _, t := range a.terms {
45		if t.NeedsParens() {
46			return true
47		}
48	}
49	return false
50}
51
52func (a AssertionOr) CollectUrls(v []AssertionURL) []AssertionURL {
53	for _, t := range a.terms {
54		v = t.CollectUrls(v)
55	}
56	return v
57}
58
59func (a AssertionOr) String() string {
60	v := make([]string, len(a.terms))
61	for i, t := range a.terms {
62		v[i] = t.String()
63	}
64	return strings.Join(v, ",")
65}
66
67func (a AssertionOr) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
68	return sa, fmt.Errorf("cannot convert OR expression to single social assertion")
69}
70
71type AssertionAnd struct {
72	factors []AssertionExpression
73}
74
75func (a AssertionAnd) Len() int {
76	return len(a.factors)
77}
78
79func (a AssertionAnd) HasOr() bool {
80	for _, f := range a.factors {
81		if f.HasOr() {
82			return true
83		}
84	}
85	return false
86}
87
88func (a AssertionAnd) NeedsParens() bool {
89	for _, f := range a.factors {
90		if f.HasOr() {
91			return true
92		}
93	}
94	return false
95}
96
97func (a AssertionAnd) CollectUrls(v []AssertionURL) []AssertionURL {
98	for _, t := range a.factors {
99		v = t.CollectUrls(v)
100	}
101	return v
102}
103
104func (a AssertionAnd) MatchSet(ps ProofSet) bool {
105	for _, f := range a.factors {
106		if !f.MatchSet(ps) {
107			return false
108		}
109	}
110	return true
111}
112
113func (a AssertionAnd) HasFactor(pf Proof) bool {
114	ps := NewProofSet([]Proof{pf})
115	for _, f := range a.factors {
116		if f.MatchSet(*ps) {
117			return true
118		}
119	}
120	return false
121}
122
123func (a AssertionAnd) String() string {
124	v := make([]string, len(a.factors))
125	for i, f := range a.factors {
126		v[i] = f.String()
127		if _, ok := f.(AssertionOr); ok {
128			v[i] = "(" + v[i] + ")"
129		}
130	}
131	return strings.Join(v, "+")
132}
133
134func (a AssertionAnd) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
135	return sa, fmt.Errorf("cannot convert AND expression to single social assertion")
136}
137
138type AssertionURL interface {
139	AssertionExpression
140	Keys() []string
141	CheckAndNormalize(ctx AssertionContext) (AssertionURL, error)
142	IsKeybase() bool
143	IsUID() bool
144	IsTeamID() bool
145	IsTeamName() bool
146	IsSocial() bool
147	IsRemote() bool
148	IsFingerprint() bool
149	IsEmail() bool
150	MatchProof(p Proof) bool
151	ToUID() keybase1.UID
152	ToTeamID() keybase1.TeamID
153	ToTeamName() keybase1.TeamName
154	ToKeyValuePair() (string, string)
155	CacheKey() string
156	GetValue() string
157	GetKey() string
158	ToLookup() (string, string, error)
159	IsServerTrust() bool
160}
161
162type AssertionURLBase struct {
163	Key, Value string
164}
165
166func (b AssertionURLBase) ToKeyValuePair() (string, string) {
167	return b.Key, b.Value
168}
169func (b AssertionURLBase) GetKey() string { return b.Key }
170
171func (b AssertionURLBase) CacheKey() string {
172	return b.Key + ":" + b.Value
173}
174
175func (b AssertionURLBase) GetValue() string {
176	return b.Value
177}
178
179func (b AssertionURLBase) matchSet(v AssertionURL, ps ProofSet) bool {
180	proofs := ps.Get(v.Keys())
181	for _, proof := range proofs {
182		if v.MatchProof(proof) {
183			return true
184		}
185	}
186	return false
187}
188
189func (b AssertionURLBase) NeedsParens() bool { return false }
190func (b AssertionURLBase) HasOr() bool       { return false }
191
192func (a AssertionUID) MatchSet(ps ProofSet) bool         { return a.matchSet(a, ps) }
193func (a AssertionTeamID) MatchSet(ps ProofSet) bool      { return a.matchSet(a, ps) }
194func (a AssertionTeamName) MatchSet(ps ProofSet) bool    { return a.matchSet(a, ps) }
195func (a AssertionKeybase) MatchSet(ps ProofSet) bool     { return a.matchSet(a, ps) }
196func (a AssertionWeb) MatchSet(ps ProofSet) bool         { return a.matchSet(a, ps) }
197func (a AssertionSocial) MatchSet(ps ProofSet) bool      { return a.matchSet(a, ps) }
198func (a AssertionHTTP) MatchSet(ps ProofSet) bool        { return a.matchSet(a, ps) }
199func (a AssertionHTTPS) MatchSet(ps ProofSet) bool       { return a.matchSet(a, ps) }
200func (a AssertionDNS) MatchSet(ps ProofSet) bool         { return a.matchSet(a, ps) }
201func (a AssertionFingerprint) MatchSet(ps ProofSet) bool { return a.matchSet(a, ps) }
202func (a AssertionPhoneNumber) MatchSet(ps ProofSet) bool { return a.matchSet(a, ps) }
203func (a AssertionEmail) MatchSet(ps ProofSet) bool       { return a.matchSet(a, ps) }
204func (a AssertionWeb) Keys() []string {
205	return []string{"dns", "http", "https"}
206}
207func (a AssertionHTTP) Keys() []string                         { return []string{"http", "https"} }
208func (b AssertionURLBase) Keys() []string                      { return []string{b.Key} }
209func (b AssertionURLBase) IsKeybase() bool                     { return false }
210func (b AssertionURLBase) IsSocial() bool                      { return false }
211func (b AssertionURLBase) IsRemote() bool                      { return false }
212func (b AssertionURLBase) IsFingerprint() bool                 { return false }
213func (b AssertionURLBase) IsUID() bool                         { return false }
214func (b AssertionURLBase) IsEmail() bool                       { return b.Key == "email" }
215func (b AssertionURLBase) ToUID() (ret keybase1.UID)           { return ret }
216func (b AssertionURLBase) IsTeamID() bool                      { return false }
217func (b AssertionURLBase) IsTeamName() bool                    { return false }
218func (b AssertionURLBase) ToTeamID() (ret keybase1.TeamID)     { return ret }
219func (b AssertionURLBase) ToTeamName() (ret keybase1.TeamName) { return ret }
220func (b AssertionURLBase) MatchProof(proof Proof) bool {
221	return (strings.ToLower(proof.Value) == b.Value)
222}
223func (b AssertionURLBase) IsServerTrust() bool { return false }
224
225func (b AssertionURLBase) ToSocialAssertionHelper() (sa keybase1.SocialAssertion, err error) {
226	return keybase1.SocialAssertion{
227		User:    b.GetValue(),
228		Service: keybase1.SocialAssertionService(b.GetKey()),
229	}, nil
230}
231func (a AssertionUID) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
232	return sa, fmt.Errorf("cannot convert AssertionUID to social assertion")
233}
234func (a AssertionTeamID) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
235	return sa, fmt.Errorf("cannot convert AssertionTeamID to social assertion")
236}
237func (a AssertionTeamName) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
238	return sa, fmt.Errorf("cannot convert AssertionTeamName to social assertion")
239}
240func (a AssertionKeybase) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
241	return sa, fmt.Errorf("cannot convert AssertionKeybase to social assertion")
242}
243func (a AssertionWeb) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
244	return a.ToSocialAssertionHelper()
245}
246func (a AssertionSocial) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
247	return a.ToSocialAssertionHelper()
248}
249func (a AssertionHTTP) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
250	return a.ToSocialAssertionHelper()
251}
252func (a AssertionHTTPS) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
253	return a.ToSocialAssertionHelper()
254}
255func (a AssertionDNS) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
256	return a.ToSocialAssertionHelper()
257}
258func (a AssertionFingerprint) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
259	return a.ToSocialAssertionHelper()
260}
261func (a AssertionPhoneNumber) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
262	// Phone number is not "social" like facebook or twitter, and there are no
263	// public prooofs, but it still conforms to keybase1.SocialAssertion type
264	// used in implicit team handling code.
265	return a.ToSocialAssertionHelper()
266}
267func (a AssertionEmail) ToSocialAssertion() (sa keybase1.SocialAssertion, err error) {
268	// Email have no public proofs, but can still be converted to
269	// keybase1.SocialAssertion, used in implicit team handling code.
270	return a.ToSocialAssertionHelper()
271}
272func (a AssertionEmail) String() string {
273	return fmt.Sprintf("[%s]@email", a.Value)
274}
275
276func (a AssertionSocial) GetValue() string {
277	return a.Value
278}
279
280// Fingerprint matching is on the suffixes.  If the assertion matches
281// any suffix of the proof, then we're OK
282func (a AssertionFingerprint) MatchProof(proof Proof) bool {
283	v1, v2 := strings.ToLower(proof.Value), a.Value
284	l1, l2 := len(v1), len(v2)
285	if l2 > l1 {
286		return false
287	}
288	// Match the suffixes of the fingerprint
289	return (v1[(l1-l2):] == v2)
290}
291
292func (a AssertionUID) CollectUrls(v []AssertionURL) []AssertionURL         { return append(v, a) }
293func (a AssertionTeamID) CollectUrls(v []AssertionURL) []AssertionURL      { return append(v, a) }
294func (a AssertionTeamName) CollectUrls(v []AssertionURL) []AssertionURL    { return append(v, a) }
295func (a AssertionKeybase) CollectUrls(v []AssertionURL) []AssertionURL     { return append(v, a) }
296func (a AssertionWeb) CollectUrls(v []AssertionURL) []AssertionURL         { return append(v, a) }
297func (a AssertionSocial) CollectUrls(v []AssertionURL) []AssertionURL      { return append(v, a) }
298func (a AssertionHTTP) CollectUrls(v []AssertionURL) []AssertionURL        { return append(v, a) }
299func (a AssertionHTTPS) CollectUrls(v []AssertionURL) []AssertionURL       { return append(v, a) }
300func (a AssertionDNS) CollectUrls(v []AssertionURL) []AssertionURL         { return append(v, a) }
301func (a AssertionFingerprint) CollectUrls(v []AssertionURL) []AssertionURL { return append(v, a) }
302func (a AssertionPhoneNumber) CollectUrls(v []AssertionURL) []AssertionURL { return append(v, a) }
303func (a AssertionPhoneNumber) IsServerTrust() bool                         { return true }
304
305func (a AssertionEmail) CollectUrls(v []AssertionURL) []AssertionURL { return append(v, a) }
306func (a AssertionEmail) IsServerTrust() bool                         { return true }
307
308type AssertionSocial struct{ AssertionURLBase }
309type AssertionWeb struct{ AssertionURLBase }
310type AssertionKeybase struct{ AssertionURLBase }
311type AssertionUID struct {
312	AssertionURLBase
313	uid keybase1.UID
314}
315type AssertionTeamID struct {
316	AssertionURLBase
317	tid keybase1.TeamID
318}
319type AssertionTeamName struct {
320	AssertionURLBase
321	name keybase1.TeamName
322}
323
324type AssertionHTTP struct{ AssertionURLBase }
325type AssertionHTTPS struct{ AssertionURLBase }
326type AssertionDNS struct{ AssertionURLBase }
327type AssertionFingerprint struct{ AssertionURLBase }
328type AssertionPhoneNumber struct{ AssertionURLBase }
329type AssertionEmail struct{ AssertionURLBase }
330
331func (a AssertionHTTP) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
332	if err := a.checkAndNormalizeHost(); err != nil {
333		return nil, err
334	}
335	return a, nil
336}
337func (a AssertionHTTPS) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
338	if err := a.checkAndNormalizeHost(); err != nil {
339		return nil, err
340	}
341	return a, nil
342}
343func (a AssertionDNS) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
344	if err := a.checkAndNormalizeHost(); err != nil {
345		return nil, err
346	}
347	return a, nil
348}
349func (a AssertionWeb) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
350	if err := a.checkAndNormalizeHost(); err != nil {
351		return nil, err
352	}
353	return a, nil
354}
355
356func (a AssertionKeybase) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
357	a.Value = strings.ToLower(a.Value)
358	if !CheckUsername.F(a.Value) {
359		return nil, NewAssertionCheckError("bad keybase username '%s': %s", a.Value, CheckUsername.Hint)
360	}
361	return a, nil
362}
363
364func (a AssertionFingerprint) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
365	a.Value = strings.ToLower(a.Value)
366	if _, err := hex.DecodeString(a.Value); err != nil {
367		return nil, NewAssertionCheckError("bad hex string: '%s'", a.Value)
368	}
369	return a, nil
370}
371
372func (b *AssertionURLBase) checkAndNormalizeHost() error {
373
374	if len(b.Value) == 0 {
375		return NewAssertionCheckError("Bad assertion, no value given (key=%s)", b.Key)
376	}
377
378	b.Value = strings.ToLower(b.Value)
379
380	if !IsValidHostname(b.Value) {
381		return NewAssertionCheckError("Invalid hostname: %s", b.Value)
382	}
383
384	return nil
385}
386
387func (b AssertionURLBase) String() string {
388	return fmt.Sprintf("%s@%s", b.Value, b.Key)
389}
390func (a AssertionKeybase) String() string {
391	return a.Value
392}
393
394func (a AssertionWeb) ToLookup() (key, value string, err error) {
395	return "web", a.Value, nil
396}
397func (a AssertionHTTP) ToLookup() (key, value string, err error) {
398	return "http", a.Value, nil
399}
400func (a AssertionHTTPS) ToLookup() (key, value string, err error) {
401	return "https", a.Value, nil
402}
403func (a AssertionDNS) ToLookup() (key, value string, err error) {
404	return "dns", a.Value, nil
405}
406func (a AssertionFingerprint) ToLookup() (key, value string, err error) {
407	cmp := len(a.Value) - PGPFingerprintHexLen
408	value = a.Value
409	if len(a.Value) < 4 {
410		err = fmt.Errorf("fingerprint queries must be at least 2 bytes long")
411	} else if cmp == 0 {
412		key = "key_fingerprint"
413	} else if cmp < 0 {
414		key = "key_suffix"
415	} else {
416		err = fmt.Errorf("bad fingerprint; too long: %s", a.Value)
417	}
418	return
419}
420
421var assertionBracketNameRxx = regexp.MustCompile(`^\[[-_a-zA-Z0-9.@+]+\]$`)
422var assertionNameRxx = regexp.MustCompile(`^[-_a-zA-Z0-9.]+$`)
423var assertionServiceRxx = regexp.MustCompile(`^[a-zA-Z.-]+$`)
424
425func parseToKVPair(s string) (key string, value string, err error) {
426	// matchNameAndService runs regexp against potential name and service
427	// strings extracted from assertion.
428	matchNameAndService := func(name, service string) bool {
429		var k, v string // temp variables for key and value
430		if !assertionServiceRxx.MatchString(service) {
431			return false
432		}
433
434		// Normalize service name at parser level.
435		k = strings.ToLower(service)
436
437		if name == "" {
438			// We are fine with matching just the service. "dns:" is a valid
439			// assertion at parser level (but is rejected later in the
440			// process).
441			key = k
442			return true
443		}
444
445		var hasBrackets bool
446		if assertionNameRxx.MatchString(name) {
447			v = name
448		} else if assertionBracketNameRxx.MatchString(name) {
449			v = name[1 : len(name)-1]
450			hasBrackets = true
451		} else {
452			return false
453		}
454
455		// Set err in outer scope if find invalid square bracket syntax.
456		// Still return `true` because it's a successful match.
457		if k == "email" && !hasBrackets {
458			err = fmt.Errorf("expected bracket syntax for email assertion")
459		} else if k != "email" && hasBrackets {
460			err = fmt.Errorf("unexpected bracket syntax for assertion: %s", k)
461		}
462
463		// Finally pass back temp variables to outer scope.
464		key = k
465		value = v
466		return true
467	}
468
469	if atIndex := strings.LastIndex(s, "@"); atIndex != -1 {
470		name := s[:atIndex]
471		service := s[atIndex+1:]
472
473		if matchNameAndService(name, service) {
474			return key, value, err
475		}
476	}
477
478	if colIndex := strings.Index(s, ":"); colIndex != -1 {
479		service := s[:colIndex]
480		name := s[colIndex+1:]
481
482		// "dns://keybase.io" syntax.
483		name = strings.TrimPrefix(name, "//")
484
485		if matchNameAndService(name, service) {
486			return key, value, err
487		}
488	}
489
490	if assertionNameRxx.MatchString(s) {
491		key = ""
492		value = s
493		return key, value, nil
494	}
495
496	// We've exhausted our options, it's not a valid assertion we can parse.
497	return "", "", fmt.Errorf("Invalid key-value identity: %s", s)
498}
499
500func (a AssertionKeybase) IsKeybase() bool         { return true }
501func (a AssertionSocial) IsSocial() bool           { return true }
502func (a AssertionSocial) IsRemote() bool           { return true }
503func (a AssertionWeb) IsRemote() bool              { return true }
504func (a AssertionFingerprint) IsFingerprint() bool { return true }
505func (a AssertionUID) IsUID() bool                 { return true }
506func (a AssertionTeamID) IsTeamID() bool           { return true }
507func (a AssertionTeamName) IsTeamName() bool       { return true }
508func (a AssertionHTTP) IsRemote() bool             { return true }
509func (a AssertionHTTPS) IsRemote() bool            { return true }
510func (a AssertionDNS) IsRemote() bool              { return true }
511func (a AssertionPhoneNumber) IsRemote() bool      { return true }
512func (a AssertionEmail) IsRemote() bool            { return true }
513
514func (a AssertionUID) ToUID() keybase1.UID {
515	if a.uid.IsNil() {
516		if tmp, err := UIDFromHex(a.Value); err == nil {
517			a.uid = tmp
518		}
519	}
520	return a.uid
521}
522
523func (a AssertionTeamID) ToTeamID() keybase1.TeamID {
524	if a.tid.IsNil() {
525		if tmp, err := keybase1.TeamIDFromString(a.Value); err == nil {
526			a.tid = tmp
527		}
528	}
529	return a.tid
530}
531
532func (a AssertionTeamName) ToTeamName() keybase1.TeamName {
533	if a.name.IsNil() {
534		if tmp, err := keybase1.TeamNameFromString(a.Value); err != nil {
535			a.name = tmp
536		}
537	}
538	return a.name
539}
540
541func (a AssertionKeybase) ToLookup() (key, value string, err error) {
542	return "username", a.Value, nil
543}
544
545func (a AssertionUID) ToLookup() (key, value string, err error) {
546	return "uid", a.Value, nil
547}
548
549func (a AssertionUID) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
550	var err error
551	a.uid, err = UIDFromHex(a.Value)
552	a.Value = strings.ToLower(a.Value)
553	return a, err
554}
555
556func (a AssertionTeamID) ToLookup() (key, value string, err error) {
557	return "tid", a.Value, nil
558}
559
560func (a AssertionTeamName) ToLookup() (key, value string, err error) {
561	return "team", a.Value, nil
562}
563
564func (a AssertionTeamID) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
565	var err error
566	a.tid, err = keybase1.TeamIDFromString(a.Value)
567	a.Value = strings.ToLower(a.Value)
568	return a, err
569}
570
571func (a AssertionTeamName) CheckAndNormalize(_ AssertionContext) (AssertionURL, error) {
572	var err error
573	a.name, err = keybase1.TeamNameFromString(a.Value)
574	a.Value = a.name.String()
575	return a, err
576}
577
578func (a AssertionSocial) CheckAndNormalize(ctx AssertionContext) (AssertionURL, error) {
579	var err error
580	a.Value, err = ctx.NormalizeSocialName(a.Key, a.Value)
581	return a, err
582}
583
584func (a AssertionPhoneNumber) CheckAndNormalize(ctx AssertionContext) (AssertionURL, error) {
585	if !IsPossiblePhoneNumberAssertion(a.Value) {
586		return nil, NewAssertionCheckError("Invalid phone number: %s", a.Value)
587	}
588	return a, nil
589}
590
591func (a AssertionEmail) CheckAndNormalize(ctx AssertionContext) (AssertionURL, error) {
592	if strings.Count(a.Value, "@") != 1 {
593		return nil, NewAssertionCheckError("Invalid email address: %s", a.Value)
594	}
595	return a, nil
596}
597
598func (a AssertionSocial) ToLookup() (key, value string, err error) {
599	return a.Key, a.Value, nil
600}
601
602func (a AssertionPhoneNumber) ToLookup() (key, value string, err error) {
603	return "phone", "+" + a.Value, nil
604}
605
606func (a AssertionEmail) ToLookup() (key, value string, err error) {
607	return "email", a.Value, nil
608}
609
610func ParseAssertionURL(ctx AssertionContext, s string, strict bool) (ret AssertionURL, err error) {
611	key, val, err := parseToKVPair(s)
612
613	if err != nil {
614		return
615	}
616	return ParseAssertionURLKeyValue(ctx, key, val, strict)
617}
618
619func ParseAssertionURLKeyValue(ctx AssertionContext, key string, val string, strict bool) (ret AssertionURL, err error) {
620
621	if len(key) == 0 {
622		if strict {
623			err = fmt.Errorf("Bad assertion, no 'type' given: %s", val)
624			return nil, err
625		}
626		key = "keybase"
627	}
628
629	base := AssertionURLBase{key, val}
630	switch key {
631	case "keybase":
632		ret = AssertionKeybase{base}
633	case "uid":
634		ret = AssertionUID{AssertionURLBase: base}
635	case "tid":
636		ret = AssertionTeamID{AssertionURLBase: base}
637	case "team":
638		ret = AssertionTeamName{AssertionURLBase: base}
639	case "web":
640		ret = AssertionWeb{base}
641	case "http":
642		ret = AssertionHTTP{base}
643	case "https":
644		ret = AssertionHTTPS{base}
645	case "dns":
646		ret = AssertionDNS{base}
647	case PGPAssertionKey:
648		ret = AssertionFingerprint{base}
649	case "phone":
650		ret = AssertionPhoneNumber{base}
651	case "email":
652		ret = AssertionEmail{base}
653	default:
654		ret = AssertionSocial{base}
655	}
656	return ret.CheckAndNormalize(ctx)
657}
658
659type Proof struct {
660	Key, Value string
661}
662
663type ProofSet struct {
664	proofs map[string][]Proof
665}
666
667func NewProofSet(proofs []Proof) *ProofSet {
668	ret := &ProofSet{
669		proofs: make(map[string][]Proof),
670	}
671	for _, proof := range proofs {
672		ret.Add(proof)
673	}
674	return ret
675}
676
677func (ps *ProofSet) Add(p Proof) {
678	ps.proofs[p.Key] = append(ps.proofs[p.Key], p)
679}
680
681func (ps ProofSet) Get(keys []string) (ret []Proof) {
682	for _, key := range keys {
683		if v, ok := ps.proofs[key]; ok {
684			ret = append(ret, v...)
685		}
686	}
687	return ret
688}
689
690func FindBestIdentifyComponentURL(e AssertionExpression) AssertionURL {
691	urls := e.CollectUrls(nil)
692	if len(urls) == 0 {
693		return nil
694	}
695
696	var uid, tid, kb, team, soc, fp, rooter AssertionURL
697
698	for _, u := range urls {
699		if u.IsUID() {
700			uid = u
701			break
702		}
703		if u.IsTeamID() {
704			tid = u
705			break
706		}
707
708		if u.IsKeybase() {
709			kb = u
710		} else if u.IsTeamName() {
711			team = u
712		} else if u.IsFingerprint() && fp == nil {
713			fp = u
714		} else if u.IsSocial() {
715			k, _ := u.ToKeyValuePair()
716			if k == "rooter" {
717				rooter = u
718			} else if soc == nil {
719				soc = u
720			}
721		}
722	}
723
724	order := []AssertionURL{uid, tid, kb, team, fp, rooter, soc, urls[0]}
725	for _, p := range order {
726		if p != nil {
727			return p
728		}
729	}
730	return nil
731}
732
733func FindBestIdentifyComponent(e AssertionExpression) string {
734	u := FindBestIdentifyComponentURL(e)
735	if u == nil {
736		return ""
737	}
738	return u.String()
739}
740
741func CollectAssertions(e AssertionExpression) (remotes AssertionAnd, locals AssertionAnd) {
742	urls := e.CollectUrls(nil)
743	for _, u := range urls {
744		if u.IsRemote() {
745			remotes.factors = append(remotes.factors, u)
746		} else {
747			locals.factors = append(locals.factors, u)
748		}
749	}
750	return remotes, locals
751}
752
753func AssertionIsTeam(au AssertionURL) bool {
754	return au != nil && (au.IsTeamID() || au.IsTeamName())
755}
756
757func parseImplicitTeamPart(ctx AssertionContext, s string) (typ string, name string, err error) {
758	nColons := strings.Count(s, ":")
759	nAts := strings.Count(s, "@")
760	nDelimiters := nColons + nAts
761	if nDelimiters > 1 {
762		return "", "", fmt.Errorf("Invalid implicit team part, can have at most one ':' xor '@': %v", s)
763	}
764	if nDelimiters == 0 {
765		if CheckUsername.F(s) {
766			return "keybase", strings.ToLower(s), nil
767		}
768
769		return "", "", fmt.Errorf("Parsed part as keybase username, but invalid username (%q)", s)
770	}
771	assertion, err := ParseAssertionURL(ctx, s, true)
772	if err != nil {
773		return "", "", fmt.Errorf("Could not parse part as SBS assertion")
774	}
775	return assertion.GetKey(), assertion.GetValue(), nil
776}
777
778func FormatImplicitTeamDisplayNameSuffix(conflict keybase1.ImplicitTeamConflictInfo) string {
779	return fmt.Sprintf("(conflicted copy %v #%v)",
780		conflict.Time.Time().UTC().Format("2006-01-02"),
781		conflict.Generation)
782}
783
784// Parse a name like "mlsteele,malgorithms@twitter#bot (conflicted copy 2017-03-04 #2)"
785func ParseImplicitTeamDisplayName(ctx AssertionContext, s string, isPublic bool) (ret keybase1.ImplicitTeamDisplayName, err error) {
786	// Turn the whole string tolower
787	s = strings.ToLower(s)
788
789	split1 := strings.SplitN(s, " ", 2)     // split1: [assertions, ?conflict]
790	split2 := strings.Split(split1[0], "#") // split2: [writers, ?readers]
791	if len(split2) > 2 {
792		return ret, NewImplicitTeamDisplayNameError("can have at most one '#' separator")
793	}
794
795	seen := make(map[string]bool)
796	var readers, writers keybase1.ImplicitTeamUserSet
797	writers, err = parseImplicitTeamUserSet(ctx, split2[0], seen)
798	if err != nil {
799		return ret, err
800	}
801
802	if writers.NumTotalUsers() == 0 {
803		return ret, NewImplicitTeamDisplayNameError("need at least one writer")
804	}
805
806	if len(split2) == 2 {
807		readers, err = parseImplicitTeamUserSet(ctx, split2[1], seen)
808		if err != nil {
809			return ret, err
810		}
811	}
812
813	var conflictInfo *keybase1.ImplicitTeamConflictInfo
814	if len(split1) > 1 {
815		suffix := split1[1]
816		if len(suffix) == 0 {
817			return ret, NewImplicitTeamDisplayNameError("empty suffix")
818		}
819		conflictInfo, err = ParseImplicitTeamDisplayNameSuffix(suffix)
820		if err != nil {
821			return ret, err
822		}
823	}
824
825	ret = keybase1.ImplicitTeamDisplayName{
826		IsPublic:     isPublic,
827		ConflictInfo: conflictInfo,
828		Writers:      writers,
829		Readers:      readers,
830	}
831	return ret, nil
832}
833
834var implicitTeamDisplayNameConflictRxx = regexp.MustCompile(`^\(conflicted copy (\d{4}-\d{2}-\d{2})( #(\d+))?\)$`)
835
836func ParseImplicitTeamDisplayNameSuffix(suffix string) (ret *keybase1.ImplicitTeamConflictInfo, err error) {
837	if len(suffix) == 0 {
838		return ret, NewImplicitTeamDisplayNameError("cannot parse empty suffix")
839	}
840	matches := implicitTeamDisplayNameConflictRxx.FindStringSubmatch(suffix)
841	if len(matches) == 0 {
842		return ret, NewImplicitTeamDisplayNameError("malformed suffix: '%s'", suffix)
843	}
844	if len(matches) != 4 {
845		return ret, NewImplicitTeamDisplayNameError("malformed suffix; bad number of matches: %d", len(matches))
846	}
847
848	conflictTime, err := time.Parse("2006-01-02", matches[1])
849	if err != nil {
850		return ret, NewImplicitTeamDisplayNameError("malformed suffix time: %v", conflictTime)
851	}
852
853	var generation int
854	if len(matches[3]) == 0 {
855		generation = 1
856	} else {
857		generation, err = strconv.Atoi(matches[3])
858		if err != nil || generation <= 0 {
859			return ret, NewImplicitTeamDisplayNameError("malformed suffix generation: %v", matches[3])
860		}
861	}
862
863	return &keybase1.ImplicitTeamConflictInfo{
864		Generation: keybase1.ConflictGeneration(generation),
865		Time:       keybase1.ToTime(conflictTime.UTC()),
866	}, nil
867}
868
869func parseImplicitTeamUserSet(ctx AssertionContext, s string, seen map[string]bool) (ret keybase1.ImplicitTeamUserSet, err error) {
870
871	for _, part := range strings.Split(s, ",") {
872		typ, name, err := parseImplicitTeamPart(ctx, part)
873		if err != nil {
874			return keybase1.ImplicitTeamUserSet{}, err
875		}
876		sa := keybase1.SocialAssertion{User: name, Service: keybase1.SocialAssertionService(typ)}
877		idx := sa.String()
878		if seen[idx] {
879			continue
880		}
881		seen[idx] = true
882		if typ == "keybase" {
883			ret.KeybaseUsers = append(ret.KeybaseUsers, name)
884		} else {
885			ret.UnresolvedUsers = append(ret.UnresolvedUsers, sa)
886		}
887	}
888	sort.Strings(ret.KeybaseUsers)
889	sort.Slice(ret.UnresolvedUsers, func(i, j int) bool { return ret.UnresolvedUsers[i].String() < ret.UnresolvedUsers[j].String() })
890	return ret, nil
891}
892
893// Parse a name like "/keybase/private/mlsteele,malgorithms@twitter#bot (conflicted copy 2017-03-04 #2)"
894func ParseImplicitTeamTLFName(ctx AssertionContext, s string) (keybase1.ImplicitTeamDisplayName, error) {
895	ret := keybase1.ImplicitTeamDisplayName{}
896	s = strings.ToLower(s)
897	parts := strings.Split(s, "/")
898	if len(parts) != 4 {
899		return ret, fmt.Errorf("Invalid team TLF name, must have four parts")
900	}
901	if parts[0] != "" || parts[1] != "keybase" || (parts[2] != "private" && parts[2] != "public") {
902		return ret, fmt.Errorf("Invalid team TLF name")
903	}
904	isPublic := parts[2] == "public"
905	return ParseImplicitTeamDisplayName(ctx, parts[3], isPublic)
906}
907
908// Parse a name like "/keybase/team/happy.toucans"
909func ParseTeamPrivateKBFSPath(s string) (ret keybase1.TeamName, err error) {
910	s = strings.ToLower(s)
911	parts := strings.Split(s, "/")
912	if len(parts) != 4 {
913		return ret, fmt.Errorf("Invalid team TLF name, must have four parts")
914	}
915	if parts[0] != "" || parts[1] != "keybase" || parts[2] != "team" {
916		return ret, fmt.Errorf("Invalid team TLF name")
917	}
918	return keybase1.TeamNameFromString(parts[3])
919}
920
921type ResolvedAssertion struct {
922	UID           keybase1.UID
923	Assertion     AssertionExpression
924	ResolveResult ResolveResult
925}
926