/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/libkb/id_table.go
1 947 строк
59 KB
zoom-ua
perf(identify): singleflight concurrent remote proof checks (#29488)
11 авг 2026, 20:59
Не верифицирован
11 авг 2026, 20:59
9deebb5
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. package libkb import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "strings" "time" keybase1 "github.com/keybase/client/go/protocol/keybase1" stellar1 "github.com/keybase/client/go/protocol/stellar1" jsonw "github.com/keybase/go-jsonw" ) type TypedChainLink interface { GetRevocations() []keybase1.SigID GetRevokeKids() []keybase1.KID insertIntoTable(tab *IdentityTable) GetSigID() keybase1.SigID GetArmoredSig() string markRevoked(l TypedChainLink) ToDebugString() string Type() string ToDisplayString() string IsRevocationIsh() bool IsRevoked() bool IsDirectlyRevoked() bool GetRole() KeyRole GetSeqno() keybase1.Seqno GetCTime() time.Time GetETime() time.Time GetPGPFingerprint() *PGPFingerprint GetPGPFullHash() string GetKID() keybase1.KID IsInCurrentFamily(u *User) bool GetUsername() string GetUID() keybase1.UID GetDelegatedKid() keybase1.KID GetMerkleHashMeta() (keybase1.HashMeta, error) GetParentKid() keybase1.KID VerifyReverseSig(ckf ComputedKeyFamily) error GetMerkleSeqno() keybase1.Seqno GetFirstAppearedMerkleSeqnoUnverified() keybase1.Seqno GetDevice() *Device DoOwnNewLinkFromServerNotifications(g *GlobalContext) ToSigChainLocation() keybase1.SigChainLocation } // ========================================================================= // GenericChainLink // type GenericChainLink struct { *ChainLink } func (g *GenericChainLink) GetSigID() keybase1.SigID { return g.unpacked.sigID } func (g *GenericChainLink) ToSigChainLocation() keybase1.SigChainLocation { return g.ChainLink.ToSigChainLocation() } func (g *GenericChainLink) Type() string { return "generic" } func (g *GenericChainLink) ToDisplayString() string { return "unknown" } func (g *GenericChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(g) } func (g *GenericChainLink) markRevoked(r TypedChainLink) { g.revoked = true } func (g *GenericChainLink) ToDebugString() string { return fmt.Sprintf("uid=%s, seq=%d, link=%s", g.Parent().uid, g.unpacked.seqno, g.id) } func (g *GenericChainLink) GetDelegatedKid() (kid keybase1.KID) { return } func (g *GenericChainLink) GetParentKid() (kid keybase1.KID) { return } func (g *GenericChainLink) VerifyReverseSig(ckf ComputedKeyFamily) error { return nil } func (g *GenericChainLink) IsRevocationIsh() bool { return false } func (g *GenericChainLink) GetRole() KeyRole { return DLGNone } func (g *GenericChainLink) IsRevoked() bool { return g.revoked } func (g *GenericChainLink) IsDirectlyRevoked() bool { // Same as IsRevoked, but should not be overridden by subclasses (as // TrackChainLink does with IsRevoked). E.g. if in the future // SibkeyChainLink decides to return IsRevoked=true when the delegated // sibkey has been revoked *by KID*, that could be fine, but // IsDirectlyRevoked should still return false in that case. return g.revoked } func (g *GenericChainLink) GetSeqno() keybase1.Seqno { return g.unpacked.seqno } func (g *GenericChainLink) GetPGPFingerprint() *PGPFingerprint { return g.unpacked.pgpFingerprint } func (g *GenericChainLink) GetPGPFullHash() string { return "" } func (g *GenericChainLink) GetArmoredSig() string { return g.unpacked.sig } func (g *GenericChainLink) GetUsername() string { return g.unpacked.username } func (g *GenericChainLink) GetUID() keybase1.UID { return g.unpacked.uid } func (g *GenericChainLink) GetDevice() *Device { return nil } func (g *GenericChainLink) extractPGPFullHash(loc string) string { if jw := g.UnmarshalPayloadJSON().AtPath("body." + loc + ".full_hash"); !jw.IsNil() { if ret, err := jw.GetString(); err == nil { return ret } } return "" } func (g *GenericChainLink) DoOwnNewLinkFromServerNotifications(glob *GlobalContext) {} func CanonicalProofName(t TypedChainLink) string { return strings.ToLower(t.ToDisplayString()) } // // ========================================================================= // ========================================================================= // Web of Trust type WotVouchChainLink struct { GenericChainLink ExpansionID string Revocations []keybase1.SigID } func (cl *WotVouchChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) {} func (cl *WotVouchChainLink) Type() string { return string(LinkTypeWotVouch) } var _ TypedChainLink = (*WotVouchChainLink)(nil) func ParseWotVouch(base GenericChainLink) (ret *WotVouchChainLink, err error) { body := base.UnmarshalPayloadJSON() expansionID, err := body.AtPath("body.wot_vouch").GetString() if err != nil { return nil, err } return &WotVouchChainLink{ GenericChainLink: base, ExpansionID: expansionID, Revocations: base.GetRevocations(), }, nil } type WotReactChainLink struct { GenericChainLink ExpansionID string } func (cl *WotReactChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) {} func (cl *WotReactChainLink) Type() string { return string(LinkTypeWotReact) } var _ TypedChainLink = (*WotReactChainLink)(nil) func ParseWotReact(base GenericChainLink) (ret *WotReactChainLink, err error) { body := base.UnmarshalPayloadJSON() expansionID, err := body.AtPath("body.wot_react").GetString() if err != nil { return nil, err } return &WotReactChainLink{ GenericChainLink: base, ExpansionID: expansionID, }, nil } type sigExpansion struct { Key string `json:"key"` Obj any `json:"obj"` } // ExtractExpansionObj extracts the `obj` field from a sig expansion and verifies the // hash of the content matches the expected id. This is reusable beyond WotVouchChainLink. func ExtractExpansionObj(expansionID string, expansionJSON string) (expansionObj []byte, err error) { var expansions map[string]sigExpansion err = json.Unmarshal([]byte(expansionJSON), &expansions) if err != nil { return nil, err } expansion, ok := expansions[expansionID] if !ok { return nil, fmt.Errorf("expansion %s does not exist", expansionID) } // verify the hash of the expansion object payload matches the expension id objBytes, err := json.Marshal(expansion.Obj) if err != nil { return nil, err } hmacKey, err := hex.DecodeString(expansion.Key) if err != nil { return nil, err } mac := hmac.New(sha256.New, hmacKey) if _, err := mac.Write(objBytes); err != nil { return nil, err } sum := mac.Sum(nil) expectedID := hex.EncodeToString(sum) if expectedID != expansionID { return nil, fmt.Errorf("expansion id doesn't match expected value %s != %s", expansionID, expectedID) } return objBytes, nil } func EmbedExpansionObj(statement *jsonw.Wrapper) (expansion *jsonw.Wrapper, sum []byte, err error) { outer := jsonw.NewDictionary() inner := jsonw.NewDictionary() if err := inner.SetKey("obj", statement); err != nil { return nil, nil, err } randKey, err := RandBytes(16) if err != nil { return nil, nil, err } hexKey := hex.EncodeToString(randKey) if err := inner.SetKey("key", jsonw.NewString(hexKey)); err != nil { return nil, nil, err } marshaled, err := statement.Marshal() if err != nil { return nil, nil, err } mac := hmac.New(sha256.New, randKey) if _, err := mac.Write(marshaled); err != nil { return nil, nil, err } sum = mac.Sum(nil) if err := outer.SetKey(hex.EncodeToString(sum), inner); err != nil { return nil, nil, err } return outer, sum, nil } // ========================================================================= // Remote, Web and Social type RemoteProofChainLink interface { TypedChainLink DisplayPriorityKey() string TableKey() string LastWriterWins() bool GetRemoteUsername() string GetHostname() string GetProtocol() string DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error ToTrackingStatement(keybase1.ProofState) (*jsonw.Wrapper, error) CheckDataJSON() *jsonw.Wrapper ToIDString() string ToKeyValuePair() (string, string) ComputeTrackDiff(tl *TrackLookup) TrackDiff GetProofType() keybase1.ProofType ProofText() string } type WebProofChainLink struct { GenericChainLink protocol string hostname string proofText string } type SocialProofChainLink struct { GenericChainLink service string username string proofText string // signifies a GENERIC_SOCIAL link from a parameterized proof isGeneric bool } func (w *WebProofChainLink) DisplayPriorityKey() string { return w.protocol } func (w *WebProofChainLink) TableKey() string { if w.protocol == "https" { return "http" } return w.protocol } func (w *WebProofChainLink) GetProofType() keybase1.ProofType { if w.protocol == "dns" { return keybase1.ProofType_DNS } return keybase1.ProofType_GENERIC_WEB_SITE } func (w *WebProofChainLink) ToTrackingStatement(state keybase1.ProofState) (*jsonw.Wrapper, error) { ret := w.BaseToTrackingStatement(state) remoteProofToTrackingStatement(w, ret) return ret, nil } func (w *WebProofChainLink) DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error { return ui.FinishWebProofCheck(m, ExportRemoteProof(w), lcr.Export()) } func (w *WebProofChainLink) Type() string { return "proof" } func (w *WebProofChainLink) insertIntoTable(tab *IdentityTable) { remoteProofInsertIntoTable(w, tab) } func (w *WebProofChainLink) ToDisplayString() string { return w.protocol + "://" + w.hostname } func (w *WebProofChainLink) LastWriterWins() bool { return false } func (w *WebProofChainLink) GetRemoteUsername() string { return "" } func (w *WebProofChainLink) GetHostname() string { return w.hostname } func (w *WebProofChainLink) GetProtocol() string { return w.protocol } func (w *WebProofChainLink) ProofText() string { return w.proofText } func (w *WebProofChainLink) CheckDataJSON() *jsonw.Wrapper { ret := jsonw.NewDictionary() if w.protocol == "dns" { _ = ret.SetKey("protocol", jsonw.NewString(w.protocol)) _ = ret.SetKey("domain", jsonw.NewString(w.hostname)) } else { _ = ret.SetKey("protocol", jsonw.NewString(w.protocol+":")) _ = ret.SetKey("hostname", jsonw.NewString(w.hostname)) } return ret } func (w *WebProofChainLink) ToIDString() string { return w.ToDisplayString() } func (w *WebProofChainLink) ToKeyValuePair() (string, string) { return w.GetProtocol(), w.GetHostname() } func (w *WebProofChainLink) ComputeTrackDiff(tl *TrackLookup) (res TrackDiff) { find := func(list []string) bool { for _, e := range list { if Cicmp(e, w.hostname) { return true } } return false } if find(tl.ids[w.protocol]) { res = TrackDiffNone{} } else if w.protocol == "https" && find(tl.ids["http"]) { res = TrackDiffUpgraded{"http", "https"} } else { res = TrackDiffNew{} } return } func (s *SocialProofChainLink) DisplayPriorityKey() string { return s.TableKey() } func (s *SocialProofChainLink) TableKey() string { return s.service } func (s *SocialProofChainLink) Type() string { return "proof" } func (s *SocialProofChainLink) insertIntoTable(tab *IdentityTable) { remoteProofInsertIntoTable(s, tab) } func (s *SocialProofChainLink) ToDisplayString() string { return s.username + "@" + s.service } func (s *SocialProofChainLink) LastWriterWins() bool { return true } func (s *SocialProofChainLink) GetRemoteUsername() string { return s.username } func (s *SocialProofChainLink) GetHostname() string { return "" } func (s *SocialProofChainLink) GetProtocol() string { return "" } func (s *SocialProofChainLink) ProofText() string { return s.proofText } func (s *SocialProofChainLink) ToIDString() string { return s.ToDisplayString() } func (s *SocialProofChainLink) ToKeyValuePair() (string, string) { return s.service, s.username } func (s *SocialProofChainLink) GetService() string { return s.service } func (s *SocialProofChainLink) ToTrackingStatement(state keybase1.ProofState) (*jsonw.Wrapper, error) { ret := s.BaseToTrackingStatement(state) remoteProofToTrackingStatement(s, ret) return ret, nil } func (s *SocialProofChainLink) ComputeTrackDiff(tl *TrackLookup) TrackDiff { k, v := s.ToKeyValuePair() if list, found := tl.ids[k]; !found || len(list) == 0 { return TrackDiffNew{} } else if expected := list[len(list)-1]; !Cicmp(expected, v) { return TrackDiffClash{observed: v, expected: expected} } return TrackDiffNone{} } func (s *SocialProofChainLink) DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error { return ui.FinishSocialProofCheck(m, ExportRemoteProof(s), lcr.Export()) } func (s *SocialProofChainLink) CheckDataJSON() *jsonw.Wrapper { ret := jsonw.NewDictionary() _ = ret.SetKey("username", jsonw.NewString(s.username)) _ = ret.SetKey("name", jsonw.NewString(s.service)) return ret } func (s *SocialProofChainLink) GetProofType() keybase1.ProofType { if s.isGeneric { return keybase1.ProofType_GENERIC_SOCIAL } return RemoteServiceTypes[s.service] } var ( _ RemoteProofChainLink = (*SocialProofChainLink)(nil) _ RemoteProofChainLink = (*WebProofChainLink)(nil) ) func NewWebProofChainLink(b GenericChainLink, p, h, proofText string) *WebProofChainLink { return &WebProofChainLink{b, p, h, proofText} } func NewSocialProofChainLink(b GenericChainLink, s, u, proofText string) *SocialProofChainLink { _, found := RemoteServiceTypes[s] return &SocialProofChainLink{ GenericChainLink: b, service: s, username: u, proofText: proofText, isGeneric: !found, } } // ========================================================================= // Can be used to either parse a proof `service` JSON block, or a // `remote_key_proof` JSON block in a tracking statement. type ServiceBlock struct { social bool typ string id string proofState keybase1.ProofState proofType keybase1.ProofType } func (sb ServiceBlock) GetProofState() keybase1.ProofState { return sb.proofState } func (sb ServiceBlock) IsSocial() bool { return sb.social } func (sb ServiceBlock) ToIDString() string { if sb.social { return sb.id + "@" + sb.typ } return sb.typ + "://" + sb.id } func (sb ServiceBlock) ToKeyValuePair() (string, string) { return sb.typ, sb.id } func (sb ServiceBlock) LastWriterWins() bool { return sb.social } func (sb ServiceBlock) GetProofType() keybase1.ProofType { return sb.proofType } func ParseServiceBlock(jw *jsonw.Wrapper, pt keybase1.ProofType) (sb *ServiceBlock, err error) { var social bool var typ, id string if prot, e1 := jw.AtKey("protocol").GetString(); e1 == nil { var hostname string jw.AtKey("hostname").GetStringVoid(&hostname, &e1) if e1 == nil { switch prot { case "http:": typ, id = "http", hostname case "https:": typ, id = "https", hostname } } else if domain, e2 := jw.AtKey("domain").GetString(); e2 == nil && prot == "dns" { typ, id = "dns", domain } } else { var e2 error jw.AtKey("name").GetStringVoid(&typ, &e2) jw.AtKey("username").GetStringVoid(&id, &e2) if e2 != nil { id, typ = "", "" } else { social = true } } if len(typ) == 0 { err = fmt.Errorf("Unrecognized Web proof @%s", jw.MarshalToDebug()) } sb = &ServiceBlock{social: social, typ: typ, id: id, proofType: pt} return } // To be used for signatures in a user's signature chain. func ParseWebServiceBinding(base GenericChainLink) (ret RemoteProofChainLink, err error) { jw := base.UnmarshalPayloadJSON().AtKey("body").AtKey("service") sptf := base.unpacked.proofText if jw.IsNil() { ret, err = ParseSelfSigChainLink(base) if err != nil { return nil, err } } else if sb, err := ParseServiceBlock(jw, keybase1.ProofType_NONE); err != nil { err = fmt.Errorf("%s @%s", err, base.ToDebugString()) return nil, err } else if sb.social { ret = NewSocialProofChainLink(base, sb.typ, sb.id, sptf) } else { ret = NewWebProofChainLink(base, sb.typ, sb.id, sptf) } return ret, nil } func remoteProofInsertIntoTable(l RemoteProofChainLink, tab *IdentityTable) { tab.insertLink(l) tab.insertRemoteProof(l) } // // ========================================================================= // ========================================================================= // TrackChainLink type TrackChainLink struct { GenericChainLink whomUsername NormalizedUsername whomUID keybase1.UID untrack *UntrackChainLink local bool tmpExpireTime time.Time // should only be relevant if local is set to true } func (l TrackChainLink) IsRemote() bool { return !l.local } func ParseTrackChainLink(b GenericChainLink) (ret *TrackChainLink, err error) { payload := b.UnmarshalPayloadJSON() var tmp string tmp, err = payload.AtPath("body.track.basics.username").GetString() if err != nil { err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) return } whomUsername := NewNormalizedUsername(tmp) whomUID, err := GetUID(payload.AtPath("body.track.id")) if err != nil { err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) return } ret = &TrackChainLink{b, whomUsername, whomUID, nil, false, time.Time{}} return } func (l *TrackChainLink) Type() string { return "track" } func (l *TrackChainLink) ToDisplayString() string { return l.whomUsername.String() } func (l *TrackChainLink) GetTmpExpireTime() (ret time.Time) { if l.local { ret = l.tmpExpireTime } return ret } func (l *TrackChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(l) tab.tracks[l.whomUsername] = append(tab.tracks[l.whomUsername], l) } type TrackedKey struct { KID keybase1.KID Fingerprint *PGPFingerprint } func trackedKeyFromJSON(jw *jsonw.Wrapper) (TrackedKey, error) { var ret TrackedKey kid, err := GetKID(jw.AtKey("kid")) if err != nil { return TrackedKey{}, err } ret.KID = kid // It's ok if key_fingerprint doesn't exist. But if it does, then include it: fp, err := GetPGPFingerprint(jw.AtKey("key_fingerprint")) if err == nil && fp != nil { ret.Fingerprint = fp } return ret, nil } func (l *TrackChainLink) GetTrackedKeys() ([]TrackedKey, error) { // presumably order is important, so we'll only use the map as a set // to deduplicate keys. set := make(map[keybase1.KID]bool) var res []TrackedKey pgpKeysJSON := l.UnmarshalPayloadJSON().AtPath("body.track.pgp_keys") if !pgpKeysJSON.IsNil() { n, err := pgpKeysJSON.Len() if err != nil { return nil, err } for i := range n { keyJSON := pgpKeysJSON.AtIndex(i) tracked, err := trackedKeyFromJSON(keyJSON) if err != nil { return nil, err } if !set[tracked.KID] { res = append(res, tracked) set[tracked.KID] = true } } } return res, nil } func (l *TrackChainLink) GetEldestKID() (kid keybase1.KID, err error) { keyJSON := l.UnmarshalPayloadJSON().AtPath("body.track.key") if keyJSON.IsNil() { return kid, nil } tracked, err := trackedKeyFromJSON(keyJSON) if err != nil { return kid, err } return tracked.KID, nil } func (l *TrackChainLink) GetTrackedUID() (keybase1.UID, error) { return GetUID(l.UnmarshalPayloadJSON().AtPath("body.track.id")) } func (l *TrackChainLink) GetTrackedUsername() (NormalizedUsername, error) { tmp, err := l.UnmarshalPayloadJSON().AtPath("body.track.basics.username").GetString() if err != nil { return NormalizedUsername(""), fmt.Errorf("no tracked username: %v", err) } return NewNormalizedUsername(tmp), err } func (l *TrackChainLink) IsRevoked() bool { return l.revoked || l.untrack != nil } func (l *TrackChainLink) RemoteKeyProofs() *jsonw.Wrapper { return l.UnmarshalPayloadJSON().AtPath("body.track.remote_proofs") } func (l *TrackChainLink) ToServiceBlocks() (ret []*ServiceBlock) { w := l.RemoteKeyProofs() ln, err := w.Len() if err != nil { return nil } for index := range ln { proof := w.AtIndex(index).AtKey("remote_key_proof") sb := convertTrackedProofToServiceBlock(l.G(), proof, index) if sb != nil { ret = append(ret, sb) } } return ret } // Get the tail of the trackee's sigchain. func (l *TrackChainLink) GetTrackedLinkSeqno() (seqno keybase1.Seqno, err error) { seqnoJSON := l.UnmarshalPayloadJSON().AtPath("body.track.seq_tail.seqno") if seqnoJSON.IsNil() { return seqno, nil } i64, err := seqnoJSON.GetInt64() if err != nil { return seqno, err } return keybase1.Seqno(i64), nil } // convertTrackedProofToServiceBlock will take a JSON stanza from a track statement, and convert it // to a ServiceBlock if it fails some important sanity checks. We check that the JSON stanza is // well-formed, and that it's not for a defunct proof type (like Coinbase). If all succeeds, // we output a service block that can entered into found-versus-tracked comparison logic. // The `index` provided is what index this JSON stanza is in the overall track statement. func convertTrackedProofToServiceBlock(g *GlobalContext, proof *jsonw.Wrapper, index int) (ret *ServiceBlock) { var i, t int var err error i, err = proof.AtKey("state").GetInt() if err != nil { g.Log.Warning("Bad 'state' in track statement: %s", err) return nil } t, err = proof.AtKey("proof_type").GetInt() if err != nil { g.Log.Warning("Bad 'proof_type' in track statement: %s", err) return nil } proofType := keybase1.ProofType(t) if isProofTypeDefunct(g, proofType) { g.Log.Debug("Ignoring now defunct proof type %q at index=%d", proofType, index) return nil } ret, err = ParseServiceBlock(proof.AtKey("check_data_json"), proofType) if err != nil { g.Log.Warning("Bad remote_key_proof.check_data_json: %s", err) return nil } ret.proofState = keybase1.ProofState(i) if ret.proofState != keybase1.ProofState_OK { g.Log.Debug("Including broken proof at index=%d (proof state=%d)", index, ret.proofState) } return ret } func (l *TrackChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) { g.Log.Debug("Post notification for new TrackChainLink") g.NotifyRouter.HandleTrackingChanged(l.whomUID, l.whomUsername, true) } // // ========================================================================= // ========================================================================= // EldestChainLink // type EldestChainLink struct { GenericChainLink kid keybase1.KID device *Device } func ParseEldestChainLink(b GenericChainLink) (ret *EldestChainLink, err error) { var kid keybase1.KID var device *Device payload := b.UnmarshalPayloadJSON() if kid, err = GetKID(payload.AtPath("body.key.kid")); err != nil { err = ChainLinkError{fmt.Sprintf("Bad eldest statement @%s: %s", b.ToDebugString(), err)} return } if jw := payload.AtPath("body.device"); !jw.IsNil() { if device, err = ParseDevice(jw, b.GetCTime()); err != nil { return } } ret = &EldestChainLink{b, kid, device} return } func (s *EldestChainLink) GetDelegatedKid() keybase1.KID { return s.kid } func (s *EldestChainLink) GetRole() KeyRole { return DLGSibkey } func (s *EldestChainLink) Type() string { return string(DelegationTypeEldest) } func (s *EldestChainLink) ToDisplayString() string { return s.kid.String() } func (s *EldestChainLink) GetDevice() *Device { return s.device } func (s *EldestChainLink) GetPGPFullHash() string { return s.extractPGPFullHash("key") } func (s *EldestChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(s) } // // ========================================================================= // ========================================================================= // SibkeyChainLink // type SibkeyChainLink struct { GenericChainLink kid keybase1.KID device *Device reverseSig string } func ParseSibkeyChainLink(b GenericChainLink) (ret *SibkeyChainLink, err error) { var kid keybase1.KID var device *Device payload := b.UnmarshalPayloadJSON() if kid, err = GetKID(payload.AtPath("body.sibkey.kid")); err != nil { err = ChainLinkError{fmt.Sprintf("Bad sibkey statement @%s: %s", b.ToDebugString(), err)} return } var rs string if rs, err = payload.AtPath("body.sibkey.reverse_sig").GetString(); err != nil { err = ChainLinkError{fmt.Sprintf("Missing reverse_sig in sibkey delegation: @%s: %s", b.ToDebugString(), err)} return } if jw := payload.AtPath("body.device"); !jw.IsNil() { if device, err = ParseDevice(jw, b.GetCTime()); err != nil { return } } ret = &SibkeyChainLink{b, kid, device, rs} return } func (s *SibkeyChainLink) GetDelegatedKid() keybase1.KID { return s.kid } func (s *SibkeyChainLink) GetRole() KeyRole { return DLGSibkey } func (s *SibkeyChainLink) Type() string { return string(DelegationTypeSibkey) } func (s *SibkeyChainLink) ToDisplayString() string { return s.kid.String() } func (s *SibkeyChainLink) GetDevice() *Device { return s.device } func (s *SibkeyChainLink) GetPGPFullHash() string { return s.extractPGPFullHash("sibkey") } func (s *SibkeyChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(s) } //------------------------------------- func makeDeepCopy(w *jsonw.Wrapper) (ret *jsonw.Wrapper, err error) { var b []byte if b, err = w.Marshal(); err != nil { return nil, err } return jsonw.Unmarshal(b) } //------------------------------------- // VerifyReverseSig checks a SibkeyChainLink's reverse signature using the ComputedKeyFamily provided. func (s *SibkeyChainLink) VerifyReverseSig(ckf ComputedKeyFamily) (err error) { var key GenericKey if key, err = ckf.FindKeyWithKIDUnsafe(s.GetDelegatedKid()); err != nil { return err } return VerifyReverseSig(s.G(), key, "body.sibkey.reverse_sig", s.UnmarshalPayloadJSON(), s.reverseSig) } // // ========================================================================= // SubkeyChainLink type SubkeyChainLink struct { GenericChainLink kid keybase1.KID parentKid keybase1.KID } func ParseSubkeyChainLink(b GenericChainLink) (ret *SubkeyChainLink, err error) { var kid, pkid keybase1.KID payload := b.UnmarshalPayloadJSON() if kid, err = GetKID(payload.AtPath("body.subkey.kid")); err != nil { err = ChainLinkError{fmt.Sprintf("Can't get KID for subkey @%s: %s", b.ToDebugString(), err)} } else if pkid, err = GetKID(payload.AtPath("body.subkey.parent_kid")); err != nil { err = ChainLinkError{fmt.Sprintf("Can't get parent_kid for subkey @%s: %s", b.ToDebugString(), err)} } else { ret = &SubkeyChainLink{b, kid, pkid} } return } func (s *SubkeyChainLink) Type() string { return string(DelegationTypeSubkey) } func (s *SubkeyChainLink) ToDisplayString() string { return s.kid.String() } func (s *SubkeyChainLink) GetRole() KeyRole { return DLGSubkey } func (s *SubkeyChainLink) GetDelegatedKid() keybase1.KID { return s.kid } func (s *SubkeyChainLink) GetParentKid() keybase1.KID { return s.parentKid } func (s *SubkeyChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(s) } // // ========================================================================= // ========================================================================= // PerUserKeyChainLink type PerUserKeyChainLink struct { GenericChainLink // KID of the signing key derived from the per-user-secret. sigKID keybase1.KID // KID of the encryption key derived from the per-user-secret. encKID keybase1.KID generation keybase1.PerUserKeyGeneration reverseSig string } func ParsePerUserKeyChainLink(b GenericChainLink) (ret *PerUserKeyChainLink, err error) { var sigKID, encKID keybase1.KID var g int var reverseSig string section := b.UnmarshalPayloadJSON().AtPath("body.per_user_key") if sigKID, err = GetKID(section.AtKey("signing_kid")); err != nil { err = ChainLinkError{fmt.Sprintf("Can't get signing KID for per_user_secret: @%s: %s", b.ToDebugString(), err)} } else if encKID, err = GetKID(section.AtKey("encryption_kid")); err != nil { err = ChainLinkError{fmt.Sprintf("Can't get encryption KID for per_user_secret: @%s: %s", b.ToDebugString(), err)} } else if g, err = section.AtKey("generation").GetInt(); err != nil { err = ChainLinkError{fmt.Sprintf("Can't get generation for per_user_secret @%s: %s", b.ToDebugString(), err)} } else if reverseSig, err = section.AtKey("reverse_sig").GetString(); err != nil { err = ChainLinkError{fmt.Sprintf("Missing reverse_sig in per-user-key section: @%s: %s", b.ToDebugString(), err)} } else { ret = &PerUserKeyChainLink{b, sigKID, encKID, keybase1.PerUserKeyGeneration(g), reverseSig} } return ret, err } func (s *PerUserKeyChainLink) Type() string { return string(LinkTypePerUserKey) } func (s *PerUserKeyChainLink) ToDisplayString() string { return s.sigKID.String() + " + " + s.encKID.String() } // Don't consider per-user-keys as normal delegations. Because they have // multiple kids and initially can't delegate further. They are handled // separately by the sigchain loader. func (s *PerUserKeyChainLink) GetRole() KeyRole { return DLGNone } func (s *PerUserKeyChainLink) GetDelegatedKid() (res keybase1.KID) { return } func (s *PerUserKeyChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(s) } func (s *PerUserKeyChainLink) ToPerUserKey() keybase1.PerUserKey { return keybase1.PerUserKey{ Gen: int(s.generation), Seqno: s.GetSeqno(), SigKID: s.sigKID, EncKID: s.encKID, SignedByKID: s.GetKID(), } } //------------------------------------- // VerifyReverseSig checks a SibkeyChainLink's reverse signature using the ComputedKeyFamily provided. func (s *PerUserKeyChainLink) VerifyReverseSig(_ ComputedKeyFamily) (err error) { key, err := ImportNaclSigningKeyPairFromHex(s.sigKID.String()) if err != nil { return fmt.Errorf("Invalid per-user signing KID: %s", s.sigKID) } return VerifyReverseSig(s.G(), key, "body.per_user_key.reverse_sig", s.UnmarshalPayloadJSON(), s.reverseSig) } // // ========================================================================= // PGPUpdateChainLink // // PGPUpdateChainLink represents a chain link which marks a new version of a // PGP key as current. The KID and a new full hash are included in the // pgp_update section of the body. type PGPUpdateChainLink struct { GenericChainLink kid keybase1.KID } // ParsePGPUpdateChainLink creates a PGPUpdateChainLink from a GenericChainLink // and verifies that its pgp_update section contains a KID and full_hash func ParsePGPUpdateChainLink(b GenericChainLink) (ret *PGPUpdateChainLink, err error) { var kid keybase1.KID pgpUpdate := b.UnmarshalPayloadJSON().AtPath("body.pgp_update") if pgpUpdate.IsNil() { err = ChainLinkError{fmt.Sprintf("missing pgp_update section @%s", b.ToDebugString())} return } if kid, err = GetKID(pgpUpdate.AtKey("kid")); err != nil { err = ChainLinkError{fmt.Sprintf("Missing kid @%s: %s", b.ToDebugString(), err)} return } ret = &PGPUpdateChainLink{b, kid} if fh := ret.GetPGPFullHash(); fh == "" { err = ChainLinkError{fmt.Sprintf("Missing full_hash @%s", b.ToDebugString())} ret = nil return } return } func (l *PGPUpdateChainLink) Type() string { return string(DelegationTypePGPUpdate) } func (l *PGPUpdateChainLink) ToDisplayString() string { return l.kid.String() } func (l *PGPUpdateChainLink) GetPGPFullHash() string { return l.extractPGPFullHash("pgp_update") } func (l *PGPUpdateChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(l) } // // ========================================================================= // type DeviceChainLink struct { GenericChainLink device *Device } func ParseDeviceChainLink(b GenericChainLink) (ret *DeviceChainLink, err error) { dobj, err := ParseDevice(b.UnmarshalPayloadJSON().AtPath("body.device"), b.GetCTime()) if err != nil { return nil, err } ret = &DeviceChainLink{b, dobj} return ret, nil } func (s *DeviceChainLink) GetDevice() *Device { return s.device } func (s *DeviceChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(s) } // // ========================================================================= // WalletStellarChainLink type WalletStellarChainLink struct { GenericChainLink addressKID keybase1.KID reverseSig string address string network string name string } func ParseWalletStellarChainLink(b GenericChainLink) (ret *WalletStellarChainLink, err error) { ret = &WalletStellarChainLink{GenericChainLink: b} mkErr := func(format string, args ...any) error { return ChainLinkError{fmt.Sprintf(format, args...) + fmt.Sprintf(" @%s", b.ToDebugString())} } bodyW := b.UnmarshalPayloadJSON() walletSection := bodyW.AtPath("body.wallet") walletKeySection := bodyW.AtPath("body.wallet_key") ret.addressKID, err = GetKID(walletKeySection.AtKey("kid")) if err != nil { return nil, mkErr("Can't get address KID: %v", err) } ret.reverseSig, err = walletKeySection.AtKey("reverse_sig").GetString() if err != nil { return nil, mkErr("Missing reverse_sig: %v", err) } ret.address, err = walletSection.AtKey("address").GetString() if err != nil { return nil, mkErr("Can't get address: %v", err) } ret.network, err = walletSection.AtKey("network").GetString() if err != nil { return nil, mkErr("Can't get address network: %v", err) } nameOption := walletSection.AtKey("name") if !nameOption.IsNil() { ret.name, err = nameOption.GetString() if err != nil { return nil, mkErr("Can't get account name: %v", err) } } // Check the network and that the keys match. if ret.network != string(WalletNetworkStellar) { return nil, mkErr("Unsupported wallet network '%v'", ret.network) } accountKey, err := MakeNaclSigningKeyPairFromStellarAccountID(stellar1.AccountID(ret.address)) if err != nil { return nil, mkErr("Invalid stellar account address: '%v'", ret.address) } if !ret.addressKID.Equal(accountKey.GetKID()) { return nil, mkErr("Mismatched wallet keys: '%v' <-/-> '%v", ret.addressKID, ret.address) } return ret, nil } func (s *WalletStellarChainLink) Type() string { return string(LinkTypeWalletStellar) } func (s *WalletStellarChainLink) ToDisplayString() string { return fmt.Sprintf("%v %v %v %v", s.network, s.name, s.address, s.addressKID.String()) } func (s *WalletStellarChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(s) if tab.stellar == nil || tab.stellar.GetSeqno() <= s.GetSeqno() { tab.stellar = s } } // VerifyReverseSig checks a SibkeyChainLink's reverse signature using the ComputedKeyFamily provided. func (s *WalletStellarChainLink) VerifyReverseSig(_ ComputedKeyFamily) (err error) { key, err := ImportNaclSigningKeyPairFromHex(s.addressKID.String()) if err != nil { return fmt.Errorf("Invalid wallet reverse signing KID: %s", s.addressKID) } return VerifyReverseSig(s.G(), key, "body.wallet_key.reverse_sig", s.UnmarshalPayloadJSON(), s.reverseSig) } func (s *WalletStellarChainLink) Display(m MetaContext, ui IdentifyUI) error { // First get an up to date user card, since hiding the Stellar address affects it. card, err := UserCard(m, s.GetUID(), true) if err != nil { m.Info("Could not get usercard, so skipping displaying stellar chain link: %s.", err) return nil } selfUID := m.G().Env.GetUID() if selfUID.IsNil() { m.G().Log.Warning("Could not get self UID for api") } if card.StellarHidden && !selfUID.Equal(s.GetUID()) { return nil } return ui.DisplayStellarAccount(m, keybase1.StellarAccount{ AccountID: s.address, FederationAddress: fmt.Sprintf("%s*keybase.io", s.GetUsername()), SigID: s.GetSigID(), Hidden: card.StellarHidden, }) } // // ========================================================================= // UntrackChainLink type UntrackChainLink struct { GenericChainLink whomUsername NormalizedUsername whomUID keybase1.UID } func ParseUntrackChainLink(b GenericChainLink) (ret *UntrackChainLink, err error) { var tmp string payload := b.UnmarshalPayloadJSON() tmp, err = payload.AtPath("body.untrack.basics.username").GetString() if err != nil { err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) return } whomUsername := NewNormalizedUsername(tmp) whomUID, err := GetUID(payload.AtPath("body.untrack.id")) if err != nil { err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) return } ret = &UntrackChainLink{b, whomUsername, whomUID} return } func (u *UntrackChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(u) if list, found := tab.tracks[u.whomUsername]; !found { u.G().Log.Debug("| Useless untrack of %s; no previous tracking statement found", u.whomUsername) } else { for _, obj := range list { obj.untrack = u } } } func (u *UntrackChainLink) ToDisplayString() string { return u.whomUsername.String() } func (u *UntrackChainLink) Type() string { return "untrack" } func (u *UntrackChainLink) IsRevocationIsh() bool { return true } func (u *UntrackChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) { g.Log.Debug("Post notification for new UntrackChainLink") g.NotifyRouter.HandleTrackingChanged(u.whomUID, u.whomUsername, false) } // // ========================================================================= // ========================================================================= // CryptocurrencyChainLink type CryptocurrencyChainLink struct { GenericChainLink pkhash []byte address string typ CryptocurrencyType } func (c CryptocurrencyChainLink) GetAddress() string { return c.address } func ParseCryptocurrencyChainLink(b GenericChainLink) ( cl *CryptocurrencyChainLink, err error, ) { jw := b.UnmarshalPayloadJSON().AtPath("body.cryptocurrency") var styp, addr string var pkhash []byte jw.AtKey("type").GetStringVoid(&styp, &err) jw.AtKey("address").GetStringVoid(&addr, &err) if err != nil { return } var typ CryptocurrencyType typ, pkhash, err = CryptocurrencyParseAndCheck(addr) if err != nil { err = fmt.Errorf("At signature %s: %s", b.ToDebugString(), err) return } if styp != typ.String() { err = fmt.Errorf("Got %q type but wanted %q at: %s", styp, typ.String(), b.ToDebugString()) return } cl = &CryptocurrencyChainLink{b, pkhash, addr, typ} return } func (c *CryptocurrencyChainLink) Type() string { return "cryptocurrency" } func (c *CryptocurrencyChainLink) ToDisplayString() string { return c.address } func (c *CryptocurrencyChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(c) tab.cryptocurrency = append(tab.cryptocurrency, c) } func (c CryptocurrencyChainLink) Display(m MetaContext, ui IdentifyUI) error { return ui.DisplayCryptocurrency(m, c.Export()) } // // ========================================================================= // ========================================================================= // RevokeChainLink type RevokeChainLink struct { GenericChainLink device *Device } func ParseRevokeChainLink(b GenericChainLink) (ret *RevokeChainLink, err error) { var device *Device if jw := b.UnmarshalPayloadJSON().AtPath("body.device"); !jw.IsNil() { if device, err = ParseDevice(jw, b.GetCTime()); err != nil { return } } ret = &RevokeChainLink{b, device} return } func (r *RevokeChainLink) Type() string { return "revoke" } func (r *RevokeChainLink) ToDisplayString() string { v := r.GetRevocations() list := make([]string, len(v)) for i, s := range v { list[i] = s.String() } return strings.Join(list, ",") } func (r *RevokeChainLink) IsRevocationIsh() bool { return true } func (r *RevokeChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(r) } func (r *RevokeChainLink) GetDevice() *Device { return r.device } // // ========================================================================= // ========================================================================= // SelfSigChainLink type SelfSigChainLink struct { GenericChainLink device *Device } func (s *SelfSigChainLink) Type() string { return "self" } func (s *SelfSigChainLink) ToDisplayString() string { return s.unpacked.username } func (s *SelfSigChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(s) } func (s *SelfSigChainLink) DisplayPriorityKey() string { return s.TableKey() } func (s *SelfSigChainLink) TableKey() string { return "keybase" } func (s *SelfSigChainLink) LastWriterWins() bool { return true } func (s *SelfSigChainLink) GetRemoteUsername() string { return s.GetUsername() } func (s *SelfSigChainLink) GetHostname() string { return "" } func (s *SelfSigChainLink) GetProtocol() string { return "" } func (s *SelfSigChainLink) ProofText() string { return "" } func (s *SelfSigChainLink) GetPGPFullHash() string { return s.extractPGPFullHash("key") } func (s *SelfSigChainLink) DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error { return nil } func (s *SelfSigChainLink) CheckDataJSON() *jsonw.Wrapper { return nil } func (s *SelfSigChainLink) ToTrackingStatement(keybase1.ProofState) (*jsonw.Wrapper, error) { return nil, nil } func (s *SelfSigChainLink) ToIDString() string { return s.GetUsername() } func (s *SelfSigChainLink) ToKeyValuePair() (string, string) { return s.TableKey(), s.GetUsername() } func (s *SelfSigChainLink) ComputeTrackDiff(tl *TrackLookup) TrackDiff { return nil } func (s *SelfSigChainLink) GetProofType() keybase1.ProofType { return keybase1.ProofType_KEYBASE } func (s *SelfSigChainLink) ParseDevice() (err error) { if jw := s.UnmarshalPayloadJSON().AtPath("body.device"); !jw.IsNil() { s.device, err = ParseDevice(jw, s.GetCTime()) } return err } func (s *SelfSigChainLink) GetDevice() *Device { return s.device } func ParseSelfSigChainLink(base GenericChainLink) (ret *SelfSigChainLink, err error) { ret = &SelfSigChainLink{base, nil} if err = ret.ParseDevice(); err != nil { ret = nil } return } // // ========================================================================= // ========================================================================= type IdentityTable struct { Contextified sigChain *SigChain revocations map[keybase1.SigIDMapKey]bool links map[keybase1.SigIDMapKey]TypedChainLink remoteProofLinks *RemoteProofLinks tracks map[NormalizedUsername][]*TrackChainLink Order []TypedChainLink sigHints *SigHints cryptocurrency []*CryptocurrencyChainLink stellar *WalletStellarChainLink checkResult *CheckResult eldest keybase1.KID hasStubs bool } func (idt *IdentityTable) GetActiveProofsFor(st ServiceType) (ret []RemoteProofChainLink) { return idt.remoteProofLinks.ForService(st) } func (idt *IdentityTable) GetTrackMap() map[NormalizedUsername][]*TrackChainLink { return idt.tracks } func (idt *IdentityTable) HasStubs() bool { return idt.hasStubs } func (idt *IdentityTable) insertLink(l TypedChainLink) { idt.links[l.GetSigID().ToMapKey()] = l idt.Order = append(idt.Order, l) for _, rev := range l.GetRevocations() { idt.revocations[rev.ToMapKey()] = true if targ, found := idt.links[rev.ToMapKey()]; !found { idt.G().Log.Warning("Can't revoke signature %s @%s", rev, l.ToDebugString()) } else { targ.markRevoked(l) } } } func (idt *IdentityTable) MarkCheckResult(err ProofError) { idt.checkResult = NewNowCheckResult(idt.G(), err) } func NewTypedChainLink(cl *ChainLink) (ret TypedChainLink, w Warning) { if ret = cl.typed; ret != nil { return } base := GenericChainLink{cl} s, err := cl.UnmarshalPayloadJSON().AtKey("body").AtKey("type").GetString() if len(s) == 0 || err != nil { err = fmt.Errorf("No type in signature @%s", base.ToDebugString()) } else { switch s { case string(DelegationTypeEldest): ret, err = ParseEldestChainLink(base) case "web_service_binding": ret, err = ParseWebServiceBinding(base) case "track": ret, err = ParseTrackChainLink(base) case "untrack": ret, err = ParseUntrackChainLink(base) case "cryptocurrency": ret, err = ParseCryptocurrencyChainLink(base) case "revoke": ret, err = ParseRevokeChainLink(base) case string(DelegationTypeSibkey): ret, err = ParseSibkeyChainLink(base) case string(DelegationTypeSubkey): ret, err = ParseSubkeyChainLink(base) case string(DelegationTypePGPUpdate): ret, err = ParsePGPUpdateChainLink(base) case "per_user_key": ret, err = ParsePerUserKeyChainLink(base) case "device": ret, err = ParseDeviceChainLink(base) case string(LinkTypeWalletStellar): ret, err = ParseWalletStellarChainLink(base) case string(LinkTypeWotVouch): ret, err = ParseWotVouch(base) case string(LinkTypeWotReact): ret, err = ParseWotReact(base) default: err = fmt.Errorf("Unknown signature type %s @%s", s, base.ToDebugString()) } } if err != nil { w = ErrorToWarning(err) ret = &base } cl.typed = ret // Basically we never fail, since worse comes to worse, we treat // unknown signatures as "generic" and can still display them return ret, w } func NewIdentityTable(m MetaContext, eldest keybase1.KID, sc *SigChain, h *SigHints) (*IdentityTable, error) { ret := &IdentityTable{ Contextified: NewContextified(m.G()), sigChain: sc, revocations: make(map[keybase1.SigIDMapKey]bool), links: make(map[keybase1.SigIDMapKey]TypedChainLink), remoteProofLinks: NewRemoteProofLinks(m.G()), tracks: make(map[NormalizedUsername][]*TrackChainLink), sigHints: h, eldest: eldest, } err := ret.populate(m) return ret, err } func (idt *IdentityTable) populate(m MetaContext) (err error) { defer m.Trace("IdentityTable#populate", &err)() var links []*ChainLink if links, err = idt.sigChain.GetCurrentSubchain(m, idt.eldest); err != nil { return err } for _, link := range links { isBad, reason, err := link.IsBad() if err != nil { return err } if isBad { m.Debug("Ignoring bad chain link with linkID %s: %s", link.LinkID(), reason) continue } if link.IsStubbed() { idt.hasStubs = true continue } tcl, w := NewTypedChainLink(link) if w != nil { w.Warn(idt.G()) } // If it's an unknown link type, then it's OK to ignore it if tcl == nil { continue } tcl.insertIntoTable(idt) if link.isOwnNewLinkFromServer { link.isOwnNewLinkFromServer = false tcl.DoOwnNewLinkFromServerNotifications(idt.G()) } } return nil } func isProofTypeDefunct(g *GlobalContext, typ keybase1.ProofType) bool { switch typ { case keybase1.ProofType_COINBASE: return true default: return false } } func (idt *IdentityTable) insertRemoteProof(link RemoteProofChainLink) { if isProofTypeDefunct(idt.G(), link.GetProofType()) { idt.G().Log.Debug("Ignoring now-defunct proof: %s", link.ToDebugString()) return } // note that the links in the identity table have no ProofError state. idt.remoteProofLinks.Insert(link, nil) } func (idt *IdentityTable) VerifySelfSig(nun NormalizedUsername, uid keybase1.UID) bool { list := idt.Order ln := len(list) for i := ln - 1; i >= 0; i-- { link := list[i] if link.IsRevoked() { continue } if NewNormalizedUsername(link.GetUsername()).Eq(nun) && link.GetUID().Equal(uid) { idt.G().Log.Debug("| Found self-signature for %s @%s", string(nun), link.ToDebugString()) return true } } return false } func (idt *IdentityTable) GetTrackList() (ret []*TrackChainLink) { for _, v := range idt.tracks { for i := len(v) - 1; i >= 0; i-- { link := v[i] if !link.IsRevoked() { ret = append(ret, link) break } } } return } func (idt *IdentityTable) TrackChainLinkFor(username NormalizedUsername, uid keybase1.UID) (*TrackChainLink, error) { list, found := idt.tracks[username] if !found { return nil, nil } for i := len(list) - 1; i >= 0; i-- { link := list[i] if link.IsRevoked() { // noop; continue on! continue } uid2, err := link.GetTrackedUID() if err != nil { return nil, fmt.Errorf("Bad tracking statement for %s: %s", username, err) } if uid.NotEqual(uid2) { return nil, fmt.Errorf("Bad UID in tracking statement for %s: %s != %s", username, uid, uid2) } return link, nil } return nil, nil } func (idt *IdentityTable) ActiveCryptocurrency(family CryptocurrencyFamily) *CryptocurrencyChainLink { tab := idt.cryptocurrency for i := len(tab) - 1; i >= 0; i-- { link := tab[i] if link.typ.ToCryptocurrencyFamily() == family { if link.IsRevoked() { return nil } return link } } return nil } func (idt *IdentityTable) AllActiveCryptocurrency() []CryptocurrencyChainLink { var ret []CryptocurrencyChainLink for _, link := range idt.cryptocurrency { if !link.IsRevoked() { ret = append(ret, *link) } } return ret } func (idt *IdentityTable) HasActiveCryptocurrencyFamily(family CryptocurrencyFamily) bool { for _, link := range idt.AllActiveCryptocurrency() { if link.typ.ToCryptocurrencyFamily() == family { return true } } return false } func (idt *IdentityTable) GetRevokedCryptocurrencyForTesting() []CryptocurrencyChainLink { ret := []CryptocurrencyChainLink{} for _, link := range idt.cryptocurrency { if link.IsRevoked() { ret = append(ret, *link) } } return ret } // Return the active stellar public address for a user. // Returns nil if there is none or it has not been loaded. func (idt *IdentityTable) StellarAccountID() *stellar1.AccountID { // Return the account ID of the latest link with the network set to stellar. if idt.stellar == nil { return nil } link := idt.stellar if link.network == string(WalletNetworkStellar) { // Something should have already validated link.address as a stellar account ID. tmp := stellar1.AccountID(link.address) return &tmp } return nil } func (idt *IdentityTable) Len() int { return len(idt.Order) } type CheckCompletedListener interface { CCLCheckCompleted(lcr *LinkCheckResult) } type IdentifyTableMode int const ( IdentifyTableModePassive IdentifyTableMode = iota IdentifyTableModeActive IdentifyTableMode = iota ) // requestedAt is when the identify this table walk belongs to was asked for. It // bounds which newly cached remote proof results a forced check may reuse. func (idt *IdentityTable) Identify(m MetaContext, is IdentifyState, forceRemoteCheck bool, requestedAt time.Time, ui IdentifyUI, ccl CheckCompletedListener, itm IdentifyTableMode) error { errs := make(chan error, len(is.res.ProofChecks)) for _, lcr := range is.res.ProofChecks { go func(l *LinkCheckResult) { errs <- idt.identifyActiveProof(m, l, is, forceRemoteCheck, requestedAt, ui, ccl, itm) }(lcr) } allAcc := idt.AllActiveCryptocurrency() for _, acc := range allAcc { if err := acc.Display(m, ui); err != nil { return err } } if stellar := idt.stellar; stellar != nil { if err := stellar.Display(m, ui); err != nil { return err } } for i := 0; i < len(is.res.ProofChecks); i++ { err := <-errs if err != nil { return err } } return nil } // ========================================================================= func (idt *IdentityTable) identifyActiveProof(m MetaContext, lcr *LinkCheckResult, is IdentifyState, forceRemoteCheck bool, requestedAt time.Time, ui IdentifyUI, ccl CheckCompletedListener, itm IdentifyTableMode) error { idt.proofRemoteCheck(m, is.HasPreviousTrack(), forceRemoteCheck, requestedAt, lcr, itm) if ccl != nil { ccl.CCLCheckCompleted(lcr) } return lcr.link.DisplayCheck(m, ui, *lcr) } type LinkCheckResult struct { hint *SigHint // The client checker fills this in with any knowledge it has about hint // metadata if it is able to derive it without server help. This value is // preferred to the plain old server-trust `hint` verifiedHint *SigHint cached *CheckResult err ProofError snoozedErr ProofError diff TrackDiff remoteDiff TrackDiff link RemoteProofChainLink trackedProofState keybase1.ProofState tmpTrackedProofState keybase1.ProofState tmpTrackExpireTime time.Time position int torWarning bool proofCacheRequestKey *proofCacheRequestKey } func (l LinkCheckResult) GetDiff() TrackDiff { return l.diff } func (l LinkCheckResult) GetError() error { return l.err } func (l LinkCheckResult) GetProofError() ProofError { return l.err } func (l LinkCheckResult) GetHint() *SigHint { if l.verifiedHint != nil { return l.verifiedHint } return l.hint } func (l LinkCheckResult) GetCached() *CheckResult { return l.cached } func (l LinkCheckResult) GetPosition() int { return l.position } func (l LinkCheckResult) GetTorWarning() bool { return l.torWarning } func (l LinkCheckResult) GetLink() RemoteProofChainLink { return l.link } func (l LinkCheckResult) GetRemoteDiff() TrackDiff { return l.remoteDiff } // ComputeRemoteDiff takes as input three tracking results: the permanent track, // the local temporary track, and the one it observed remotely. It favors the // permanent track but will roll back to the temporary track if needs be. func (idt *IdentityTable) ComputeRemoteDiff(tracked, trackedTmp, observed keybase1.ProofState) (ret TrackDiff) { idt.G().Log.Debug("+ ComputeRemoteDiff(%v,%v,%v)", tracked, trackedTmp, observed) if observed == tracked { ret = TrackDiffNone{} } else if observed == trackedTmp { ret = TrackDiffNoneViaTemporary{} } else if observed == keybase1.ProofState_OK { ret = TrackDiffRemoteWorking{tracked} } else if tracked == keybase1.ProofState_OK { ret = TrackDiffRemoteFail{observed} } else { ret = TrackDiffRemoteChanged{tracked, observed} } idt.G().Log.Debug("- ComputeRemoteDiff(%v,%v,%v) -> (%+v,(%T))", tracked, trackedTmp, observed, ret, ret) return ret } func (idt *IdentityTable) proofRemoteCheck(m MetaContext, hasPreviousTrack, forceRemoteCheck bool, requestedAt time.Time, res *LinkCheckResult, itm IdentifyTableMode) { p := res.link m.Debug("+ RemoteCheckProof %s", p.ToDebugString()) doCache := false checkCompleted := false pvlHashUsed := keybase1.MerkleStoreKitHash("") sid := p.GetSigID() defer func() { if hasPreviousTrack { observedProofState := ProofErrorToState(res.err) res.remoteDiff = idt.ComputeRemoteDiff(res.trackedProofState, res.tmpTrackedProofState, observedProofState) // If the remote diff only worked out because of the temporary track, then // also update the local diff (i.e., the difference between what we tracked // and what Keybase is saying) accordingly. if _, ok := res.remoteDiff.(TrackDiffNoneViaTemporary); ok { res.diff = res.remoteDiff } } if doCache { // Only make a result eligible for request coalescing when the check // returned normally and its caller's context is still live. if !checkCompleted || m.Ctx().Err() != nil { res.proofCacheRequestKey = nil } m.Debug("| Caching results under key=%s pvlHash=%s", sid, pvlHashUsed) if cacheErr := idt.G().ProofCache.Put(sid, res, pvlHashUsed); cacheErr != nil { m.Warning("proof cache put error: %s", cacheErr) } } m.Debug("- RemoteCheckProof %s", p.ToDebugString()) }() pvlSource := idt.G().GetPvlSource() if pvlSource == nil { res.err = NewProofError(keybase1.ProofStatus_MISSING_PVL, "no pvl source for proof verification") return } pvlU, err := pvlSource.GetLatestEntry(m) if err != nil { res.err = NewProofError(keybase1.ProofStatus_MISSING_PVL, "error getting pvl: %s", err) return } pvlHashUsed = pvlU.Hash res.hint = idt.sigHints.Lookup(sid) if res.hint == nil { res.err = NewProofError(keybase1.ProofStatus_NO_HINT, "No server-given hint for sig=%s", sid) return } var pc ProofChecker // Call the Global context's version of what a proof checker is. We might want to stub it out // for the purposes of testing. pc, res.err = MakeProofChecker(m, m.G().GetProofServices(), p) if res.err != nil || pc == nil { return } if m.G().Env.GetTorMode().Enabled() { if e := pc.GetTorError(); e != nil { res.torWarning = true } } // ProofCheckerModeActive or Passive mainly decides whether we need to reach out to // self-hosted services. We want to avoid so doing when the user is acting passively // (such as when receiving a message). pcm := ProofCheckerModePassive if (hasPreviousTrack && res.trackedProofState != keybase1.ProofState_NONE && res.trackedProofState != keybase1.ProofState_UNCHECKED) || itm == IdentifyTableModeActive { pcm = ProofCheckerModeActive } var hint SigHint if res.hint != nil { hint = *res.hint } requestKey := proofCacheRequestKey{ mode: pcm, apiURL: hint.GetAPIURL(), checkText: hint.GetCheckText(), } var cached *CheckResult if forceRemoteCheck { cached = m.G().ProofCache.getForRequest(sid, pvlU.Hash, requestedAt, requestKey) m.Debug("| Proof request cache lookup for %s: %+v", sid, cached) } else { cached = m.G().ProofCache.Get(sid, pvlU.Hash) m.Debug("| Proof cache lookup for %s: %+v", sid, cached) // Preserve the cached result for the soft-error fallback below even when // it is too old for the normal early return. res.cached = cached } if cached != nil && (forceRemoteCheck || cached.Freshness() == keybase1.CheckResultFreshness_FRESH) { res.err = cached.Status res.verifiedHint = cached.VerifiedHint if forceRemoteCheck { m.Debug("| Early exit after proofCache hit produced during this request for %s", sid) } else { m.Debug("| Early exit after proofCache hit for %s", sid) } return } // From this point on in the function, we'll be putting our results into // cache (in the defer above). doCache = true res.proofCacheRequestKey = &requestKey res.verifiedHint, res.err = pc.CheckStatus(m, hint, pcm, pvlU) checkCompleted = true // If no error than all good if res.err == nil { return } // If the error was soft, and we had a cached successful result that wasn't rancid, // then it's OK to stifle the error message for now. We just have to be certain // not to cache it. if ProofErrorIsSoft(res.err) && res.cached != nil && res.cached.Status == nil && res.cached.Freshness() != keybase1.CheckResultFreshness_RANCID { m.Debug("| Got soft error (%s) but returning success (last seen at %s)", res.err.Error(), res.cached.Time) res.snoozedErr = res.err res.err = nil doCache = false return } m.Debug("| Check status (%s) failed with error: %s", p.ToDebugString(), res.err.Error()) } // VerifyReverseSig checks reverse signature using the key provided. // does not modify `payload`. // `path` is the path to the reverse sig spot to null before checking. func VerifyReverseSig(g *GlobalContext, key GenericKey, path string, payload *jsonw.Wrapper, reverseSig string) (err error) { var p1, p2 []byte if p1, _, err = key.VerifyStringAndExtract(g.Log, reverseSig); err != nil { err = ReverseSigError{fmt.Sprintf("Failed to verify/extract sig: %s", err)} return err } if p1, err = jsonw.Canonicalize(p1); err != nil { err = ReverseSigError{fmt.Sprintf("Failed to canonicalize json: %s", err)} return err } // Make a deep copy. It's dangerous to try to mutate this thing // since other goroutines might be accessing it at the same time. var jsonCopy *jsonw.Wrapper if jsonCopy, err = makeDeepCopy(payload); err != nil { err = ReverseSigError{fmt.Sprintf("Failed to copy payload json: %s", err)} return err } err = jsonCopy.SetValueAtPath(path, jsonw.NewNil()) if err != nil { return err } if p2, err = jsonCopy.Marshal(); err != nil { err = ReverseSigError{fmt.Sprintf("Can't remarshal JSON statement: %s", err)} return err } eq := FastByteArrayEq(p1, p2) if !eq { err = ReverseSigError{fmt.Sprintf("JSON mismatch: %s != %s", string(p1), string(p2))} return err } return nil }