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 "crypto/hmac" 8 "crypto/sha256" 9 "encoding/hex" 10 "encoding/json" 11 "fmt" 12 "strings" 13 "time" 14 15 keybase1 "github.com/keybase/client/go/protocol/keybase1" 16 stellar1 "github.com/keybase/client/go/protocol/stellar1" 17 jsonw "github.com/keybase/go-jsonw" 18) 19 20type TypedChainLink interface { 21 GetRevocations() []keybase1.SigID 22 GetRevokeKids() []keybase1.KID 23 insertIntoTable(tab *IdentityTable) 24 GetSigID() keybase1.SigID 25 GetArmoredSig() string 26 markRevoked(l TypedChainLink) 27 ToDebugString() string 28 Type() string 29 ToDisplayString() string 30 IsRevocationIsh() bool 31 IsRevoked() bool 32 IsDirectlyRevoked() bool 33 GetRole() KeyRole 34 GetSeqno() keybase1.Seqno 35 GetCTime() time.Time 36 GetETime() time.Time 37 GetPGPFingerprint() *PGPFingerprint 38 GetPGPFullHash() string 39 GetKID() keybase1.KID 40 IsInCurrentFamily(u *User) bool 41 GetUsername() string 42 GetUID() keybase1.UID 43 GetDelegatedKid() keybase1.KID 44 GetMerkleHashMeta() (keybase1.HashMeta, error) 45 GetParentKid() keybase1.KID 46 VerifyReverseSig(ckf ComputedKeyFamily) error 47 GetMerkleSeqno() keybase1.Seqno 48 GetFirstAppearedMerkleSeqnoUnverified() keybase1.Seqno 49 GetDevice() *Device 50 DoOwnNewLinkFromServerNotifications(g *GlobalContext) 51 ToSigChainLocation() keybase1.SigChainLocation 52} 53 54//========================================================================= 55// GenericChainLink 56// 57 58type GenericChainLink struct { 59 *ChainLink 60} 61 62func (g *GenericChainLink) GetSigID() keybase1.SigID { 63 return g.unpacked.sigID 64} 65func (g *GenericChainLink) ToSigChainLocation() keybase1.SigChainLocation { 66 return g.ChainLink.ToSigChainLocation() 67} 68func (g *GenericChainLink) Type() string { return "generic" } 69func (g *GenericChainLink) ToDisplayString() string { return "unknown" } 70func (g *GenericChainLink) insertIntoTable(tab *IdentityTable) { 71 tab.insertLink(g) 72} 73func (g *GenericChainLink) markRevoked(r TypedChainLink) { 74 g.revoked = true 75} 76func (g *GenericChainLink) ToDebugString() string { 77 return fmt.Sprintf("uid=%s, seq=%d, link=%s", g.Parent().uid, g.unpacked.seqno, g.id) 78} 79 80func (g *GenericChainLink) GetDelegatedKid() (kid keybase1.KID) { return } 81func (g *GenericChainLink) GetParentKid() (kid keybase1.KID) { return } 82func (g *GenericChainLink) VerifyReverseSig(ckf ComputedKeyFamily) error { return nil } 83func (g *GenericChainLink) IsRevocationIsh() bool { return false } 84func (g *GenericChainLink) GetRole() KeyRole { return DLGNone } 85func (g *GenericChainLink) IsRevoked() bool { return g.revoked } 86func (g *GenericChainLink) IsDirectlyRevoked() bool { 87 // Same as IsRevoked, but should not be overridden by subclasses (as 88 // TrackChainLink does with IsRevoked). E.g. if in the future 89 // SibkeyChainLink decides to return IsRevoked=true when the delegated 90 // sibkey has been revoked *by KID*, that could be fine, but 91 // IsDirectlyRevoked should still return false in that case. 92 return g.revoked 93} 94func (g *GenericChainLink) GetSeqno() keybase1.Seqno { return g.unpacked.seqno } 95func (g *GenericChainLink) GetPGPFingerprint() *PGPFingerprint { 96 return g.unpacked.pgpFingerprint 97} 98func (g *GenericChainLink) GetPGPFullHash() string { return "" } 99 100func (g *GenericChainLink) GetArmoredSig() string { 101 return g.unpacked.sig 102} 103func (g *GenericChainLink) GetUsername() string { 104 return g.unpacked.username 105} 106func (g *GenericChainLink) GetUID() keybase1.UID { 107 return g.unpacked.uid 108} 109 110func (g *GenericChainLink) GetDevice() *Device { return nil } 111 112func (g *GenericChainLink) extractPGPFullHash(loc string) string { 113 if jw := g.UnmarshalPayloadJSON().AtPath("body." + loc + ".full_hash"); !jw.IsNil() { 114 if ret, err := jw.GetString(); err == nil { 115 return ret 116 } 117 } 118 return "" 119} 120 121func (g *GenericChainLink) DoOwnNewLinkFromServerNotifications(glob *GlobalContext) {} 122 123func CanonicalProofName(t TypedChainLink) string { 124 return strings.ToLower(t.ToDisplayString()) 125} 126 127// 128//========================================================================= 129 130//========================================================================= 131// Web of Trust 132 133type WotVouchChainLink struct { 134 GenericChainLink 135 ExpansionID string 136 Revocations []keybase1.SigID 137} 138 139func (cl *WotVouchChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) {} 140func (cl *WotVouchChainLink) Type() string { return string(LinkTypeWotVouch) } 141 142var _ TypedChainLink = (*WotVouchChainLink)(nil) 143 144func ParseWotVouch(base GenericChainLink) (ret *WotVouchChainLink, err error) { 145 body := base.UnmarshalPayloadJSON() 146 expansionID, err := body.AtPath("body.wot_vouch").GetString() 147 if err != nil { 148 return nil, err 149 } 150 return &WotVouchChainLink{ 151 GenericChainLink: base, 152 ExpansionID: expansionID, 153 Revocations: base.GetRevocations(), 154 }, nil 155} 156 157type WotReactChainLink struct { 158 GenericChainLink 159 ExpansionID string 160} 161 162func (cl *WotReactChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) {} 163func (cl *WotReactChainLink) Type() string { return string(LinkTypeWotReact) } 164 165var _ TypedChainLink = (*WotReactChainLink)(nil) 166 167func ParseWotReact(base GenericChainLink) (ret *WotReactChainLink, err error) { 168 body := base.UnmarshalPayloadJSON() 169 expansionID, err := body.AtPath("body.wot_react").GetString() 170 if err != nil { 171 return nil, err 172 } 173 return &WotReactChainLink{ 174 GenericChainLink: base, 175 ExpansionID: expansionID, 176 }, nil 177} 178 179type sigExpansion struct { 180 Key string `json:"key"` 181 Obj interface{} `json:"obj"` 182} 183 184// ExtractExpansionObj extracts the `obj` field from a sig expansion and verifies the 185// hash of the content matches the expected id. This is reusable beyond WotVouchChainLink. 186func ExtractExpansionObj(expansionID string, expansionJSON string) (expansionObj []byte, err error) { 187 var expansions map[string]sigExpansion 188 err = json.Unmarshal([]byte(expansionJSON), &expansions) 189 if err != nil { 190 return nil, err 191 } 192 expansion, ok := expansions[expansionID] 193 if !ok { 194 return nil, fmt.Errorf("expansion %s does not exist", expansionID) 195 } 196 197 // verify the hash of the expansion object payload matches the expension id 198 objBytes, err := json.Marshal(expansion.Obj) 199 if err != nil { 200 return nil, err 201 } 202 hmacKey, err := hex.DecodeString(expansion.Key) 203 if err != nil { 204 return nil, err 205 } 206 mac := hmac.New(sha256.New, hmacKey) 207 if _, err := mac.Write(objBytes); err != nil { 208 return nil, err 209 } 210 sum := mac.Sum(nil) 211 expectedID := hex.EncodeToString(sum) 212 if expectedID != expansionID { 213 return nil, fmt.Errorf("expansion id doesn't match expected value %s != %s", expansionID, expectedID) 214 } 215 return objBytes, nil 216} 217 218func EmbedExpansionObj(statement *jsonw.Wrapper) (expansion *jsonw.Wrapper, sum []byte, err error) { 219 outer := jsonw.NewDictionary() 220 inner := jsonw.NewDictionary() 221 if err := inner.SetKey("obj", statement); err != nil { 222 return nil, nil, err 223 } 224 randKey, err := RandBytes(16) 225 if err != nil { 226 return nil, nil, err 227 } 228 hexKey := hex.EncodeToString(randKey) 229 if err := inner.SetKey("key", jsonw.NewString(hexKey)); err != nil { 230 return nil, nil, err 231 } 232 marshaled, err := statement.Marshal() 233 if err != nil { 234 return nil, nil, err 235 } 236 mac := hmac.New(sha256.New, randKey) 237 if _, err := mac.Write(marshaled); err != nil { 238 return nil, nil, err 239 } 240 sum = mac.Sum(nil) 241 if err := outer.SetKey(hex.EncodeToString(sum), inner); err != nil { 242 return nil, nil, err 243 } 244 return outer, sum, nil 245} 246 247//========================================================================= 248// Remote, Web and Social 249// 250type RemoteProofChainLink interface { 251 TypedChainLink 252 DisplayPriorityKey() string 253 TableKey() string 254 LastWriterWins() bool 255 GetRemoteUsername() string 256 GetHostname() string 257 GetProtocol() string 258 DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error 259 ToTrackingStatement(keybase1.ProofState) (*jsonw.Wrapper, error) 260 CheckDataJSON() *jsonw.Wrapper 261 ToIDString() string 262 ToKeyValuePair() (string, string) 263 ComputeTrackDiff(tl *TrackLookup) TrackDiff 264 GetProofType() keybase1.ProofType 265 ProofText() string 266} 267 268type WebProofChainLink struct { 269 GenericChainLink 270 protocol string 271 hostname string 272 proofText string 273} 274 275type SocialProofChainLink struct { 276 GenericChainLink 277 service string 278 username string 279 proofText string 280 // signifies a GENERIC_SOCIAL link from a parameterized proof 281 isGeneric bool 282} 283 284func (w *WebProofChainLink) DisplayPriorityKey() string { 285 return w.protocol 286} 287 288func (w *WebProofChainLink) TableKey() string { 289 if w.protocol == "https" { 290 return "http" 291 } 292 return w.protocol 293} 294 295func (w *WebProofChainLink) GetProofType() keybase1.ProofType { 296 if w.protocol == "dns" { 297 return keybase1.ProofType_DNS 298 } 299 return keybase1.ProofType_GENERIC_WEB_SITE 300} 301 302func (w *WebProofChainLink) ToTrackingStatement(state keybase1.ProofState) (*jsonw.Wrapper, error) { 303 ret := w.BaseToTrackingStatement(state) 304 remoteProofToTrackingStatement(w, ret) 305 return ret, nil 306} 307 308func (w *WebProofChainLink) DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error { 309 return ui.FinishWebProofCheck(m, ExportRemoteProof(w), lcr.Export()) 310} 311 312func (w *WebProofChainLink) Type() string { return "proof" } 313func (w *WebProofChainLink) insertIntoTable(tab *IdentityTable) { 314 remoteProofInsertIntoTable(w, tab) 315} 316func (w *WebProofChainLink) ToDisplayString() string { 317 return w.protocol + "://" + w.hostname 318} 319func (w *WebProofChainLink) LastWriterWins() bool { return false } 320func (w *WebProofChainLink) GetRemoteUsername() string { return "" } 321func (w *WebProofChainLink) GetHostname() string { return w.hostname } 322func (w *WebProofChainLink) GetProtocol() string { return w.protocol } 323func (w *WebProofChainLink) ProofText() string { return w.proofText } 324 325func (w *WebProofChainLink) CheckDataJSON() *jsonw.Wrapper { 326 ret := jsonw.NewDictionary() 327 if w.protocol == "dns" { 328 _ = ret.SetKey("protocol", jsonw.NewString(w.protocol)) 329 _ = ret.SetKey("domain", jsonw.NewString(w.hostname)) 330 331 } else { 332 _ = ret.SetKey("protocol", jsonw.NewString(w.protocol+":")) 333 _ = ret.SetKey("hostname", jsonw.NewString(w.hostname)) 334 } 335 return ret 336} 337func (w *WebProofChainLink) ToIDString() string { return w.ToDisplayString() } 338func (w *WebProofChainLink) ToKeyValuePair() (string, string) { 339 return w.GetProtocol(), w.GetHostname() 340} 341 342func (w *WebProofChainLink) ComputeTrackDiff(tl *TrackLookup) (res TrackDiff) { 343 344 find := func(list []string) bool { 345 for _, e := range list { 346 if Cicmp(e, w.hostname) { 347 return true 348 } 349 } 350 return false 351 } 352 if find(tl.ids[w.protocol]) { 353 res = TrackDiffNone{} 354 } else if w.protocol == "https" && find(tl.ids["http"]) { 355 res = TrackDiffUpgraded{"http", "https"} 356 } else { 357 res = TrackDiffNew{} 358 } 359 return 360} 361 362func (s *SocialProofChainLink) DisplayPriorityKey() string { 363 return s.TableKey() 364} 365func (s *SocialProofChainLink) TableKey() string { return s.service } 366func (s *SocialProofChainLink) Type() string { return "proof" } 367func (s *SocialProofChainLink) insertIntoTable(tab *IdentityTable) { 368 remoteProofInsertIntoTable(s, tab) 369} 370func (s *SocialProofChainLink) ToDisplayString() string { 371 return s.username + "@" + s.service 372} 373func (s *SocialProofChainLink) LastWriterWins() bool { return true } 374func (s *SocialProofChainLink) GetRemoteUsername() string { return s.username } 375func (s *SocialProofChainLink) GetHostname() string { return "" } 376func (s *SocialProofChainLink) GetProtocol() string { return "" } 377func (s *SocialProofChainLink) ProofText() string { return s.proofText } 378func (s *SocialProofChainLink) ToIDString() string { return s.ToDisplayString() } 379func (s *SocialProofChainLink) ToKeyValuePair() (string, string) { 380 return s.service, s.username 381} 382func (s *SocialProofChainLink) GetService() string { return s.service } 383 384func (s *SocialProofChainLink) ToTrackingStatement(state keybase1.ProofState) (*jsonw.Wrapper, error) { 385 ret := s.BaseToTrackingStatement(state) 386 remoteProofToTrackingStatement(s, ret) 387 return ret, nil 388} 389 390func (s *SocialProofChainLink) ComputeTrackDiff(tl *TrackLookup) TrackDiff { 391 k, v := s.ToKeyValuePair() 392 if list, found := tl.ids[k]; !found || len(list) == 0 { 393 return TrackDiffNew{} 394 } else if expected := list[len(list)-1]; !Cicmp(expected, v) { 395 return TrackDiffClash{observed: v, expected: expected} 396 } else { 397 return TrackDiffNone{} 398 } 399} 400 401func (s *SocialProofChainLink) DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error { 402 return ui.FinishSocialProofCheck(m, ExportRemoteProof(s), lcr.Export()) 403} 404 405func (s *SocialProofChainLink) CheckDataJSON() *jsonw.Wrapper { 406 ret := jsonw.NewDictionary() 407 _ = ret.SetKey("username", jsonw.NewString(s.username)) 408 _ = ret.SetKey("name", jsonw.NewString(s.service)) 409 return ret 410} 411 412func (s *SocialProofChainLink) GetProofType() keybase1.ProofType { 413 if s.isGeneric { 414 return keybase1.ProofType_GENERIC_SOCIAL 415 } 416 return RemoteServiceTypes[s.service] 417} 418 419var _ RemoteProofChainLink = (*SocialProofChainLink)(nil) 420var _ RemoteProofChainLink = (*WebProofChainLink)(nil) 421 422func NewWebProofChainLink(b GenericChainLink, p, h, proofText string) *WebProofChainLink { 423 return &WebProofChainLink{b, p, h, proofText} 424} 425 426func NewSocialProofChainLink(b GenericChainLink, s, u, proofText string) *SocialProofChainLink { 427 _, found := RemoteServiceTypes[s] 428 return &SocialProofChainLink{ 429 GenericChainLink: b, 430 service: s, 431 username: u, 432 proofText: proofText, 433 isGeneric: !found, 434 } 435} 436 437//========================================================================= 438 439// Can be used to either parse a proof `service` JSON block, or a 440// `remote_key_proof` JSON block in a tracking statement. 441type ServiceBlock struct { 442 social bool 443 typ string 444 id string 445 proofState keybase1.ProofState 446 proofType keybase1.ProofType 447} 448 449func (sb ServiceBlock) GetProofState() keybase1.ProofState { return sb.proofState } 450 451func (sb ServiceBlock) IsSocial() bool { return sb.social } 452 453func (sb ServiceBlock) ToIDString() string { 454 if sb.social { 455 return sb.id + "@" + sb.typ 456 } 457 return sb.typ + "://" + sb.id 458} 459 460func (sb ServiceBlock) ToKeyValuePair() (string, string) { 461 return sb.typ, sb.id 462} 463 464func (sb ServiceBlock) LastWriterWins() bool { 465 return sb.social 466} 467 468func (sb ServiceBlock) GetProofType() keybase1.ProofType { return sb.proofType } 469 470func ParseServiceBlock(jw *jsonw.Wrapper, pt keybase1.ProofType) (sb *ServiceBlock, err error) { 471 var social bool 472 var typ, id string 473 474 if prot, e1 := jw.AtKey("protocol").GetString(); e1 == nil { 475 476 var hostname string 477 478 jw.AtKey("hostname").GetStringVoid(&hostname, &e1) 479 if e1 == nil { 480 switch prot { 481 case "http:": 482 typ, id = "http", hostname 483 case "https:": 484 typ, id = "https", hostname 485 } 486 } else if domain, e2 := jw.AtKey("domain").GetString(); e2 == nil && prot == "dns" { 487 typ, id = "dns", domain 488 } 489 } else { 490 491 var e2 error 492 493 jw.AtKey("name").GetStringVoid(&typ, &e2) 494 jw.AtKey("username").GetStringVoid(&id, &e2) 495 if e2 != nil { 496 id, typ = "", "" 497 } else { 498 social = true 499 } 500 } 501 502 if len(typ) == 0 { 503 err = fmt.Errorf("Unrecognized Web proof @%s", jw.MarshalToDebug()) 504 } 505 sb = &ServiceBlock{social: social, typ: typ, id: id, proofType: pt} 506 return 507} 508 509// To be used for signatures in a user's signature chain. 510func ParseWebServiceBinding(base GenericChainLink) (ret RemoteProofChainLink, err error) { 511 jw := base.UnmarshalPayloadJSON().AtKey("body").AtKey("service") 512 sptf := base.unpacked.proofText 513 514 if jw.IsNil() { 515 ret, err = ParseSelfSigChainLink(base) 516 if err != nil { 517 return nil, err 518 } 519 } else if sb, err := ParseServiceBlock(jw, keybase1.ProofType_NONE); err != nil { 520 err = fmt.Errorf("%s @%s", err, base.ToDebugString()) 521 return nil, err 522 } else if sb.social { 523 ret = NewSocialProofChainLink(base, sb.typ, sb.id, sptf) 524 } else { 525 ret = NewWebProofChainLink(base, sb.typ, sb.id, sptf) 526 } 527 528 return ret, nil 529} 530 531func remoteProofInsertIntoTable(l RemoteProofChainLink, tab *IdentityTable) { 532 tab.insertLink(l) 533 tab.insertRemoteProof(l) 534} 535 536// 537//========================================================================= 538 539//========================================================================= 540// TrackChainLink 541// 542type TrackChainLink struct { 543 GenericChainLink 544 whomUsername NormalizedUsername 545 whomUID keybase1.UID 546 untrack *UntrackChainLink 547 local bool 548 tmpExpireTime time.Time // should only be relevant if local is set to true 549} 550 551func (l TrackChainLink) IsRemote() bool { 552 return !l.local 553} 554 555func ParseTrackChainLink(b GenericChainLink) (ret *TrackChainLink, err error) { 556 payload := b.UnmarshalPayloadJSON() 557 var tmp string 558 tmp, err = payload.AtPath("body.track.basics.username").GetString() 559 if err != nil { 560 err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) 561 return 562 } 563 whomUsername := NewNormalizedUsername(tmp) 564 565 whomUID, err := GetUID(payload.AtPath("body.track.id")) 566 if err != nil { 567 err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) 568 return 569 } 570 571 ret = &TrackChainLink{b, whomUsername, whomUID, nil, false, time.Time{}} 572 return 573} 574 575func (l *TrackChainLink) Type() string { return "track" } 576 577func (l *TrackChainLink) ToDisplayString() string { 578 return l.whomUsername.String() 579} 580 581func (l *TrackChainLink) GetTmpExpireTime() (ret time.Time) { 582 if l.local { 583 ret = l.tmpExpireTime 584 } 585 return ret 586} 587 588func (l *TrackChainLink) insertIntoTable(tab *IdentityTable) { 589 tab.insertLink(l) 590 tab.tracks[l.whomUsername] = append(tab.tracks[l.whomUsername], l) 591} 592 593type TrackedKey struct { 594 KID keybase1.KID 595 Fingerprint *PGPFingerprint 596} 597 598func trackedKeyFromJSON(jw *jsonw.Wrapper) (TrackedKey, error) { 599 var ret TrackedKey 600 kid, err := GetKID(jw.AtKey("kid")) 601 if err != nil { 602 return TrackedKey{}, err 603 } 604 ret.KID = kid 605 606 // It's ok if key_fingerprint doesn't exist. But if it does, then include it: 607 fp, err := GetPGPFingerprint(jw.AtKey("key_fingerprint")) 608 if err == nil && fp != nil { 609 ret.Fingerprint = fp 610 } 611 return ret, nil 612} 613 614func (l *TrackChainLink) GetTrackedKeys() ([]TrackedKey, error) { 615 // presumably order is important, so we'll only use the map as a set 616 // to deduplicate keys. 617 set := make(map[keybase1.KID]bool) 618 619 var res []TrackedKey 620 621 pgpKeysJSON := l.UnmarshalPayloadJSON().AtPath("body.track.pgp_keys") 622 if !pgpKeysJSON.IsNil() { 623 n, err := pgpKeysJSON.Len() 624 if err != nil { 625 return nil, err 626 } 627 for i := 0; i < n; i++ { 628 keyJSON := pgpKeysJSON.AtIndex(i) 629 tracked, err := trackedKeyFromJSON(keyJSON) 630 if err != nil { 631 return nil, err 632 } 633 if !set[tracked.KID] { 634 res = append(res, tracked) 635 set[tracked.KID] = true 636 } 637 } 638 } 639 return res, nil 640} 641 642func (l *TrackChainLink) GetEldestKID() (kid keybase1.KID, err error) { 643 keyJSON := l.UnmarshalPayloadJSON().AtPath("body.track.key") 644 if keyJSON.IsNil() { 645 return kid, nil 646 } 647 tracked, err := trackedKeyFromJSON(keyJSON) 648 if err != nil { 649 return kid, err 650 } 651 return tracked.KID, nil 652} 653 654func (l *TrackChainLink) GetTrackedUID() (keybase1.UID, error) { 655 return GetUID(l.UnmarshalPayloadJSON().AtPath("body.track.id")) 656} 657 658func (l *TrackChainLink) GetTrackedUsername() (NormalizedUsername, error) { 659 tmp, err := l.UnmarshalPayloadJSON().AtPath("body.track.basics.username").GetString() 660 if err != nil { 661 return NormalizedUsername(""), fmt.Errorf("no tracked username: %v", err) 662 } 663 return NewNormalizedUsername(tmp), err 664} 665 666func (l *TrackChainLink) IsRevoked() bool { 667 return l.revoked || l.untrack != nil 668} 669 670func (l *TrackChainLink) RemoteKeyProofs() *jsonw.Wrapper { 671 return l.UnmarshalPayloadJSON().AtPath("body.track.remote_proofs") 672} 673 674func (l *TrackChainLink) ToServiceBlocks() (ret []*ServiceBlock) { 675 w := l.RemoteKeyProofs() 676 ln, err := w.Len() 677 if err != nil { 678 return nil 679 } 680 for index := 0; index < ln; index++ { 681 proof := w.AtIndex(index).AtKey("remote_key_proof") 682 sb := convertTrackedProofToServiceBlock(l.G(), proof, index) 683 if sb != nil { 684 ret = append(ret, sb) 685 } 686 } 687 return ret 688} 689 690// Get the tail of the trackee's sigchain. 691func (l *TrackChainLink) GetTrackedLinkSeqno() (seqno keybase1.Seqno, err error) { 692 seqnoJSON := l.UnmarshalPayloadJSON().AtPath("body.track.seq_tail.seqno") 693 if seqnoJSON.IsNil() { 694 return seqno, nil 695 } 696 i64, err := seqnoJSON.GetInt64() 697 if err != nil { 698 return seqno, err 699 } 700 return keybase1.Seqno(i64), nil 701} 702 703// convertTrackedProofToServiceBlock will take a JSON stanza from a track statement, and convert it 704// to a ServiceBlock if it fails some important sanity checks. We check that the JSON stanza is 705// well-formed, and that it's not for a defunct proof type (like Coinbase). If all succeeds, 706// we output a service block that can entered into found-versus-tracked comparison logic. 707// The `index` provided is what index this JSON stanza is in the overall track statement. 708func convertTrackedProofToServiceBlock(g *GlobalContext, proof *jsonw.Wrapper, index int) (ret *ServiceBlock) { 709 var i, t int 710 var err error 711 i, err = proof.AtKey("state").GetInt() 712 if err != nil { 713 g.Log.Warning("Bad 'state' in track statement: %s", err) 714 return nil 715 } 716 t, err = proof.AtKey("proof_type").GetInt() 717 if err != nil { 718 g.Log.Warning("Bad 'proof_type' in track statement: %s", err) 719 return nil 720 } 721 proofType := keybase1.ProofType(t) 722 if isProofTypeDefunct(g, proofType) { 723 g.Log.Debug("Ignoring now defunct proof type %q at index=%d", proofType, index) 724 return nil 725 } 726 ret, err = ParseServiceBlock(proof.AtKey("check_data_json"), proofType) 727 if err != nil { 728 g.Log.Warning("Bad remote_key_proof.check_data_json: %s", err) 729 return nil 730 } 731 732 ret.proofState = keybase1.ProofState(i) 733 if ret.proofState != keybase1.ProofState_OK { 734 g.Log.Debug("Including broken proof at index=%d (proof state=%d)", index, ret.proofState) 735 } 736 return ret 737} 738 739func (l *TrackChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) { 740 g.Log.Debug("Post notification for new TrackChainLink") 741 g.NotifyRouter.HandleTrackingChanged(l.whomUID, l.whomUsername, true) 742} 743 744// 745//========================================================================= 746 747//========================================================================= 748// EldestChainLink 749// 750 751type EldestChainLink struct { 752 GenericChainLink 753 kid keybase1.KID 754 device *Device 755} 756 757func ParseEldestChainLink(b GenericChainLink) (ret *EldestChainLink, err error) { 758 var kid keybase1.KID 759 var device *Device 760 761 payload := b.UnmarshalPayloadJSON() 762 if kid, err = GetKID(payload.AtPath("body.key.kid")); err != nil { 763 err = ChainLinkError{fmt.Sprintf("Bad eldest statement @%s: %s", b.ToDebugString(), err)} 764 return 765 } 766 767 if jw := payload.AtPath("body.device"); !jw.IsNil() { 768 if device, err = ParseDevice(jw, b.GetCTime()); err != nil { 769 return 770 } 771 } 772 773 ret = &EldestChainLink{b, kid, device} 774 return 775} 776 777func (s *EldestChainLink) GetDelegatedKid() keybase1.KID { return s.kid } 778func (s *EldestChainLink) GetRole() KeyRole { return DLGSibkey } 779func (s *EldestChainLink) Type() string { return string(DelegationTypeEldest) } 780func (s *EldestChainLink) ToDisplayString() string { return s.kid.String() } 781func (s *EldestChainLink) GetDevice() *Device { return s.device } 782func (s *EldestChainLink) GetPGPFullHash() string { return s.extractPGPFullHash("key") } 783func (s *EldestChainLink) insertIntoTable(tab *IdentityTable) { 784 tab.insertLink(s) 785} 786 787// 788//========================================================================= 789 790//========================================================================= 791// SibkeyChainLink 792// 793 794type SibkeyChainLink struct { 795 GenericChainLink 796 kid keybase1.KID 797 device *Device 798 reverseSig string 799} 800 801func ParseSibkeyChainLink(b GenericChainLink) (ret *SibkeyChainLink, err error) { 802 var kid keybase1.KID 803 var device *Device 804 805 payload := b.UnmarshalPayloadJSON() 806 if kid, err = GetKID(payload.AtPath("body.sibkey.kid")); err != nil { 807 err = ChainLinkError{fmt.Sprintf("Bad sibkey statement @%s: %s", b.ToDebugString(), err)} 808 return 809 } 810 811 var rs string 812 if rs, err = payload.AtPath("body.sibkey.reverse_sig").GetString(); err != nil { 813 err = ChainLinkError{fmt.Sprintf("Missing reverse_sig in sibkey delegation: @%s: %s", 814 b.ToDebugString(), err)} 815 return 816 } 817 818 if jw := payload.AtPath("body.device"); !jw.IsNil() { 819 if device, err = ParseDevice(jw, b.GetCTime()); err != nil { 820 return 821 } 822 } 823 824 ret = &SibkeyChainLink{b, kid, device, rs} 825 return 826} 827 828func (s *SibkeyChainLink) GetDelegatedKid() keybase1.KID { return s.kid } 829func (s *SibkeyChainLink) GetRole() KeyRole { return DLGSibkey } 830func (s *SibkeyChainLink) Type() string { return string(DelegationTypeSibkey) } 831func (s *SibkeyChainLink) ToDisplayString() string { return s.kid.String() } 832func (s *SibkeyChainLink) GetDevice() *Device { return s.device } 833func (s *SibkeyChainLink) GetPGPFullHash() string { return s.extractPGPFullHash("sibkey") } 834func (s *SibkeyChainLink) insertIntoTable(tab *IdentityTable) { 835 tab.insertLink(s) 836} 837 838//------------------------------------- 839 840func makeDeepCopy(w *jsonw.Wrapper) (ret *jsonw.Wrapper, err error) { 841 var b []byte 842 if b, err = w.Marshal(); err != nil { 843 return nil, err 844 } 845 return jsonw.Unmarshal(b) 846} 847 848//------------------------------------- 849 850// VerifyReverseSig checks a SibkeyChainLink's reverse signature using the ComputedKeyFamily provided. 851func (s *SibkeyChainLink) VerifyReverseSig(ckf ComputedKeyFamily) (err error) { 852 var key GenericKey 853 854 if key, err = ckf.FindKeyWithKIDUnsafe(s.GetDelegatedKid()); err != nil { 855 return err 856 } 857 858 return VerifyReverseSig(s.G(), key, "body.sibkey.reverse_sig", s.UnmarshalPayloadJSON(), s.reverseSig) 859} 860 861// 862//========================================================================= 863// SubkeyChainLink 864 865type SubkeyChainLink struct { 866 GenericChainLink 867 kid keybase1.KID 868 parentKid keybase1.KID 869} 870 871func ParseSubkeyChainLink(b GenericChainLink) (ret *SubkeyChainLink, err error) { 872 var kid, pkid keybase1.KID 873 payload := b.UnmarshalPayloadJSON() 874 if kid, err = GetKID(payload.AtPath("body.subkey.kid")); err != nil { 875 err = ChainLinkError{fmt.Sprintf("Can't get KID for subkey @%s: %s", b.ToDebugString(), err)} 876 } else if pkid, err = GetKID(payload.AtPath("body.subkey.parent_kid")); err != nil { 877 err = ChainLinkError{fmt.Sprintf("Can't get parent_kid for subkey @%s: %s", b.ToDebugString(), err)} 878 } else { 879 ret = &SubkeyChainLink{b, kid, pkid} 880 } 881 return 882} 883 884func (s *SubkeyChainLink) Type() string { return string(DelegationTypeSubkey) } 885func (s *SubkeyChainLink) ToDisplayString() string { return s.kid.String() } 886func (s *SubkeyChainLink) GetRole() KeyRole { return DLGSubkey } 887func (s *SubkeyChainLink) GetDelegatedKid() keybase1.KID { return s.kid } 888func (s *SubkeyChainLink) GetParentKid() keybase1.KID { return s.parentKid } 889func (s *SubkeyChainLink) insertIntoTable(tab *IdentityTable) { 890 tab.insertLink(s) 891} 892 893// 894//========================================================================= 895 896//========================================================================= 897// PerUserKeyChainLink 898 899type PerUserKeyChainLink struct { 900 GenericChainLink 901 // KID of the signing key derived from the per-user-secret. 902 sigKID keybase1.KID 903 // KID of the encryption key derived from the per-user-secret. 904 encKID keybase1.KID 905 generation keybase1.PerUserKeyGeneration 906 reverseSig string 907} 908 909func ParsePerUserKeyChainLink(b GenericChainLink) (ret *PerUserKeyChainLink, err error) { 910 var sigKID, encKID keybase1.KID 911 var g int 912 var reverseSig string 913 section := b.UnmarshalPayloadJSON().AtPath("body.per_user_key") 914 if sigKID, err = GetKID(section.AtKey("signing_kid")); err != nil { 915 err = ChainLinkError{fmt.Sprintf("Can't get signing KID for per_user_secret: @%s: %s", b.ToDebugString(), err)} 916 } else if encKID, err = GetKID(section.AtKey("encryption_kid")); err != nil { 917 err = ChainLinkError{fmt.Sprintf("Can't get encryption KID for per_user_secret: @%s: %s", b.ToDebugString(), err)} 918 } else if g, err = section.AtKey("generation").GetInt(); err != nil { 919 err = ChainLinkError{fmt.Sprintf("Can't get generation for per_user_secret @%s: %s", b.ToDebugString(), err)} 920 } else if reverseSig, err = section.AtKey("reverse_sig").GetString(); err != nil { 921 err = ChainLinkError{fmt.Sprintf("Missing reverse_sig in per-user-key section: @%s: %s", b.ToDebugString(), err)} 922 } else { 923 ret = &PerUserKeyChainLink{b, sigKID, encKID, keybase1.PerUserKeyGeneration(g), reverseSig} 924 } 925 return ret, err 926} 927 928func (s *PerUserKeyChainLink) Type() string { return string(LinkTypePerUserKey) } 929func (s *PerUserKeyChainLink) ToDisplayString() string { 930 return s.sigKID.String() + " + " + s.encKID.String() 931} 932 933// Don't consider per-user-keys as normal delegations. Because they have 934// multiple kids and initially can't delegate further. They are handled 935// separately by the sigchain loader. 936func (s *PerUserKeyChainLink) GetRole() KeyRole { return DLGNone } 937func (s *PerUserKeyChainLink) GetDelegatedKid() (res keybase1.KID) { return } 938func (s *PerUserKeyChainLink) insertIntoTable(tab *IdentityTable) { 939 tab.insertLink(s) 940} 941 942func (s *PerUserKeyChainLink) ToPerUserKey() keybase1.PerUserKey { 943 return keybase1.PerUserKey{ 944 Gen: int(s.generation), 945 Seqno: s.GetSeqno(), 946 SigKID: s.sigKID, 947 EncKID: s.encKID, 948 SignedByKID: s.GetKID(), 949 } 950} 951 952//------------------------------------- 953 954// VerifyReverseSig checks a SibkeyChainLink's reverse signature using the ComputedKeyFamily provided. 955func (s *PerUserKeyChainLink) VerifyReverseSig(_ ComputedKeyFamily) (err error) { 956 key, err := ImportNaclSigningKeyPairFromHex(s.sigKID.String()) 957 if err != nil { 958 return fmt.Errorf("Invalid per-user signing KID: %s", s.sigKID) 959 } 960 961 return VerifyReverseSig(s.G(), key, "body.per_user_key.reverse_sig", s.UnmarshalPayloadJSON(), s.reverseSig) 962} 963 964// 965//========================================================================= 966// PGPUpdateChainLink 967// 968 969// PGPUpdateChainLink represents a chain link which marks a new version of a 970// PGP key as current. The KID and a new full hash are included in the 971// pgp_update section of the body. 972type PGPUpdateChainLink struct { 973 GenericChainLink 974 kid keybase1.KID 975} 976 977// ParsePGPUpdateChainLink creates a PGPUpdateChainLink from a GenericChainLink 978// and verifies that its pgp_update section contains a KID and full_hash 979func ParsePGPUpdateChainLink(b GenericChainLink) (ret *PGPUpdateChainLink, err error) { 980 var kid keybase1.KID 981 982 pgpUpdate := b.UnmarshalPayloadJSON().AtPath("body.pgp_update") 983 984 if pgpUpdate.IsNil() { 985 err = ChainLinkError{fmt.Sprintf("missing pgp_update section @%s", b.ToDebugString())} 986 return 987 } 988 989 if kid, err = GetKID(pgpUpdate.AtKey("kid")); err != nil { 990 err = ChainLinkError{fmt.Sprintf("Missing kid @%s: %s", b.ToDebugString(), err)} 991 return 992 } 993 994 ret = &PGPUpdateChainLink{b, kid} 995 996 if fh := ret.GetPGPFullHash(); fh == "" { 997 err = ChainLinkError{fmt.Sprintf("Missing full_hash @%s", b.ToDebugString())} 998 ret = nil 999 return 1000 } 1001 1002 return 1003} 1004 1005func (l *PGPUpdateChainLink) Type() string { return string(DelegationTypePGPUpdate) } 1006func (l *PGPUpdateChainLink) ToDisplayString() string { return l.kid.String() } 1007func (l *PGPUpdateChainLink) GetPGPFullHash() string { return l.extractPGPFullHash("pgp_update") } 1008func (l *PGPUpdateChainLink) insertIntoTable(tab *IdentityTable) { tab.insertLink(l) } 1009 1010// 1011//========================================================================= 1012// 1013 1014type DeviceChainLink struct { 1015 GenericChainLink 1016 device *Device 1017} 1018 1019func ParseDeviceChainLink(b GenericChainLink) (ret *DeviceChainLink, err error) { 1020 var dobj *Device 1021 if dobj, err = ParseDevice(b.UnmarshalPayloadJSON().AtPath("body.device"), b.GetCTime()); err != nil { 1022 } else { 1023 ret = &DeviceChainLink{b, dobj} 1024 } 1025 return 1026} 1027 1028func (s *DeviceChainLink) GetDevice() *Device { return s.device } 1029func (s *DeviceChainLink) insertIntoTable(tab *IdentityTable) { 1030 tab.insertLink(s) 1031} 1032 1033// 1034//========================================================================= 1035// WalletStellarChainLink 1036 1037type WalletStellarChainLink struct { 1038 GenericChainLink 1039 addressKID keybase1.KID 1040 reverseSig string 1041 address string 1042 network string 1043 name string 1044} 1045 1046func ParseWalletStellarChainLink(b GenericChainLink) (ret *WalletStellarChainLink, err error) { 1047 ret = &WalletStellarChainLink{GenericChainLink: b} 1048 mkErr := func(format string, args ...interface{}) error { 1049 return ChainLinkError{fmt.Sprintf(format, args...) + fmt.Sprintf(" @%s", b.ToDebugString())} 1050 } 1051 bodyW := b.UnmarshalPayloadJSON() 1052 walletSection := bodyW.AtPath("body.wallet") 1053 walletKeySection := bodyW.AtPath("body.wallet_key") 1054 ret.addressKID, err = GetKID(walletKeySection.AtKey("kid")) 1055 if err != nil { 1056 return nil, mkErr("Can't get address KID: %v", err) 1057 } 1058 ret.reverseSig, err = walletKeySection.AtKey("reverse_sig").GetString() 1059 if err != nil { 1060 return nil, mkErr("Missing reverse_sig: %v", err) 1061 } 1062 ret.address, err = walletSection.AtKey("address").GetString() 1063 if err != nil { 1064 return nil, mkErr("Can't get address: %v", err) 1065 } 1066 ret.network, err = walletSection.AtKey("network").GetString() 1067 if err != nil { 1068 return nil, mkErr("Can't get address network: %v", err) 1069 } 1070 nameOption := walletSection.AtKey("name") 1071 if !nameOption.IsNil() { 1072 ret.name, err = nameOption.GetString() 1073 if err != nil { 1074 return nil, mkErr("Can't get account name: %v", err) 1075 } 1076 } 1077 1078 // Check the network and that the keys match. 1079 if ret.network != string(WalletNetworkStellar) { 1080 return nil, mkErr("Unsupported wallet network '%v'", ret.network) 1081 } 1082 accountKey, err := MakeNaclSigningKeyPairFromStellarAccountID(stellar1.AccountID(ret.address)) 1083 if err != nil { 1084 return nil, mkErr("Invalid stellar account address: '%v'", ret.address) 1085 } 1086 if !ret.addressKID.Equal(accountKey.GetKID()) { 1087 return nil, mkErr("Mismatched wallet keys: '%v' <-/-> '%v", ret.addressKID, ret.address) 1088 } 1089 1090 return ret, nil 1091} 1092 1093func (s *WalletStellarChainLink) Type() string { return string(LinkTypeWalletStellar) } 1094func (s *WalletStellarChainLink) ToDisplayString() string { 1095 return fmt.Sprintf("%v %v %v %v", s.network, s.name, s.address, s.addressKID.String()) 1096} 1097func (s *WalletStellarChainLink) insertIntoTable(tab *IdentityTable) { 1098 tab.insertLink(s) 1099 if tab.stellar == nil || tab.stellar.GetSeqno() <= s.GetSeqno() { 1100 tab.stellar = s 1101 } 1102} 1103 1104// VerifyReverseSig checks a SibkeyChainLink's reverse signature using the ComputedKeyFamily provided. 1105func (s *WalletStellarChainLink) VerifyReverseSig(_ ComputedKeyFamily) (err error) { 1106 key, err := ImportNaclSigningKeyPairFromHex(s.addressKID.String()) 1107 if err != nil { 1108 return fmt.Errorf("Invalid wallet reverse signing KID: %s", s.addressKID) 1109 } 1110 1111 return VerifyReverseSig(s.G(), key, "body.wallet_key.reverse_sig", s.UnmarshalPayloadJSON(), s.reverseSig) 1112} 1113 1114func (s *WalletStellarChainLink) Display(m MetaContext, ui IdentifyUI) error { 1115 // First get an up to date user card, since hiding the Stellar address affects it. 1116 card, err := UserCard(m, s.GetUID(), true) 1117 if err != nil { 1118 m.Info("Could not get usercard, so skipping displaying stellar chain link: %s.", err) 1119 return nil 1120 } 1121 selfUID := m.G().Env.GetUID() 1122 if selfUID.IsNil() { 1123 m.G().Log.Warning("Could not get self UID for api") 1124 } 1125 if card.StellarHidden && !selfUID.Equal(s.GetUID()) { 1126 return nil 1127 } 1128 return ui.DisplayStellarAccount(m, keybase1.StellarAccount{ 1129 AccountID: s.address, 1130 FederationAddress: fmt.Sprintf("%s*keybase.io", s.GetUsername()), 1131 SigID: s.GetSigID(), 1132 Hidden: card.StellarHidden, 1133 }) 1134} 1135 1136// 1137//========================================================================= 1138// UntrackChainLink 1139 1140type UntrackChainLink struct { 1141 GenericChainLink 1142 whomUsername NormalizedUsername 1143 whomUID keybase1.UID 1144} 1145 1146func ParseUntrackChainLink(b GenericChainLink) (ret *UntrackChainLink, err error) { 1147 var tmp string 1148 payload := b.UnmarshalPayloadJSON() 1149 tmp, err = payload.AtPath("body.untrack.basics.username").GetString() 1150 if err != nil { 1151 err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) 1152 return 1153 } 1154 whomUsername := NewNormalizedUsername(tmp) 1155 1156 whomUID, err := GetUID(payload.AtPath("body.untrack.id")) 1157 if err != nil { 1158 err = fmt.Errorf("Bad track statement @%s: %s", b.ToDebugString(), err) 1159 return 1160 } 1161 1162 ret = &UntrackChainLink{b, whomUsername, whomUID} 1163 return 1164} 1165 1166func (u *UntrackChainLink) insertIntoTable(tab *IdentityTable) { 1167 tab.insertLink(u) 1168 if list, found := tab.tracks[u.whomUsername]; !found { 1169 u.G().Log.Debug("| Useless untrack of %s; no previous tracking statement found", 1170 u.whomUsername) 1171 } else { 1172 for _, obj := range list { 1173 obj.untrack = u 1174 } 1175 } 1176} 1177 1178func (u *UntrackChainLink) ToDisplayString() string { 1179 return u.whomUsername.String() 1180} 1181 1182func (u *UntrackChainLink) Type() string { return "untrack" } 1183 1184func (u *UntrackChainLink) IsRevocationIsh() bool { return true } 1185 1186func (u *UntrackChainLink) DoOwnNewLinkFromServerNotifications(g *GlobalContext) { 1187 g.Log.Debug("Post notification for new UntrackChainLink") 1188 g.NotifyRouter.HandleTrackingChanged(u.whomUID, u.whomUsername, false) 1189} 1190 1191// 1192//========================================================================= 1193 1194//========================================================================= 1195// CryptocurrencyChainLink 1196 1197type CryptocurrencyChainLink struct { 1198 GenericChainLink 1199 pkhash []byte 1200 address string 1201 typ CryptocurrencyType 1202} 1203 1204func (c CryptocurrencyChainLink) GetAddress() string { 1205 return c.address 1206} 1207 1208func ParseCryptocurrencyChainLink(b GenericChainLink) ( 1209 cl *CryptocurrencyChainLink, err error) { 1210 1211 jw := b.UnmarshalPayloadJSON().AtPath("body.cryptocurrency") 1212 var styp, addr string 1213 var pkhash []byte 1214 1215 jw.AtKey("type").GetStringVoid(&styp, &err) 1216 jw.AtKey("address").GetStringVoid(&addr, &err) 1217 1218 if err != nil { 1219 return 1220 } 1221 1222 var typ CryptocurrencyType 1223 typ, pkhash, err = CryptocurrencyParseAndCheck(addr) 1224 if err != nil { 1225 err = fmt.Errorf("At signature %s: %s", b.ToDebugString(), err) 1226 return 1227 } 1228 if styp != typ.String() { 1229 err = fmt.Errorf("Got %q type but wanted %q at: %s", styp, typ.String(), b.ToDebugString()) 1230 return 1231 } 1232 1233 cl = &CryptocurrencyChainLink{b, pkhash, addr, typ} 1234 return 1235} 1236 1237func (c *CryptocurrencyChainLink) Type() string { return "cryptocurrency" } 1238 1239func (c *CryptocurrencyChainLink) ToDisplayString() string { return c.address } 1240 1241func (c *CryptocurrencyChainLink) insertIntoTable(tab *IdentityTable) { 1242 tab.insertLink(c) 1243 tab.cryptocurrency = append(tab.cryptocurrency, c) 1244} 1245 1246func (c CryptocurrencyChainLink) Display(m MetaContext, ui IdentifyUI) error { 1247 return ui.DisplayCryptocurrency(m, c.Export()) 1248} 1249 1250// 1251//========================================================================= 1252 1253//========================================================================= 1254// RevokeChainLink 1255 1256type RevokeChainLink struct { 1257 GenericChainLink 1258 device *Device 1259} 1260 1261func ParseRevokeChainLink(b GenericChainLink) (ret *RevokeChainLink, err error) { 1262 var device *Device 1263 if jw := b.UnmarshalPayloadJSON().AtPath("body.device"); !jw.IsNil() { 1264 if device, err = ParseDevice(jw, b.GetCTime()); err != nil { 1265 return 1266 } 1267 } 1268 ret = &RevokeChainLink{b, device} 1269 return 1270} 1271 1272func (r *RevokeChainLink) Type() string { return "revoke" } 1273 1274func (r *RevokeChainLink) ToDisplayString() string { 1275 v := r.GetRevocations() 1276 list := make([]string, len(v)) 1277 for i, s := range v { 1278 list[i] = s.String() 1279 } 1280 return strings.Join(list, ",") 1281} 1282 1283func (r *RevokeChainLink) IsRevocationIsh() bool { return true } 1284 1285func (r *RevokeChainLink) insertIntoTable(tab *IdentityTable) { 1286 tab.insertLink(r) 1287} 1288 1289func (r *RevokeChainLink) GetDevice() *Device { return r.device } 1290 1291// 1292//========================================================================= 1293 1294//========================================================================= 1295// SelfSigChainLink 1296 1297type SelfSigChainLink struct { 1298 GenericChainLink 1299 device *Device 1300} 1301 1302func (s *SelfSigChainLink) Type() string { return "self" } 1303 1304func (s *SelfSigChainLink) ToDisplayString() string { return s.unpacked.username } 1305 1306func (s *SelfSigChainLink) insertIntoTable(tab *IdentityTable) { 1307 tab.insertLink(s) 1308} 1309func (s *SelfSigChainLink) DisplayPriorityKey() string { return s.TableKey() } 1310func (s *SelfSigChainLink) TableKey() string { return "keybase" } 1311func (s *SelfSigChainLink) LastWriterWins() bool { return true } 1312func (s *SelfSigChainLink) GetRemoteUsername() string { return s.GetUsername() } 1313func (s *SelfSigChainLink) GetHostname() string { return "" } 1314func (s *SelfSigChainLink) GetProtocol() string { return "" } 1315func (s *SelfSigChainLink) ProofText() string { return "" } 1316 1317func (s *SelfSigChainLink) GetPGPFullHash() string { return s.extractPGPFullHash("key") } 1318 1319func (s *SelfSigChainLink) DisplayCheck(m MetaContext, ui IdentifyUI, lcr LinkCheckResult) error { 1320 return nil 1321} 1322 1323func (s *SelfSigChainLink) CheckDataJSON() *jsonw.Wrapper { return nil } 1324 1325func (s *SelfSigChainLink) ToTrackingStatement(keybase1.ProofState) (*jsonw.Wrapper, error) { 1326 return nil, nil 1327} 1328 1329func (s *SelfSigChainLink) ToIDString() string { return s.GetUsername() } 1330func (s *SelfSigChainLink) ToKeyValuePair() (string, string) { 1331 return s.TableKey(), s.GetUsername() 1332} 1333 1334func (s *SelfSigChainLink) ComputeTrackDiff(tl *TrackLookup) TrackDiff { return nil } 1335 1336func (s *SelfSigChainLink) GetProofType() keybase1.ProofType { return keybase1.ProofType_KEYBASE } 1337 1338func (s *SelfSigChainLink) ParseDevice() (err error) { 1339 if jw := s.UnmarshalPayloadJSON().AtPath("body.device"); !jw.IsNil() { 1340 s.device, err = ParseDevice(jw, s.GetCTime()) 1341 } 1342 return err 1343} 1344 1345func (s *SelfSigChainLink) GetDevice() *Device { 1346 return s.device 1347} 1348 1349func ParseSelfSigChainLink(base GenericChainLink) (ret *SelfSigChainLink, err error) { 1350 ret = &SelfSigChainLink{base, nil} 1351 if err = ret.ParseDevice(); err != nil { 1352 ret = nil 1353 } 1354 return 1355} 1356 1357// 1358//========================================================================= 1359 1360//========================================================================= 1361 1362type IdentityTable struct { 1363 Contextified 1364 sigChain *SigChain 1365 revocations map[keybase1.SigIDMapKey]bool 1366 links map[keybase1.SigIDMapKey]TypedChainLink 1367 remoteProofLinks *RemoteProofLinks 1368 tracks map[NormalizedUsername][]*TrackChainLink 1369 Order []TypedChainLink 1370 sigHints *SigHints 1371 cryptocurrency []*CryptocurrencyChainLink 1372 stellar *WalletStellarChainLink 1373 checkResult *CheckResult 1374 eldest keybase1.KID 1375 hasStubs bool 1376} 1377 1378func (idt *IdentityTable) GetActiveProofsFor(st ServiceType) (ret []RemoteProofChainLink) { 1379 return idt.remoteProofLinks.ForService(st) 1380} 1381 1382func (idt *IdentityTable) GetTrackMap() map[NormalizedUsername][]*TrackChainLink { 1383 return idt.tracks 1384} 1385 1386func (idt *IdentityTable) HasStubs() bool { 1387 return idt.hasStubs 1388} 1389 1390func (idt *IdentityTable) insertLink(l TypedChainLink) { 1391 idt.links[l.GetSigID().ToMapKey()] = l 1392 idt.Order = append(idt.Order, l) 1393 for _, rev := range l.GetRevocations() { 1394 idt.revocations[rev.ToMapKey()] = true 1395 if targ, found := idt.links[rev.ToMapKey()]; !found { 1396 idt.G().Log.Warning("Can't revoke signature %s @%s", rev, l.ToDebugString()) 1397 } else { 1398 targ.markRevoked(l) 1399 } 1400 } 1401} 1402 1403func (idt *IdentityTable) MarkCheckResult(err ProofError) { 1404 idt.checkResult = NewNowCheckResult(idt.G(), err) 1405} 1406 1407func NewTypedChainLink(cl *ChainLink) (ret TypedChainLink, w Warning) { 1408 if ret = cl.typed; ret != nil { 1409 return 1410 } 1411 1412 base := GenericChainLink{cl} 1413 1414 s, err := cl.UnmarshalPayloadJSON().AtKey("body").AtKey("type").GetString() 1415 if len(s) == 0 || err != nil { 1416 err = fmt.Errorf("No type in signature @%s", base.ToDebugString()) 1417 } else { 1418 switch s { 1419 case string(DelegationTypeEldest): 1420 ret, err = ParseEldestChainLink(base) 1421 case "web_service_binding": 1422 ret, err = ParseWebServiceBinding(base) 1423 case "track": 1424 ret, err = ParseTrackChainLink(base) 1425 case "untrack": 1426 ret, err = ParseUntrackChainLink(base) 1427 case "cryptocurrency": 1428 ret, err = ParseCryptocurrencyChainLink(base) 1429 case "revoke": 1430 ret, err = ParseRevokeChainLink(base) 1431 case string(DelegationTypeSibkey): 1432 ret, err = ParseSibkeyChainLink(base) 1433 case string(DelegationTypeSubkey): 1434 ret, err = ParseSubkeyChainLink(base) 1435 case string(DelegationTypePGPUpdate): 1436 ret, err = ParsePGPUpdateChainLink(base) 1437 case "per_user_key": 1438 ret, err = ParsePerUserKeyChainLink(base) 1439 case "device": 1440 ret, err = ParseDeviceChainLink(base) 1441 case string(LinkTypeWalletStellar): 1442 ret, err = ParseWalletStellarChainLink(base) 1443 case string(LinkTypeWotVouch): 1444 ret, err = ParseWotVouch(base) 1445 case string(LinkTypeWotReact): 1446 ret, err = ParseWotReact(base) 1447 default: 1448 err = fmt.Errorf("Unknown signature type %s @%s", s, base.ToDebugString()) 1449 } 1450 } 1451 1452 if err != nil { 1453 w = ErrorToWarning(err) 1454 ret = &base 1455 } 1456 1457 cl.typed = ret 1458 1459 // Basically we never fail, since worse comes to worse, we treat 1460 // unknown signatures as "generic" and can still display them 1461 return ret, w 1462} 1463 1464func NewIdentityTable(m MetaContext, eldest keybase1.KID, sc *SigChain, h *SigHints) (*IdentityTable, error) { 1465 ret := &IdentityTable{ 1466 Contextified: NewContextified(m.G()), 1467 sigChain: sc, 1468 revocations: make(map[keybase1.SigIDMapKey]bool), 1469 links: make(map[keybase1.SigIDMapKey]TypedChainLink), 1470 remoteProofLinks: NewRemoteProofLinks(m.G()), 1471 tracks: make(map[NormalizedUsername][]*TrackChainLink), 1472 sigHints: h, 1473 eldest: eldest, 1474 } 1475 err := ret.populate(m) 1476 return ret, err 1477} 1478 1479func (idt *IdentityTable) populate(m MetaContext) (err error) { 1480 defer m.Trace("IdentityTable#populate", &err)() 1481 1482 var links []*ChainLink 1483 if links, err = idt.sigChain.GetCurrentSubchain(m, idt.eldest); err != nil { 1484 return err 1485 } 1486 1487 for _, link := range links { 1488 isBad, reason, err := link.IsBad() 1489 if err != nil { 1490 return err 1491 } 1492 if isBad { 1493 m.Debug("Ignoring bad chain link with linkID %s: %s", link.LinkID(), reason) 1494 continue 1495 } 1496 if link.IsStubbed() { 1497 idt.hasStubs = true 1498 continue 1499 } 1500 1501 tcl, w := NewTypedChainLink(link) 1502 if w != nil { 1503 w.Warn(idt.G()) 1504 } 1505 // If it's an unknown link type, then it's OK to ignore it 1506 if tcl == nil { 1507 continue 1508 } 1509 tcl.insertIntoTable(idt) 1510 if link.isOwnNewLinkFromServer { 1511 link.isOwnNewLinkFromServer = false 1512 tcl.DoOwnNewLinkFromServerNotifications(idt.G()) 1513 } 1514 } 1515 return nil 1516} 1517 1518func isProofTypeDefunct(g *GlobalContext, typ keybase1.ProofType) bool { 1519 switch typ { 1520 case keybase1.ProofType_COINBASE: 1521 return true 1522 default: 1523 return false 1524 } 1525} 1526 1527func (idt *IdentityTable) insertRemoteProof(link RemoteProofChainLink) { 1528 1529 if isProofTypeDefunct(idt.G(), link.GetProofType()) { 1530 idt.G().Log.Debug("Ignoring now-defunct proof: %s", link.ToDebugString()) 1531 return 1532 } 1533 1534 // note that the links in the identity table have no ProofError state. 1535 idt.remoteProofLinks.Insert(link, nil) 1536} 1537 1538func (idt *IdentityTable) VerifySelfSig(nun NormalizedUsername, uid keybase1.UID) bool { 1539 list := idt.Order 1540 ln := len(list) 1541 for i := ln - 1; i >= 0; i-- { 1542 link := list[i] 1543 1544 if link.IsRevoked() { 1545 continue 1546 } 1547 if NewNormalizedUsername(link.GetUsername()).Eq(nun) && link.GetUID().Equal(uid) { 1548 idt.G().Log.Debug("| Found self-signature for %s @%s", string(nun), 1549 link.ToDebugString()) 1550 return true 1551 } 1552 } 1553 return false 1554} 1555 1556func (idt *IdentityTable) GetTrackList() (ret []*TrackChainLink) { 1557 for _, v := range idt.tracks { 1558 for i := len(v) - 1; i >= 0; i-- { 1559 link := v[i] 1560 if !link.IsRevoked() { 1561 ret = append(ret, link) 1562 break 1563 } 1564 } 1565 } 1566 return 1567} 1568 1569func (idt *IdentityTable) TrackChainLinkFor(username NormalizedUsername, uid keybase1.UID) (*TrackChainLink, error) { 1570 list, found := idt.tracks[username] 1571 if !found { 1572 return nil, nil 1573 } 1574 for i := len(list) - 1; i >= 0; i-- { 1575 link := list[i] 1576 if link.IsRevoked() { 1577 // noop; continue on! 1578 continue 1579 } 1580 uid2, err := link.GetTrackedUID() 1581 if err != nil { 1582 return nil, fmt.Errorf("Bad tracking statement for %s: %s", username, err) 1583 } 1584 if uid.NotEqual(uid2) { 1585 return nil, fmt.Errorf("Bad UID in tracking statement for %s: %s != %s", username, uid, uid2) 1586 } 1587 return link, nil 1588 } 1589 return nil, nil 1590} 1591 1592func (idt *IdentityTable) ActiveCryptocurrency(family CryptocurrencyFamily) *CryptocurrencyChainLink { 1593 tab := idt.cryptocurrency 1594 for i := len(tab) - 1; i >= 0; i-- { 1595 link := tab[i] 1596 if link.typ.ToCryptocurrencyFamily() == family { 1597 if link.IsRevoked() { 1598 return nil 1599 } 1600 return link 1601 } 1602 } 1603 return nil 1604} 1605 1606func (idt *IdentityTable) AllActiveCryptocurrency() []CryptocurrencyChainLink { 1607 var ret []CryptocurrencyChainLink 1608 for _, link := range idt.cryptocurrency { 1609 if !link.IsRevoked() { 1610 ret = append(ret, *link) 1611 } 1612 } 1613 return ret 1614} 1615 1616func (idt *IdentityTable) HasActiveCryptocurrencyFamily(family CryptocurrencyFamily) bool { 1617 for _, link := range idt.AllActiveCryptocurrency() { 1618 if link.typ.ToCryptocurrencyFamily() == family { 1619 return true 1620 } 1621 } 1622 return false 1623} 1624 1625func (idt *IdentityTable) GetRevokedCryptocurrencyForTesting() []CryptocurrencyChainLink { 1626 ret := []CryptocurrencyChainLink{} 1627 for _, link := range idt.cryptocurrency { 1628 if link.IsRevoked() { 1629 ret = append(ret, *link) 1630 } 1631 } 1632 return ret 1633} 1634 1635// Return the active stellar public address for a user. 1636// Returns nil if there is none or it has not been loaded. 1637func (idt *IdentityTable) StellarAccountID() *stellar1.AccountID { 1638 // Return the account ID of the latest link with the network set to stellar. 1639 if idt.stellar == nil { 1640 return nil 1641 } 1642 link := idt.stellar 1643 if link.network == string(WalletNetworkStellar) { 1644 // Something should have already validated link.address as a stellar account ID. 1645 tmp := stellar1.AccountID(link.address) 1646 return &tmp 1647 } 1648 return nil 1649} 1650 1651func (idt *IdentityTable) Len() int { 1652 return len(idt.Order) 1653} 1654 1655type CheckCompletedListener interface { 1656 CCLCheckCompleted(lcr *LinkCheckResult) 1657} 1658 1659type IdentifyTableMode int 1660 1661const ( 1662 IdentifyTableModePassive IdentifyTableMode = iota 1663 IdentifyTableModeActive IdentifyTableMode = iota 1664) 1665 1666func (idt *IdentityTable) Identify(m MetaContext, is IdentifyState, forceRemoteCheck bool, ui IdentifyUI, ccl CheckCompletedListener, itm IdentifyTableMode) error { 1667 errs := make(chan error, len(is.res.ProofChecks)) 1668 for _, lcr := range is.res.ProofChecks { 1669 go func(l *LinkCheckResult) { 1670 errs <- idt.identifyActiveProof(m, l, is, forceRemoteCheck, ui, ccl, itm) 1671 }(lcr) 1672 } 1673 1674 allAcc := idt.AllActiveCryptocurrency() 1675 for _, acc := range allAcc { 1676 if err := acc.Display(m, ui); err != nil { 1677 return err 1678 } 1679 } 1680 1681 if stellar := idt.stellar; stellar != nil { 1682 if err := stellar.Display(m, ui); err != nil { 1683 return err 1684 } 1685 } 1686 1687 for i := 0; i < len(is.res.ProofChecks); i++ { 1688 err := <-errs 1689 if err != nil { 1690 return err 1691 } 1692 } 1693 1694 return nil 1695} 1696 1697//========================================================================= 1698 1699func (idt *IdentityTable) identifyActiveProof(m MetaContext, lcr *LinkCheckResult, is IdentifyState, forceRemoteCheck bool, ui IdentifyUI, ccl CheckCompletedListener, itm IdentifyTableMode) error { 1700 idt.proofRemoteCheck(m, is.HasPreviousTrack(), forceRemoteCheck, lcr, itm) 1701 if ccl != nil { 1702 ccl.CCLCheckCompleted(lcr) 1703 } 1704 return lcr.link.DisplayCheck(m, ui, *lcr) 1705} 1706 1707type LinkCheckResult struct { 1708 hint *SigHint 1709 // The client checker fills this in with any knowledge it has about hint 1710 // metadata if it is able to derive it without server help. This value is 1711 // preferred to the plain old server-trust `hint` 1712 verifiedHint *SigHint 1713 cached *CheckResult 1714 err ProofError 1715 snoozedErr ProofError 1716 diff TrackDiff 1717 remoteDiff TrackDiff 1718 link RemoteProofChainLink 1719 trackedProofState keybase1.ProofState 1720 tmpTrackedProofState keybase1.ProofState 1721 tmpTrackExpireTime time.Time 1722 position int 1723 torWarning bool 1724} 1725 1726func (l LinkCheckResult) GetDiff() TrackDiff { return l.diff } 1727func (l LinkCheckResult) GetError() error { return l.err } 1728func (l LinkCheckResult) GetProofError() ProofError { return l.err } 1729func (l LinkCheckResult) GetHint() *SigHint { 1730 if l.verifiedHint != nil { 1731 return l.verifiedHint 1732 } 1733 return l.hint 1734} 1735func (l LinkCheckResult) GetCached() *CheckResult { return l.cached } 1736func (l LinkCheckResult) GetPosition() int { return l.position } 1737func (l LinkCheckResult) GetTorWarning() bool { return l.torWarning } 1738func (l LinkCheckResult) GetLink() RemoteProofChainLink { return l.link } 1739func (l LinkCheckResult) GetRemoteDiff() TrackDiff { return l.remoteDiff } 1740 1741// ComputeRemoteDiff takes as input three tracking results: the permanent track, 1742// the local temporary track, and the one it observed remotely. It favors the 1743// permanent track but will roll back to the temporary track if needs be. 1744func (idt *IdentityTable) ComputeRemoteDiff(tracked, trackedTmp, observed keybase1.ProofState) (ret TrackDiff) { 1745 idt.G().Log.Debug("+ ComputeRemoteDiff(%v,%v,%v)", tracked, trackedTmp, observed) 1746 if observed == tracked { 1747 ret = TrackDiffNone{} 1748 } else if observed == trackedTmp { 1749 ret = TrackDiffNoneViaTemporary{} 1750 } else if observed == keybase1.ProofState_OK { 1751 ret = TrackDiffRemoteWorking{tracked} 1752 } else if tracked == keybase1.ProofState_OK { 1753 ret = TrackDiffRemoteFail{observed} 1754 } else { 1755 ret = TrackDiffRemoteChanged{tracked, observed} 1756 } 1757 idt.G().Log.Debug("- ComputeRemoteDiff(%v,%v,%v) -> (%+v,(%T))", tracked, trackedTmp, observed, ret, ret) 1758 return ret 1759} 1760 1761func (idt *IdentityTable) proofRemoteCheck(m MetaContext, hasPreviousTrack, forceRemoteCheck bool, res *LinkCheckResult, itm IdentifyTableMode) { 1762 p := res.link 1763 1764 m.Debug("+ RemoteCheckProof %s", p.ToDebugString()) 1765 doCache := false 1766 pvlHashUsed := keybase1.MerkleStoreKitHash("") 1767 sid := p.GetSigID() 1768 1769 defer func() { 1770 1771 if hasPreviousTrack { 1772 observedProofState := ProofErrorToState(res.err) 1773 res.remoteDiff = idt.ComputeRemoteDiff(res.trackedProofState, res.tmpTrackedProofState, observedProofState) 1774 1775 // If the remote diff only worked out because of the temporary track, then 1776 // also update the local diff (i.e., the difference between what we tracked 1777 // and what Keybase is saying) accordingly. 1778 if _, ok := res.remoteDiff.(TrackDiffNoneViaTemporary); ok { 1779 res.diff = res.remoteDiff 1780 } 1781 } 1782 1783 if doCache { 1784 m.Debug("| Caching results under key=%s pvlHash=%s", sid, pvlHashUsed) 1785 if cacheErr := idt.G().ProofCache.Put(sid, res, pvlHashUsed); cacheErr != nil { 1786 m.Warning("proof cache put error: %s", cacheErr) 1787 } 1788 } 1789 1790 m.Debug("- RemoteCheckProof %s", p.ToDebugString()) 1791 }() 1792 1793 pvlSource := idt.G().GetPvlSource() 1794 if pvlSource == nil { 1795 res.err = NewProofError(keybase1.ProofStatus_MISSING_PVL, "no pvl source for proof verification") 1796 return 1797 } 1798 pvlU, err := pvlSource.GetLatestEntry(m) 1799 if err != nil { 1800 res.err = NewProofError(keybase1.ProofStatus_MISSING_PVL, "error getting pvl: %s", err) 1801 return 1802 } 1803 pvlHashUsed = pvlU.Hash 1804 1805 res.hint = idt.sigHints.Lookup(sid) 1806 if res.hint == nil { 1807 res.err = NewProofError(keybase1.ProofStatus_NO_HINT, "No server-given hint for sig=%s", sid) 1808 return 1809 } 1810 1811 var pc ProofChecker 1812 1813 // Call the Global context's version of what a proof checker is. We might want to stub it out 1814 // for the purposes of testing. 1815 pc, res.err = MakeProofChecker(m, m.G().GetProofServices(), p) 1816 1817 if res.err != nil || pc == nil { 1818 return 1819 } 1820 1821 if m.G().Env.GetTorMode().Enabled() { 1822 if e := pc.GetTorError(); e != nil { 1823 res.torWarning = true 1824 } 1825 } 1826 1827 if !forceRemoteCheck { 1828 res.cached = m.G().ProofCache.Get(sid, pvlU.Hash) 1829 m.Debug("| Proof cache lookup for %s: %+v", sid, res.cached) 1830 if res.cached != nil && res.cached.Freshness() == keybase1.CheckResultFreshness_FRESH { 1831 res.err = res.cached.Status 1832 res.verifiedHint = res.cached.VerifiedHint 1833 m.Debug("| Early exit after proofCache hit for %s", sid) 1834 return 1835 } 1836 } 1837 1838 // From this point on in the function, we'll be putting our results into 1839 // cache (in the defer above). 1840 doCache = true 1841 1842 // ProofCheckerModeActive or Passive mainly decides whether we need to reach out to 1843 // self-hosted services. We want to avoid so doing when the user is acting passively 1844 // (such as when receiving a message). 1845 pcm := ProofCheckerModePassive 1846 if (hasPreviousTrack && res.trackedProofState != keybase1.ProofState_NONE && res.trackedProofState != keybase1.ProofState_UNCHECKED) || itm == IdentifyTableModeActive { 1847 pcm = ProofCheckerModeActive 1848 } 1849 var hint SigHint 1850 if res.hint != nil { 1851 hint = *res.hint 1852 } 1853 res.verifiedHint, res.err = pc.CheckStatus(m, hint, pcm, pvlU) 1854 1855 // If no error than all good 1856 if res.err == nil { 1857 return 1858 } 1859 1860 // If the error was soft, and we had a cached successful result that wasn't rancid, 1861 // then it's OK to stifle the error message for now. We just have to be certain 1862 // not to cache it. 1863 if ProofErrorIsSoft(res.err) && res.cached != nil && res.cached.Status == nil && 1864 res.cached.Freshness() != keybase1.CheckResultFreshness_RANCID { 1865 m.Debug("| Got soft error (%s) but returning success (last seen at %s)", 1866 res.err.Error(), res.cached.Time) 1867 res.snoozedErr = res.err 1868 res.err = nil 1869 doCache = false 1870 return 1871 } 1872 1873 m.Debug("| Check status (%s) failed with error: %s", p.ToDebugString(), res.err.Error()) 1874} 1875 1876// VerifyReverseSig checks reverse signature using the key provided. 1877// does not modify `payload`. 1878// `path` is the path to the reverse sig spot to null before checking. 1879func VerifyReverseSig(g *GlobalContext, key GenericKey, path string, payload *jsonw.Wrapper, reverseSig string) (err error) { 1880 var p1, p2 []byte 1881 if p1, _, err = key.VerifyStringAndExtract(g.Log, reverseSig); err != nil { 1882 err = ReverseSigError{fmt.Sprintf("Failed to verify/extract sig: %s", err)} 1883 return err 1884 } 1885 1886 if p1, err = jsonw.Canonicalize(p1); err != nil { 1887 err = ReverseSigError{fmt.Sprintf("Failed to canonicalize json: %s", err)} 1888 return err 1889 } 1890 1891 // Make a deep copy. It's dangerous to try to mutate this thing 1892 // since other goroutines might be accessing it at the same time. 1893 var jsonCopy *jsonw.Wrapper 1894 if jsonCopy, err = makeDeepCopy(payload); err != nil { 1895 err = ReverseSigError{fmt.Sprintf("Failed to copy payload json: %s", err)} 1896 return err 1897 } 1898 1899 err = jsonCopy.SetValueAtPath(path, jsonw.NewNil()) 1900 if err != nil { 1901 return err 1902 } 1903 if p2, err = jsonCopy.Marshal(); err != nil { 1904 err = ReverseSigError{fmt.Sprintf("Can't remarshal JSON statement: %s", err)} 1905 return err 1906 } 1907 1908 eq := FastByteArrayEq(p1, p2) 1909 1910 if !eq { 1911 err = ReverseSigError{fmt.Sprintf("JSON mismatch: %s != %s", 1912 string(p1), string(p2))} 1913 return err 1914 } 1915 return nil 1916} 1917