/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/libkb/chain_link.go
1 654 строки
50 KB
zoom-ua
gofumpt (#28710)
17 дек 2025, 17:16
Не верифицирован
17 дек 2025, 17:16
cda510b
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. package libkb import ( "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "strconv" "time" "github.com/buger/jsonparser" "github.com/keybase/client/go/jsonparserw" "github.com/keybase/client/go/msgpack" pkgerrors "github.com/pkg/errors" keybase1 "github.com/keybase/client/go/protocol/keybase1" jsonw "github.com/keybase/go-jsonw" ) const ( LinkIDLen = 32 ) type LinkID []byte func GetLinkID(w *jsonw.Wrapper) (LinkID, error) { if w.IsNil() { return nil, nil } s, err := w.GetString() if err != nil { return nil, err } ret, err := LinkIDFromHex(s) return ret, err } func ImportLinkID(i keybase1.LinkID) (LinkID, error) { return LinkIDFromHex(string(i)) } func GetLinkIDVoid(w *jsonw.Wrapper, l *LinkID, e *error) { ret, err := GetLinkID(w) if err != nil { *e = err } else { *l = ret } } func (l *LinkID) UnmarshalJSON(b []byte) error { lid, err := LinkIDFromHex(keybase1.Unquote(b)) if err != nil { return err } *l = make([]byte, len(lid)) copy((*l)[:], lid[:]) return nil } func (l *LinkID) MarshalJSON() ([]byte, error) { return keybase1.Quote(l.String()), nil } func LinkIDFromHex(s string) (LinkID, error) { bv, err := hex.DecodeString(s) if err == nil && len(bv) != LinkIDLen { err = fmt.Errorf("Bad link ID; wrong length: %d", len(bv)) bv = nil } var ret LinkID if bv != nil { ret = LinkID(bv) } return ret, err } func (l LinkID) String() string { return hex.EncodeToString(l) } func (l LinkID) Eq(i2 LinkID) bool { if l == nil && i2 == nil { return true } else if l == nil || i2 == nil { return false } return FastByteArrayEq(l[:], i2[:]) } type ChainLinkUnpacked struct { prev LinkID seqno keybase1.Seqno highSkip *HighSkip seqType keybase1.SeqType ignoreIfUnsupported SigIgnoreIfUnsupported payloadLocal []byte // local track payloads payloadV2 []byte ctime, etime int64 pgpFingerprint *PGPFingerprint kid keybase1.KID eldestKID keybase1.KID sig string sigID keybase1.SigID uid keybase1.UID username string typ string proofText string outerLinkV2 *OuterLinkV2WithMetadata sigVersion SigVersion // what the server hints is the sig version (must be verified) stubbed bool firstAppearedMerkleSeqnoUnverified keybase1.Seqno payloadHash []byte sigDropped bool hasRevocations bool merkleSeqno keybase1.Seqno merkleHashMeta keybase1.HashMeta } // A template for some of the reasons in badChainLinks below. const ( badAkalin = "Link %d of akalin's sigchain, which was accidentally added by an old client in development on 23 Mar 2015 20:02 GMT." badJamGregory = "Link %d of jamgregory's sigchain, which had a bad PGP keypin" badDens = "Link 8 of dens's sigchain, which signs in a revoked PGP key" badAjar = "Link 98 of ajar's sigchain allowed a PGP update with a broken PGP key" badJaccarmac = "Link 46 of jaccarmac's sigchain, which signs in a revoked PGP key (key later removed in link 46)" ) const ( akalin = keybase1.UID("ebbe1d99410ab70123262cf8dfc87900") jamGregory = keybase1.UID("e8767e19a3ed9c7350847b7b040de319") dens = keybase1.UID("ca9e948f6f7a4a19e02058ad626f6c19") ajar = keybase1.UID("d1d94b3131e493dfee738802843f7719") jaccarmac = keybase1.UID("bfd01fc0a2d5ee0462aaa3c598210b00") ) type SpecialChainLink struct { UID keybase1.UID Seqno keybase1.Seqno Reason string } // A map from SigIDs of bad chain links that should be ignored to the // reasons why they're ignored. var badChainLinks = map[keybase1.LinkID]SpecialChainLink{ // Links 22-25 of akalin's sigchain, which was accidentally // added by an old client in development on 3/23/2015, 9:02am. // Links 17-19 of jamGregory's sigchain, which referred to a corrupted // PGP key. See https://github.com/keybase/client/issues/1908 // Link 8 of dens's sigchain is to a revoked PGP key, which wasn't // properly checked for on the server side. // See: https://github.com/keybase/client/issues/4754 // Link 98 of ajar's sigchain is a PGP update with a broken PGP key, // that doesn't have a valid cross-sig on a signing key. It was a server // bug to allow it be uploaded. // Link 46 of jaccarmac's sigchain is a PGP delegation with a revoked PGP // key. The self signature is invalid (revoked key signs over itself, and // that signature cannot be verified, because the key is revoked). That // wasn't properly rejected on the server because of the same bug as in // "Link 8 of den's sigchain". // See: https://github.com/keybase/client/issues/24176 "694ed7166cee72449964e97bcd4be58243877718425c4dc655d2d80832bd5cdf": {UID: akalin, Seqno: keybase1.Seqno(22), Reason: fmt.Sprintf(badAkalin, 22)}, "27bc88059a768a82b1a21dcc1c46f7fc61c2d2b80c445eb2d18fed3a5bb42e49": {UID: akalin, Seqno: keybase1.Seqno(23), Reason: fmt.Sprintf(badAkalin, 23)}, "12b594e44d9289349283f8b14a6f83ad144a17a3025a758e17d4eca70fbdc923": {UID: akalin, Seqno: keybase1.Seqno(24), Reason: fmt.Sprintf(badAkalin, 24)}, "ce162011e380c954de15f30db28f8b7b358866d2721143d9d0d4424166ce5ed8": {UID: akalin, Seqno: keybase1.Seqno(25), Reason: fmt.Sprintf(badAkalin, 25)}, "bf914e6d4cf9b4eb7c88c2a8a6f5650e969ade9a97cf1605c1eb8cae97d5d278": {UID: jamGregory, Seqno: keybase1.Seqno(17), Reason: fmt.Sprintf(badJamGregory, 17)}, "e56f492c1b519905d04ce51368e87794963906dd6dacb63fbeab7ad23596af29": {UID: jamGregory, Seqno: keybase1.Seqno(18), Reason: fmt.Sprintf(badJamGregory, 18)}, "51e46dad8b71a1a7204368f9cb4931257a32eed92cf3b97a08190c12912739dd": {UID: jamGregory, Seqno: keybase1.Seqno(19), Reason: fmt.Sprintf(badJamGregory, 19)}, "6d527d776cb28ea980c6e0474286fe745377e116fd5d07b44928d165ae4b7c97": {UID: dens, Seqno: keybase1.Seqno(8), Reason: badDens}, "9b3b3a3d973449ca3238bf59b7407186dc80242b917c158cba5e374595257dd0": {UID: ajar, Seqno: keybase1.Seqno(98), Reason: badAjar}, "496b0328664a7c8115449f3fb17d137990c52712797730ede9c4b8ceb942e6ea": {UID: jaccarmac, Seqno: keybase1.Seqno(46), Reason: badJaccarmac}, } // Some chainlinks are broken and need a small whitespace addition to match their payload // hash in subsequent chainlinks. Caused by bad code on 15 Sep 2015. const whitespaceIssue20150915 = "Bad whitespace stripping on 15 Sep 2015" var badWhitespaceChainLinks = map[keybase1.LinkID]string{ "ac3ecaa2aa1d638867026f0c54a1d895777f366d02bfef37403275aa0d4f8322": whitespaceIssue20150915, "94fde9d49c29cba59c949b35dd424de3a0daccf8a04ba443833e3328d495b9d8": whitespaceIssue20150915, "b9f188d0c6638e3bef3dfc3476c04078bb2aef2a9249cc77b6f009692967388a": whitespaceIssue20150915, "f5f324e91a94c073fdc936b50d56250133dc19415ae592d2c7cb99db9e980e1b": whitespaceIssue20150915, "03fb1e2c0e61e3715c41515045d89d2f788dbcc7eb671b94ac12ee5f805bbe70": whitespaceIssue20150915, "e449b1cd1d6f2a86a0f800c47e7d1ad26bbb6c76b983bd78154972c51f77e960": whitespaceIssue20150915, "d380d18672da3c18f0804baf6b28f5efda76d64220a152c000f2b3f9af8b6603": whitespaceIssue20150915, "5957f583bec18cc6f381355843c21f903fe47d584a9816e072f3f102f1f488be": whitespaceIssue20150915, "2c11a140d8f231af6d69543474138a503191486ae6b5739892c5e0c6c0c4c348": whitespaceIssue20150915, "6f3d73ddf575f2033a48268a564575e40edbb5111cc057984f51f463d4e8ed58": whitespaceIssue20150915, "b23dfd34e58a814543e1f8368b9d07922abec213afca6d2b76722825794acffa": whitespaceIssue20150915, "2efe839231d6b03f85ab3c542e870e7062329a8c5e384f1289b00be7c7afb8ab": whitespaceIssue20150915, "18688c45cbe05ee2b72567acc696b3856f9876dff0ec3ea927ad7632a3f48fe6": whitespaceIssue20150915, "2cf8b9ffa500089b6db873acbabdba771e8e897c0a899a01f8967a7280cfd0da": whitespaceIssue20150915, "acf150b2d57a3aa65574bc2bb97e224413ce3f5344fd24fc7c3282da48cc2f3d": whitespaceIssue20150915, "371f9ae63d56ec853fa53941e79d29abbb4cd11aa926715d354d18d687b0ca71": whitespaceIssue20150915, "4948115615d7dceb90bcdd818f69b66b5899339a2b747b5e6dc0f6987abbcbd0": whitespaceIssue20150915, "4c3f7855eb307aa5620962e15de84b2cfe3f728a9722c43906b12e0f3082cb87": whitespaceIssue20150915, "9db59496652a1587ed56ec6ae15917b6d0ef4ac9a14dda97bfa4d2427a80e2b8": whitespaceIssue20150915, "43f21601ffaeae70eca2f585949f42c67e85e93cf2a6847d6c20ffd81a9ff890": whitespaceIssue20150915, "7560f896c19457365225f48be0217b8a00519f1daccefee4c097dd1b4594dd66": whitespaceIssue20150915, "09527db7672bf23a9681ac86c70826cdc01ed1e467252a76ca4bf4ad0964efd7": whitespaceIssue20150915, "3803be27ec0c61b3fdcd8b9b7c78de3df73766736ef00727267858d34a039c7d": whitespaceIssue20150915, "740f9140a7901defaaaec10042722b30d2fee457337b7ae8e9de3b9fc05d109f": whitespaceIssue20150915, "32f5dd2643eabf3828f7f03ccded07d8d8a29e352df6130c3a4232104398d819": whitespaceIssue20150915, "7d97355e5917c5bcc14ba3a1994398b3fa36416768b663c1454069de84a4fca2": whitespaceIssue20150915, "720b80b7c15cb9a3d21a2eec228bceb5db6f0ef54df2d0aef08aec5ed1632257": whitespaceIssue20150915, "12c9203c98fe0b1c80a551f8933b2c870fcc3754a8ea05591e43a4d528fadc68": whitespaceIssue20150915, "9644d4db6a4928ad1075a22b4473d1efa47c99a1a2a779450d4cd67d9115b9ba": whitespaceIssue20150915, "605525686fef18180be692df6106c13dae39abb2799dc9e8bed1e2bb64e9b886": whitespaceIssue20150915, "374f1da46fd8238ab9f288183cb78f3c6a59732f4b19705763c9d6ac356015ef": whitespaceIssue20150915, "893567013c77f45755279bf1138fecbb54cd3a55bf5814504cf0406acbe4bfeb": whitespaceIssue20150915, "3ca5ef6a6115a8a86d7d94cb3565f43f05f7975d66015455dd6cc32b73936177": whitespaceIssue20150915, "3cdd165df44ba7f8331b89213f213dab36482ef513d023c5d2b0f6bfd11d5678": whitespaceIssue20150915, "36328ab1cf15cc3dd2ba4c771ca1066b2d44714780ad8e83894611e2a2642003": whitespaceIssue20150915, "61e9f4b437fccac8abd396acfc96b17558c9c355b57f4a5f2f3698e78f19532f": whitespaceIssue20150915, "14ef90159164e19228ff21c909b764e239f27f0fff49f86414a2dde9b719845f": whitespaceIssue20150915, "b74b420f49b771ec04e656101f86c9729cf328b0fd32f5082d04d3c39f8ccea7": whitespaceIssue20150915, "7772c99774570202a2c5ac017eefc8296f613e64c8d4adff4ba7991b553431f5": whitespaceIssue20150915, "d7ae76e4fdae7034b07e515d5684adcd51afea5a22b8520d2c61d31f5028fc6e": whitespaceIssue20150915, "33a61f19c0ca52257214f97524ef10441cf85215ff171868f53561dfd7b14c81": whitespaceIssue20150915, "616d9710b3a594ab00292d3d414e6e141929935a133bfa9a25ec4a155a403e5c": whitespaceIssue20150915, "8d7c1a0c99186f972afc5d3624aca2f88ddc3a5dbf84e826ef0b520c31a78aa3": whitespaceIssue20150915, "9f8c0a29a6ba3a521db2cd4d3e2ae15223dbcd5d5d1201e33ebb2dee1b61342f": whitespaceIssue20150915, "a9efa00bc479cb40ac0521749520f5a7a38a4ba4e698ee03355a85a8464b3840": whitespaceIssue20150915, "f1509495f4f1d46e43dcdd341156b975f7ad19aefeb250a80fd2b236c517a891": whitespaceIssue20150915, "da99975f9ae8cdeb9e3a42a1166617dbf6afbcf841919dcf05145a73a7026cc2": whitespaceIssue20150915, } type ChainLink struct { Contextified parent *SigChain id LinkID diskVersion int hashVerified bool sigVerified bool payloadVerified bool chainVerified bool highChainVerified bool storedLocally bool revoked bool dirty bool revocationsCache *[]keybase1.SigID computedHighSkip *HighSkip unpacked *ChainLinkUnpacked cki *ComputedKeyInfos typed TypedChainLink isOwnNewLinkFromServer bool } // See NCC-KB2018-006 func (c ChainLink) checkSpecialLinksTable(tab map[keybase1.LinkID]SpecialChainLink, uid keybase1.UID, why string) (found bool, reason string, err error) { var scl SpecialChainLink // The truthiness of hashVerified should ensure that this link // is only considered here after all prevs have been successfully checked. if !c.canTrustID() { return false, "", ChainLinkError{fmt.Sprintf("cannot check if a link is %q without a verified link ID (linkID=%s, uid=%s, hash=%v, chain=%v, diskVersion=%d)", why, c.id, uid, c.hashVerified, c.chainVerified, c.diskVersion)} } scl, found = tab[c.LinkID().Export()] if !found { return false, "", nil } if !c.GetSeqno().Eq(scl.Seqno) { return false, "", NewChainLinkWrongSeqnoError(fmt.Sprintf("malicious bad link in from server has wrong seqno in %q check: %d != %d", why, c.GetSeqno(), scl.Seqno)) } if !scl.UID.Equal(uid) { return false, "", NewUIDMismatchError(fmt.Sprintf("malicious bad link from server in %q check; UID %s != %s", why, scl.UID, uid)) } return true, scl.Reason, nil } func (c *ChainLink) IsBad() (isBad bool, reason string, err error) { return c.checkSpecialLinksTable(badChainLinks, c.parent.uid, "bad chain links") } func (c *ChainLink) Parent() *SigChain { return c.parent } func (c *ChainLink) SetParent(parent *SigChain) { if c.parent != nil { c.G().Log.Warning("changing ChainLink parent") } c.parent = parent } func (c *ChainLink) getPrevFromPayload() LinkID { return c.unpacked.prev } func (c *ChainLink) getSeqTypeFromPayload() keybase1.SeqType { return c.unpacked.seqType } func (c *ChainLink) getIgnoreIfUnsupportedFromPayload() SigIgnoreIfUnsupported { return c.unpacked.ignoreIfUnsupported } func (c *ChainLink) GetIgnoreIfSupported() SigIgnoreIfUnsupported { return c.getIgnoreIfUnsupportedFromPayload() } func (c *ChainLink) getHighSkipFromPayload() *HighSkip { return c.unpacked.highSkip } func (c *ChainLink) IsStubbed() bool { return c.unpacked.stubbed } func (c *ChainLink) IsEldest() bool { if c.unpacked == nil { return false } if c.unpacked.outerLinkV2 != nil { return c.unpacked.outerLinkV2.LinkType == SigchainV2TypeEldest } if c.unpacked.typ == string(DelegationTypeEldest) { return true } return false } func (c *ChainLink) GetPrev() LinkID { return c.unpacked.prev } func (c *ChainLink) GetCTime() time.Time { if c.IsStubbed() { return time.Time{} } return time.Unix(c.unpacked.ctime, 0) } func (c *ChainLink) GetETime() time.Time { if c.IsStubbed() { return time.Time{} } return UnixToTimeMappingZero(c.unpacked.etime) } func (c *ChainLink) GetFirstAppearedMerkleSeqnoUnverified() keybase1.Seqno { if c.IsStubbed() { return keybase1.Seqno(0) } return c.unpacked.firstAppearedMerkleSeqnoUnverified } func (c *ChainLink) GetUID() keybase1.UID { return c.unpacked.uid } func (c *ChainLink) UnmarshalPayloadJSON() *jsonw.Wrapper { jw, err := c.G().PayloadCache.GetOrPrime(c) if err != nil { // Any unmarshal error here would already have // happened in Unpack return nil } return jw } func (c *ChainLink) ToSigChainLocation() keybase1.SigChainLocation { return keybase1.SigChainLocation{ Seqno: c.GetSeqno(), // This code is meant only for user chains SeqType: keybase1.SeqType_PUBLIC, } } const chainLinkDiskVersion = 1 func (c *ChainLink) canTrustID() bool { return c.hashVerified || (c.storedLocally && c.diskVersion < 2) } func (c *ChainLink) Pack() (*jsonw.Wrapper, error) { p := jsonw.NewDictionary() if c.IsStubbed() { err := p.SetKey("s2", jsonw.NewString(c.unpacked.outerLinkV2.EncodeStubbed())) if err != nil { return nil, err } } else { // store the payload for v2 links and local tracks if c.unpacked.sigVersion == KeybaseSignatureV2 { err := p.SetKey("payload_json", jsonw.NewString(string(c.unpacked.payloadV2))) if err != nil { return nil, err } } else if len(c.unpacked.payloadLocal) > 0 { err := p.SetKey("payload_json", jsonw.NewString(string(c.unpacked.payloadLocal))) if err != nil { return nil, err } } err := p.SetKey("sig", jsonw.NewString(c.unpacked.sig)) if err != nil { return nil, err } err = p.SetKey("sig_id", jsonw.NewString(string(c.unpacked.sigID))) if err != nil { return nil, err } err = p.SetKey("kid", c.unpacked.kid.ToJsonw()) if err != nil { return nil, err } err = p.SetKey("ctime", jsonw.NewInt64(c.unpacked.ctime)) if err != nil { return nil, err } if c.unpacked.pgpFingerprint != nil { err := p.SetKey("fingerprint", jsonw.NewString(c.unpacked.pgpFingerprint.String())) if err != nil { return nil, err } } err = p.SetKey("sig_verified", jsonw.NewBool(c.sigVerified)) if err != nil { return nil, err } err = p.SetKey("chain_verified", jsonw.NewBool(c.chainVerified)) if err != nil { return nil, err } err = p.SetKey("hash_verified", jsonw.NewBool(c.hashVerified)) if err != nil { return nil, err } err = p.SetKey("payload_verified", jsonw.NewBool(c.payloadVerified)) if err != nil { return nil, err } err = p.SetKey("proof_text_full", jsonw.NewString(c.unpacked.proofText)) if err != nil { return nil, err } err = p.SetKey("sig_version", jsonw.NewInt(int(c.unpacked.sigVersion))) if err != nil { return nil, err } err = p.SetKey("merkle_seqno", jsonw.NewInt64(int64(c.unpacked.firstAppearedMerkleSeqnoUnverified))) if err != nil { return nil, err } err = p.SetKey("disk_version", jsonw.NewInt(chainLinkDiskVersion)) if err != nil { return nil, err } } if c.cki != nil { err := p.SetKey("computed_key_infos", jsonw.NewWrapper(*c.cki)) if err != nil { return nil, err } } return p, nil } func (c *ChainLink) GetMerkleSeqno() keybase1.Seqno { if c.IsStubbed() { return 0 } return c.unpacked.merkleSeqno } func (c *ChainLink) GetMerkleHashMeta() (keybase1.HashMeta, error) { if c.IsStubbed() { return nil, nil } return c.unpacked.merkleHashMeta, nil } func (c *ChainLink) HasRevocations() bool { if c.IsStubbed() { return false } if c.unpacked != nil { return c.unpacked.hasRevocations } return false } func (tmp *ChainLinkUnpacked) HasRevocations(payload []byte) bool { if _, _, _, err := jsonparserw.Get(payload, "body", "revoke", "sig_id"); err == nil { return true } if _, _, _, err := jsonparserw.Get(payload, "body", "revoke", "sig_ids", "[0]"); err == nil { return true } if _, _, _, err := jsonparserw.Get(payload, "body", "revoke", "kid"); err == nil { return true } if _, _, _, err := jsonparserw.Get(payload, "body", "revoke", "kids", "[0]"); err == nil { return true } return false } func (c *ChainLink) GetRevocations() []keybase1.SigID { if c.IsStubbed() { return nil } if c.revocationsCache != nil { return *c.revocationsCache } payload, err := c.unpacked.Payload() if err != nil { return nil } var ret []keybase1.SigID if !bytes.Contains(payload, []byte("revoke")) { c.revocationsCache = &ret return nil } if s, err := jsonparserw.GetString(payload, "body", "revoke", "sig_id"); err == nil { if sigID, err := keybase1.SigIDFromString(s); err == nil { ret = append(ret, sigID) } } _, _ = jsonparserw.ArrayEach(payload, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { if s, err := keybase1.SigIDFromString(string(value)); err == nil { ret = append(ret, s) } }, "body", "revoke", "sig_ids") c.revocationsCache = &ret return ret } func (c *ChainLink) GetRevokeKids() []keybase1.KID { if c.IsStubbed() { return nil } payload, err := c.unpacked.Payload() if err != nil { return nil } var ret []keybase1.KID if s, err := jsonparserw.GetString(payload, "body", "revoke", "kid"); err == nil { ret = append(ret, keybase1.KIDFromString(s)) } _, _ = jsonparserw.ArrayEach(payload, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { ret = append(ret, keybase1.KIDFromString(string(value))) }, "body", "revoke", "kids") return ret } func (c *ChainLink) checkAgainstMerkleTree(t *MerkleTriple) (found bool, err error) { if c.IsStubbed() { return false, ChainLinkError{"cannot check stubbed link against the merkle tree"} } found = false if t != nil && c.GetSeqno() == t.Seqno { c.G().Log.Debug("| Found chain tail advertised in Merkle tree @%d", int(t.Seqno)) found = true if !c.id.Eq(t.LinkID) { err = fmt.Errorf("Bad chain ID at seqno=%d", int(t.Seqno)) } } return } func getSigVersionFromPayload(payload []byte) (SigVersion, error) { var err error var i int64 if i, err = jsonparserw.GetInt(payload, "body", "version"); err != nil { return KeybaseNullSigVersion, ChainLinkError{"link is missing a version field"} } return SigVersion(int(i)), nil } func (tmp *ChainLinkUnpacked) parseHighSkipFromPayload(payload []byte) (*HighSkip, error) { hs, dataType, _, err := jsonparserw.Get(payload, "high_skip") // high_skip is optional, but must be an object if it exists if err != nil { switch pkgerrors.Cause(err) { case jsonparser.KeyPathNotFoundError: return nil, nil default: return nil, err } } if dataType != jsonparser.Object { return nil, ChainLinkError{fmt.Sprintf("When provided, expected high_skip to be a JSON object, was %v.", dataType)} } highSkipSeqnoInt, err := jsonparserw.GetInt(hs, "seqno") if err != nil { return nil, err } // highSkipHash can either be null (zero-value of a LinkID) or a hexstring. // We call GetString first instead of Get so we only parse the value // twice for the first link. highSkipHashStr, err := jsonparserw.GetString(hs, "hash") var highSkipHash LinkID if err != nil { // If there was an error parsing as a string, make sure the value is null. _, dataType, _, getErr := jsonparserw.Get(hs, "hash") if getErr != nil { return nil, getErr } if dataType != jsonparser.Null { return nil, ChainLinkError{ fmt.Sprintf("high_skip.hash was neither a valid string (%v) nor null.", err.Error()), } } } else { highSkipHash, err = LinkIDFromHex(highSkipHashStr) if err != nil { return nil, err } } highSkip := NewHighSkip(keybase1.Seqno(highSkipSeqnoInt), highSkipHash) return &highSkip, nil } func (tmp *ChainLinkUnpacked) unpackPayloadJSON(g *GlobalContext, payload []byte, linkID LinkID) error { if !isJSONObject(payload, linkID) { return ChainLinkError{"chain link is not a valid JSON object as expected; found leading junk"} } if s, err := jsonparserw.GetString(payload, "body", "key", "fingerprint"); err == nil { if tmp.pgpFingerprint, err = PGPFingerprintFromHex(s); err != nil { return err } } if s, err := jsonparserw.GetString(payload, "body", "key", "kid"); err == nil { tmp.kid = keybase1.KIDFromString(s) } if s, err := jsonparserw.GetString(payload, "body", "key", "eldest_kid"); err == nil { tmp.eldestKID = keybase1.KIDFromString(s) } var err error tmp.username, err = jsonparserw.GetString(payload, "body", "key", "username") if err != nil { return err } suid, err := jsonparserw.GetString(payload, "body", "key", "uid") if err != nil { return err } if tmp.uid, err = UIDFromHex(suid); err != nil { return err } if prev, err := jsonparserw.GetString(payload, "prev"); err == nil { tmp.prev, err = LinkIDFromHex(prev) if err != nil { return err } } highSkip, err := tmp.parseHighSkipFromPayload(payload) if err != nil { return err } tmp.highSkip = highSkip tmp.typ, err = jsonparserw.GetString(payload, "body", "type") if err != nil { return err } tmp.ctime, err = jsonparserw.GetInt(payload, "ctime") if err != nil { return err } seqno, err := jsonparserw.GetInt(payload, "seqno") if err != nil { return err } tmp.seqno = keybase1.Seqno(seqno) if tmp.HasRevocations(payload) { tmp.hasRevocations = true } // Assume public unless its a number tmp.seqType = keybase1.SeqType_PUBLIC if seqTypeInt, err := jsonparserw.GetInt(payload, "seq_type"); err == nil { tmp.seqType = keybase1.SeqType(seqTypeInt) } // Assume false if unsupported tmp.ignoreIfUnsupported = SigIgnoreIfUnsupported(false) if ignore, err := jsonparserw.GetBoolean(payload, "ignore_if_unsupported"); err == nil { tmp.ignoreIfUnsupported = SigIgnoreIfUnsupported(ignore) } // Due to an earlier error, it's possible for the merkle root that we signed over // to be in one of two places, so check both. if i, err := jsonparserw.GetInt(payload, "body", "merkle_root", "seqno"); err == nil { tmp.merkleSeqno = keybase1.Seqno(i) } else if i, err := jsonparserw.GetInt(payload, "merkle_root", "seqno"); err == nil { tmp.merkleSeqno = keybase1.Seqno(i) } // Hash meta was only ever in the correct place (within body) if s, err := jsonparserw.GetString(payload, "body", "merkle_root", "hash_meta"); err == nil { tmp.merkleHashMeta, err = keybase1.HashMetaFromString(s) if err != nil { return err } } ei, err := jsonparserw.GetInt(payload, "expire_in") if err != nil { return err } tmp.etime = tmp.ctime + ei tmp.payloadHash = fixAndHashPayload(g, payload, linkID) if tmp.sigVersion == KeybaseSignatureV2 { tmp.payloadV2 = payload } return nil } func (c *ChainLink) UnpackLocal(payload []byte) (err error) { tmp := ChainLinkUnpacked{} err = tmp.unpackPayloadJSON(c.G(), payload, c.id) if err == nil { tmp.payloadLocal = payload c.unpacked = &tmp } return } func (c *ChainLink) UnpackComputedKeyInfos(data []byte) error { if data == nil { return nil } var tmp ComputedKeyInfos tmp.SetGlobalContext(c.G()) if err := json.Unmarshal(data, &tmp); err == nil { c.cki = &tmp } else { return err } return nil } func (c *ChainLink) unpackStubbed(raw string) error { ol, err := DecodeStubbedOuterLinkV2(raw) if err != nil { return err } if ol.SeqType == 0 { // Assume public if unset ol.SeqType = keybase1.SeqType_PUBLIC } if !ol.IgnoreIfUnsupported.Bool() && !ol.LinkType.IsSupportedType() { return ChainLinkStubbedUnsupportedError{fmt.Sprintf("Stubbed link with type %d is unknown and not marked with IgnoreIfUnsupported", ol.LinkType)} } c.id = ol.LinkID() // Because the outer link does not have a highSkip parent object, we check // for the nullity of highSkipSeqno to see if highSkip should be set, since // a null highSkipHash is valid when specifying highSkip=0. var highSkipPtr *HighSkip if ol.HighSkipSeqno != nil { highSkip := NewHighSkip(*ol.HighSkipSeqno, *ol.HighSkipHash) highSkipPtr = &highSkip } c.unpacked = &ChainLinkUnpacked{ prev: ol.Prev, seqno: ol.Seqno, seqType: ol.SeqType, ignoreIfUnsupported: ol.IgnoreIfUnsupported, highSkip: highSkipPtr, sigVersion: ol.Version, outerLinkV2: ol, stubbed: true, } return nil } func (c *ChainLink) unpackFromLocalStorage(m MetaContext, selfUID keybase1.UID, packed []byte) error { if s, err := jsonparserw.GetString(packed, "s2"); err == nil { return c.unpackStubbed(s) } tmp := ChainLinkUnpacked{} var err error tmp.sig, err = jsonparserw.GetString(packed, "sig") if err != nil { return err } // Beware that this is server-untrusted data at this point. We'll have to check it // before we can exit without error (see below). tmp.sigVersion = KeybaseSignatureV1 if sv, err := jsonparserw.GetInt(packed, "sig_version"); err == nil { tmp.sigVersion = SigVersion(int(sv)) if tmp.sigVersion != KeybaseSignatureV1 && tmp.sigVersion != KeybaseSignatureV2 { return ChainLinkError{fmt.Sprintf("Bad sig_version: expected 1 or 2 but got %d", tmp.sigVersion)} } } if i, err := jsonparserw.GetInt(packed, "merkle_seqno"); err == nil { tmp.firstAppearedMerkleSeqnoUnverified = keybase1.Seqno(i) } var payload []byte switch tmp.sigVersion { case KeybaseSignatureV1: // Use the payload from the signature here, since we always get the full signature with // the attached payload for V1 signatures. Note that we redundantly check that the payload // matches the sig in verifyPayloadV1() on the way out of this Unpack function. payload, err = tmp.Payload() if err != nil { return err } case KeybaseSignatureV2: // use the payload in payload_json data, _, _, err := jsonparserw.Get(packed, "payload_json") if err != nil { return err } // unquote it sdata, err := strconv.Unquote(`"` + string(data) + `"`) if err != nil { return err } payload = []byte(sdata) default: err = ChainLinkError{fmt.Sprintf("unhandled signature version %d", tmp.sigVersion)} return err } // unpack the payload if err := tmp.unpackPayloadJSON(c.G(), payload, c.id); err != nil { m.Debug("unpack payload json err: %s", err) return err } // We previously took the server's word on what version we wanted, but now // we're going to check that it matches what we actually sign over -- what's // in the JSON payload. If it doesn't match, the we error out right here. if err := tmp.assertPayloadSigVersionMatchesHint(payload); err != nil { return err } var sigKID, serverKID, payloadKID keybase1.KID if tmp.sigVersion == KeybaseSignatureV2 { var ol2 *OuterLinkV2WithMetadata ol2, err = DecodeOuterLinkV2(tmp.sig) if err != nil { return err } if ol2.SeqType == 0 { // Assume public if unset ol2.SeqType = keybase1.SeqType_PUBLIC } tmp.outerLinkV2 = ol2 sigKID = ol2.kid } payloadKID = tmp.kid if kid, err := jsonparserw.GetString(packed, "kid"); err == nil { serverKID = keybase1.KIDFromString(kid) } if !payloadKID.IsNil() && !serverKID.IsNil() && !payloadKID.Equal(serverKID) { return ChainLinkKIDMismatchError{ fmt.Sprintf("Payload KID (%s) doesn't match server KID (%s).", payloadKID, serverKID), } } if !payloadKID.IsNil() && !sigKID.IsNil() && !payloadKID.Equal(sigKID) { return ChainLinkKIDMismatchError{ fmt.Sprintf("Payload KID (%s) doesn't match sig KID (%s).", payloadKID, sigKID), } } if !serverKID.IsNil() && !sigKID.IsNil() && !serverKID.Equal(sigKID) { return ChainLinkKIDMismatchError{ fmt.Sprintf("Server KID (%s) doesn't match sig KID (%s).", serverKID, sigKID), } } if tmp.kid.IsNil() && !sigKID.IsNil() { tmp.kid = sigKID } if tmp.kid.IsNil() && !serverKID.IsNil() { tmp.kid = serverKID } // Note, we can still be in a situation in which don't know any kids! // That would be bad *if* we need to verify the signature for this link. // only unpack the proof_text_full if owner of this link if tmp.uid.Equal(selfUID) { if pt, err := jsonparserw.GetString(packed, "proof_text_full"); err == nil { tmp.proofText = pt } } c.unpacked = &tmp // IF we're loaded from *trusted* storage, like our local // DB, then we can skip verification later if b, err := jsonparserw.GetBoolean(packed, "sig_verified"); err == nil && b { c.sigVerified = true m.VLogf(VLog1, "| Link is marked as 'sig_verified'") if ckidata, _, _, err := jsonparserw.Get(packed, "computed_key_infos"); err == nil { if uerr := c.UnpackComputedKeyInfos(ckidata); uerr != nil { m.Warning("Problem unpacking computed key infos: %s", uerr) } } } if b, err := jsonparserw.GetBoolean(packed, "hash_verified"); err == nil && b { c.hashVerified = true } if b, err := jsonparserw.GetBoolean(packed, "chain_verified"); err == nil && b { c.chainVerified = true } if b, err := jsonparserw.GetBoolean(packed, "payload_verified"); err == nil && b { c.payloadVerified = true } if i, err := jsonparserw.GetInt(packed, "disk_version"); err == nil { c.diskVersion = int(i) } // It is not acceptable to digest sig_id from the server, but we do derive it // as we unpack the server reply (see VerifyLink), and it is acceptable to // read it out of a locally-stored chainlink. Note this field is required, // and if we don't have it, there has been a major problem. s, err := jsonparserw.GetString(packed, "sig_id") if err != nil { return err } c.unpacked.sigID, err = keybase1.SigIDFromString(s) if err != nil { return err } // sigID is set as a side effect of verifying the link. Make sure we do that // on the way out of this function, before we return success. But it's not // needed in the cased of a stubbed V2 link. err = c.VerifyLink() if err != nil { return err } c.G().VDL.Log(VLog1, "| Unpacked Link %s", c.id) return nil } func (tmp *ChainLinkUnpacked) Payload() ([]byte, error) { // local track payloads are stored in ChainLinkUnpacked. // if anything there, use it: if len(tmp.payloadLocal) > 0 { return tmp.payloadLocal, nil } switch tmp.sigVersion { case KeybaseSignatureV1: // v1 links have the payload inside the sig sigPayload, _, _, err := SigExtractPayloadAndKID(tmp.sig) return sigPayload, err case KeybaseSignatureV2: // v2 links have the payload in ChainLinkUnpacked return tmp.payloadV2, nil default: return nil, ChainLinkError{msg: fmt.Sprintf("unexpected signature version: %d", tmp.sigVersion)} } } func (tmp *ChainLinkUnpacked) assertPayloadSigVersionMatchesHint(payload []byte) error { payloadVersion, err := getSigVersionFromPayload(payload) if err != nil { return err } if tmp.sigVersion != payloadVersion { return ChainLinkError{msg: fmt.Sprintf("Big sigchain version hint from server: %d != %d", tmp.sigVersion, payloadVersion)} } return nil } func (c *ChainLink) CheckNameAndID(s NormalizedUsername, i keybase1.UID) error { // We can't check name and ID if we have compacted chain link with no // payload JSON if c.IsStubbed() { return nil } if c.unpacked.uid.NotEqual(i) { return UIDMismatchError{ fmt.Sprintf("UID mismatch %s != %s in Link %s", c.unpacked.uid, i, c.id), } } if !s.Eq(NewNormalizedUsername(c.unpacked.username)) { return NewBadUsernameErrorWithFullMessage( fmt.Sprintf("Username mismatch %s != %s in Link %s", c.unpacked.username, s, c.id)) } return nil } func ComputeLinkID(d []byte) LinkID { h := sha256.Sum256(d) return LinkID(h[:]) } func (c *ChainLink) getPayloadHash() LinkID { if c.unpacked == nil { return nil } return c.unpacked.payloadHash } func (c *ChainLink) verifyHashV2() error { if c.hashVerified { return nil } ol := c.unpacked.outerLinkV2 if ol == nil { return fmt.Errorf("nil outer link V2 unpacking") } if h := ol.LinkID(); !FastByteArrayEq(h, c.id) { return SigchainV2MismatchedHashError{} } c.hashVerified = true c.G().LinkCache().Mutate(c.id, func(c *ChainLink) { c.hashVerified = true }) return nil } func (c *ChainLink) verifyHashV1() error { if c.hashVerified { return nil } h := c.getPayloadHash() if !FastByteArrayEq(h[:], c.id) { return fmt.Errorf("hash mismatch in verifyHashV1") } c.hashVerified = true c.G().LinkCache().Mutate(c.id, func(c *ChainLink) { c.hashVerified = true }) return nil } func (c *ChainLink) markChainVerified() { c.chainVerified = true c.G().LinkCache().Mutate(c.id, func(c *ChainLink) { c.chainVerified = true }) } // getFixedPayload usually just returns c.unpacked.Payload(), but sometimes // it adds extra whitespace to work around server-side bugs. func (c ChainLink) getFixedPayload() []byte { payload, err := c.unpacked.Payload() if err != nil { return nil } return c.fixPayload(payload, c.id) } func (c *ChainLink) fixPayload(payload []byte, linkID LinkID) []byte { if s, ok := badWhitespaceChainLinks[linkID.Export()]; ok { if payload[len(payload)-1] != '\n' { c.G().Log.Debug("Fixing payload by adding newline on link '%s': %s", linkID.Export(), s) // Careful not to mutate the passed in payload via append. So make // a copy first. ret := make([]byte, len(payload)) copy(ret, payload) ret = append(ret, '\n') return ret } } return payload } // fixAndHashPayload does the inverse of ChainLink#fixPayload. It strips off a trailing // newline for buggy signature payloads, and then computes the hash of the result. This is // necessary now that we are computing chain link IDs from signature bodies. func fixAndHashPayload(g *GlobalContext, payload []byte, linkID LinkID) []byte { toHash := payload if s, ok := badWhitespaceChainLinks[linkID.Export()]; ok { last := len(payload) - 1 if payload[last] == '\n' { g.Log.Debug("Fixing payload hash by stripping newline on link '%s': %s", linkID.Export(), s) toHash = payload[0:last] } } ret := sha256.Sum256(toHash) return ret[:] } // two chainlinks in the database have leading padding via space characters // or newline characters. In these two cases, we strip off leading space // before checking if they are JSON objects. In all other cases, the first // character must be '{' var paddedChainLinks = map[keybase1.LinkID]bool{ "ebcf3fab043970beee1198f16098f411331b7dbaa865a285908d82fc58df1577": true, "c094f59c77da5e7d4023025744f8c533a098b2136f2113dc02b0745568c2faa3": true, } func stripPadding(in []byte) []byte { for i, b := range in { if b != ' ' && b != '\n' { return in[i:] } } return []byte{} } func isPadded(in []byte) bool { return len(in) > 0 && (in[0] == ' ' || in[0] == '\n') } func isJSONObject(payload []byte, linkID LinkID) bool { if isPadded(payload) && paddedChainLinks[linkID.Export()] { payload = stripPadding(payload) } return msgpack.IsJSONObject(payload) } func inferSigVersion(payload []byte, linkID LinkID) SigVersion { // Version 1 payloads are JSON and must start with an opening '{' if isJSONObject(payload, linkID) { return KeybaseSignatureV1 } // Version 2 payloads are Msgpack and must be arrays, so they must // fit the following requirements. The case where b == 0xdc or // b = 0xdd are far-fetched, since that would mean a large or very // large packing. But still, allow any valid array up front. if msgpack.IsEncodedMsgpackArray(payload) { return KeybaseSignatureV2 } // We didn't find anything useful, so mark it a "none" return KeybaseNullSigVersion } func assertCorrectSigVersion(expected SigVersion, payload []byte, linkID LinkID) error { vInferred := inferSigVersion(payload, linkID) if vInferred != expected { return ChainLinkError{msg: fmt.Sprintf("chainlink in wrong format; expected version=%d but payload was %d", expected, vInferred)} } return nil } func (c *ChainLink) getSigPayload() ([]byte, error) { if c.IsStubbed() { return nil, ChainLinkError{"Cannot verify sig with nil outer link v2"} } v := c.unpacked.sigVersion var ret []byte switch v { case KeybaseSignatureV1: ret = c.getFixedPayload() case KeybaseSignatureV2: ret = c.unpacked.outerLinkV2.raw default: return nil, ChainLinkError{msg: fmt.Sprintf("unexpected signature version: %d", c.unpacked.sigVersion)} } err := assertCorrectSigVersion(v, ret, c.id) if err != nil { return nil, err } return ret, nil } func (c *ChainLink) verifyPayloadV2() error { if c.payloadVerified { return nil } ol := c.unpacked.outerLinkV2 if ol == nil { return ChainLinkError{"no outer V2 structure available"} } version := KeybaseSignatureV2 seqno := c.getSeqnoFromPayload() prev := c.getPrevFromPayload() curr := c.getPayloadHash() innerVersion := c.unpacked.sigVersion if innerVersion != version { return ChainLinkError{fmt.Sprintf("In chainlink v2, expected inner link to match; got %d", innerVersion)} } ignoreIfUnsupported := c.getIgnoreIfUnsupportedFromPayload() linkType, err := c.GetSigchainV2TypeFromInner(ignoreIfUnsupported) if err != nil { return err } seqType := c.getSeqTypeFromPayload() highSkip := c.getHighSkipFromPayload() if err := ol.AssertFields(version, seqno, prev, curr, linkType, seqType, ignoreIfUnsupported, highSkip); err != nil { return err } c.markPayloadVerified(ol.sigID) return nil } func (c *ChainLink) markPayloadVerified(sigid keybase1.SigID) { if c.unpacked != nil { c.unpacked.sigID = sigid } c.payloadVerified = true c.G().LinkCache().Mutate(c.id, func(c *ChainLink) { c.payloadVerified = true }) } func (c *ChainLink) verifyPayloadV1() error { if c.payloadVerified { return nil } sigid, err := SigAssertPayload(c.unpacked.sig, c.getFixedPayload()) if err != nil { return err } params := keybase1.SigIDSuffixParametersFromTypeAndVersion(c.unpacked.typ, keybase1.SigVersion(1)) c.markPayloadVerified(sigid.ToSigID(params)) return nil } func (c *ChainLink) getSeqnoFromPayload() keybase1.Seqno { if c.unpacked != nil { return c.unpacked.seqno } return keybase1.Seqno(-1) } func (c *ChainLink) GetSeqno() keybase1.Seqno { return c.unpacked.seqno } func (c *ChainLink) GetHighSkip() *HighSkip { return c.unpacked.highSkip } func (c *ChainLink) GetSigID() keybase1.SigID { return c.unpacked.sigID } func (c *ChainLink) GetSigCheckCache() (cki *ComputedKeyInfos) { if c.sigVerified && c.cki != nil { cki = c.cki } return cki } func (c *ChainLink) PutSigCheckCache(cki *ComputedKeyInfos) { c.G().Log.Debug("Caching SigCheck for link %s (version: %d)", c.id, cki.Version) c.sigVerified = true c.dirty = true c.cki = cki c.G().LinkCache().Mutate(c.id, func(c *ChainLink) { c.cki = cki }) } func (c *ChainLink) VerifySigWithKeyFamily(ckf ComputedKeyFamily) (err error) { var key GenericKey var verifyKID keybase1.KID var sigIDBase keybase1.SigIDBase var params keybase1.SigIDSuffixParameters if c.IsStubbed() { return ChainLinkError{"cannot verify signature -- none available; is this a stubbed out link?"} } if c.unpacked != nil && c.unpacked.sigDropped { return ChainLinkError{"cannot verify signature -- none available; sig dropped intentionally."} } verifyKID, err = c.checkServerSignatureMetadata(ckf) if err != nil { return err } if key, _, err = ckf.FindActiveSibkeyAtTime(verifyKID, c.GetCTime()); err != nil { return err } if err = c.VerifyLink(); err != nil { return err } var sigPayload []byte sigPayload, err = c.getSigPayload() if err != nil { return err } if sigIDBase, err = key.VerifyString(c.G().Log, c.unpacked.sig, sigPayload); err != nil { return BadSigError{err.Error()} } params = keybase1.SigIDSuffixParametersFromTypeAndVersion(c.unpacked.typ, keybase1.SigVersion(c.unpacked.sigVersion)) c.unpacked.sigID = sigIDBase.ToSigID(params) return nil } func putLinkToCache(m MetaContext, link *ChainLink) { m.G().LinkCache().Put(m, link.id, link.Copy()) } func NewChainLink(g *GlobalContext, parent *SigChain, id LinkID) *ChainLink { return &ChainLink{ Contextified: NewContextified(g), parent: parent, id: id, } } func ImportLinkFromStorage(m MetaContext, id LinkID, selfUID keybase1.UID) (*ChainLink, error) { link, ok := m.G().LinkCache().Get(id) if ok { link.Contextified = NewContextified(m.G()) return &link, nil } var ret *ChainLink data, _, err := m.G().LocalDb.GetRaw(DbKey{Typ: DBLink, Key: id.String()}) if err == nil && data != nil { // May as well recheck onload (maybe revisit this) ret = NewChainLink(m.G(), nil, id) if err = ret.unpackFromLocalStorage(m, selfUID, data); err != nil { return nil, err } ret.storedLocally = true m.G().LinkCache().Put(m, id, ret.Copy()) } return ret, err } func (c *ChainLink) VerifyLink() error { v := c.unpacked.sigVersion switch v { case 1: return c.verifyLinkV1() case 2: return c.verifyLinkV2() default: return ChainLinkError{msg: fmt.Sprintf("unexpected signature version: %d", v)} } } func (c *ChainLink) verifyLinkV1() error { if err := c.verifyHashV1(); err != nil { return err } return c.verifyPayloadV1() } func (c *ChainLink) verifyLinkV2() error { if err := c.verifyHashV2(); err != nil { return err } // We might not have an unpacked payload at all, if it's a V2 link // without a body (for BW savings) if c.IsStubbed() { return nil } return c.verifyPayloadV2() } func (c *ChainLink) GetSigchainV2TypeFromInner(ignoreIfUnsupported SigIgnoreIfUnsupported) (SigchainV2Type, error) { if c.unpacked == nil || c.unpacked.typ == "" { return SigchainV2TypeNone, errors.New("chain link not unpacked") } return SigchainV2TypeFromV1TypeAndRevocations(c.unpacked.typ, SigHasRevokes(c.HasRevocations()), ignoreIfUnsupported) } func (c *ChainLink) GetSigchainV2TypeFromV2Shell() (SigchainV2Type, error) { if c.unpacked == nil { return SigchainV2TypeNone, errors.New("GetSigchainV2TypeFromV2Shell: chain link not unpacked") } if c.unpacked.outerLinkV2 == nil { return SigchainV2TypeNone, errors.New("GetSigchainV2TypeFromV2Shell: chain link has no v2 shell") } return c.unpacked.outerLinkV2.LinkType, nil } // GetSigchainV2Type is a helper function for getting a ChainLink's type. If it // is a v2 link (that may or may not be stubbed), return the type from the // outer link, otherwise from the inner link. func (c *ChainLink) GetSigchainV2Type() (SigchainV2Type, error) { if c.unpacked == nil { return SigchainV2TypeNone, errors.New("chain link is not unpacked") } if c.unpacked.outerLinkV2 == nil && c.unpacked.typ == "" { return SigchainV2TypeNone, errors.New("chain inner link type is not unpacked, and has no v2 shell") } if c.unpacked.outerLinkV2 != nil { return c.GetSigchainV2TypeFromV2Shell() } return c.GetSigchainV2TypeFromInner(c.GetIgnoreIfSupported()) } func (c *ChainLink) checkServerSignatureMetadata(ckf ComputedKeyFamily) (ret keybase1.KID, err error) { var serverKID, linkKID, verifyKID keybase1.KID // PC: I'm not sure what exactly this was trying to do since // c.packed.kid can only be equal to c.unpacked.kid at this point. // The following two lines result in the least changes below: serverKID = c.unpacked.kid linkKID = c.unpacked.kid if linkKID.Exists() && serverKID.Exists() && linkKID.NotEqual(serverKID) { // Check the KID. This is actually redundant of a check we do in Unpack(), // but I'm keeping it here in case we change the way we unpack in the // future. --jacko return ret, ChainLinkKIDMismatchError{ fmt.Sprintf("Payload KID (%s) doesn't match server KID (%s).", linkKID, serverKID), } } if serverKID.Exists() { verifyKID = serverKID } if linkKID.Exists() { verifyKID = linkKID } if verifyKID.IsNil() { return ret, ChainLinkError{"cannot verify signature without a KID"} } serverKey, err := ckf.FindKeyWithKIDUnsafe(verifyKID) if err != nil { return ret, err } // Check the fingerprint. if c.unpacked.pgpFingerprint != nil { payloadFingerprintStr := c.unpacked.pgpFingerprint.String() serverFingerprintStr := "" if fp := GetPGPFingerprintFromGenericKey(serverKey); fp != nil { serverFingerprintStr = fp.String() } if payloadFingerprintStr != serverFingerprintStr { return ret, ChainLinkFingerprintMismatchError{ fmt.Sprintf("Payload fingerprint (%s) did not match server key (%s).", payloadFingerprintStr, serverFingerprintStr), } } } return verifyKID, nil } func (c *ChainLink) Store(m MetaContext) (didStore bool, err error) { m.VLogf(VLog1, "| Storing Link %s...", c.id) if c.storedLocally && !c.dirty { m.VLogf(VLog1, "| Bailed on link %s since wasn't dirty...", c.id) return didStore, nil } if err = c.VerifyLink(); err != nil { return false, err } if !c.hashVerified || (!c.IsStubbed() && !c.payloadVerified) || !c.chainVerified { err = fmt.Errorf("Internal error; should have been verified in Store(); hashVerified=%v, isStubbed=%v, payloadVerified=%v, chainVerified=%v", c.hashVerified, c.IsStubbed(), c.payloadVerified, c.chainVerified) return false, err } packed, err := c.Pack() if err != nil { return false, err } key := DbKey{Typ: DBLink, Key: c.id.String()} // Don't write with any aliases if err = m.G().LocalDb.Put(key, nil, packed); err != nil { return false, err } m.VLogf(VLog1, "| Store Link %s", c.id) c.storedLocally = true c.dirty = false return true, nil } func (c *ChainLink) GetPGPFingerprint() *PGPFingerprint { return c.unpacked.pgpFingerprint } func (c *ChainLink) GetKID() keybase1.KID { return c.unpacked.kid } func (c *ChainLink) MatchFingerprint(fp PGPFingerprint) bool { return c.unpacked.pgpFingerprint != nil && fp.Eq(*c.unpacked.pgpFingerprint) } func (c *ChainLink) ToEldestKID() keybase1.KID { if !c.unpacked.eldestKID.IsNil() { return c.unpacked.eldestKID } // For links that don't explicitly specify an eldest KID, it's implied // that we're starting a new subchain, so the signing KID is the // eldest. return c.GetKID() } // ToLinkSummary converts a ChainLink into a MerkleTriple object. func (c ChainLink) ToMerkleTriple() *MerkleTriple { if c.IsStubbed() { return nil } return &MerkleTriple{ Seqno: c.GetSeqno(), LinkID: c.id, SigID: c.GetSigID().StripSuffix(), } } // ========================================================================= // IsInCurrentFamily checks to see if the given chainlink // was signed by a key in the current family. func (c *ChainLink) IsInCurrentFamily(u *User) bool { eldest := u.GetEldestKID() if eldest.IsNil() { return false } return eldest.Equal(c.ToEldestKID()) } // ========================================================================= func (c *ChainLink) Typed() TypedChainLink { return c.typed } func (c *ChainLink) Copy() ChainLink { var unpacked ChainLinkUnpacked if c.unpacked != nil { unpacked = *c.unpacked } r := *c r.SetGlobalContext(nil) r.parent = nil r.chainVerified = c.chainVerified r.hashVerified = c.hashVerified r.payloadVerified = c.payloadVerified r.unpacked = &unpacked if c.cki != nil { r.cki = c.cki.ShallowCopy() } return r } func (c ChainLink) LinkID() LinkID { return c.id } func (c ChainLink) AllowStubbing() bool { if c.unpacked.outerLinkV2 == nil { return false } return c.unpacked.outerLinkV2.LinkType.AllowStubbing() } // IsHighUserLink determines whether a chainlink counts as "high" in a user's chain, // which is defined as an Eldest link, a link with seqno=1, a link that is Sibkey, // PGPUpdate, Revoke, or any link that is revoking. func (c ChainLink) IsHighUserLink(mctx MetaContext, uid keybase1.UID) (bool, error) { v2Type, err := c.GetSigchainV2Type() if err != nil { return false, err } hardcodedEldest := false if c.GetSeqno() > 1 { prevLink := c.parent.GetLinkFromSeqno(c.GetSeqno() - 1) if prevLink == nil { return false, ChainLinkWrongSeqnoError{} } hardcodedEldest, err = isSubchainStart(mctx, &c, prevLink, uid) if err != nil { return false, err } } isFirstLink := v2Type == SigchainV2TypeEldest || c.GetSeqno() == 1 || hardcodedEldest isNewHighLink := isFirstLink || v2Type == SigchainV2TypeRevoke || v2Type == SigchainV2TypeWebServiceBindingWithRevoke || v2Type == SigchainV2TypeCryptocurrencyWithRevoke || v2Type == SigchainV2TypeSibkey || v2Type == SigchainV2TypeWotVouchWithRevoke || v2Type == SigchainV2TypePGPUpdate return isNewHighLink, nil } // ExpectedNextHighSkip returns the expected highSkip of the immediately // subsequent link in the chain (which may not exist yet). This function can // only be called after VerifyChain has processed the chainLink, and set // c.computedHighSkip. func (c ChainLink) ExpectedNextHighSkip(mctx MetaContext, uid keybase1.UID) (HighSkip, error) { isHigh, err := c.IsHighUserLink(mctx, uid) if err != nil { return HighSkip{}, err } if isHigh { return NewHighSkip(c.GetSeqno(), c.id), nil } if c.computedHighSkip == nil { return HighSkip{}, NewUserReverifyNeededError("Expected to have already computed this link's HighSkip, but it was not computed.") } return *c.computedHighSkip, nil }