github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/merklestore/store.go (about) 1 // Copyright 2017 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package merklestore 5 6 import ( 7 "crypto/sha512" 8 "encoding/hex" 9 "encoding/json" 10 "fmt" 11 "os" 12 "sync" 13 "time" 14 15 "github.com/keybase/client/go/libkb" 16 "github.com/keybase/client/go/profiling" 17 "github.com/keybase/client/go/protocol/keybase1" 18 ) 19 20 type MerkleStoreError struct { 21 msg string 22 } 23 24 func (e MerkleStoreError) Error() string { 25 return fmt.Sprintf("MerkleStore: %s", e.msg) 26 } 27 28 func NewMerkleStoreError(msgf string, a ...interface{}) MerkleStoreError { 29 return MerkleStoreError{msg: fmt.Sprintf(msgf, a...)} 30 } 31 32 // Bump this to ignore existing cache entries. 33 const dbVersion = 1 34 35 type dbKit struct { 36 DBVersion int 37 Hash keybase1.MerkleStoreKitHash 38 Kit keybase1.MerkleStoreKit 39 } 40 41 // MerkleStore is the way verify data stored on the server matches the hash 42 // which is published in the merkle root. This allows an auditable trail for 43 // data the clients fetch from the server and use for proof or other 44 // validation. 45 // Talks to MerkleClient 46 // Has an in-memory and LocalDB cache. 47 type MerkleStoreImpl struct { 48 libkb.Contextified 49 sync.Mutex 50 51 // human readable tag for logs/error reporting 52 tag string 53 54 // server endpoint to fetch stored data 55 endpoint string 56 57 // latest supported version 58 supportedVersion keybase1.MerkleStoreSupportedVersion 59 60 // getter for merkle hash we want to verify against 61 getHash func(libkb.MerkleRoot) string 62 63 // path to load kit from a file while debugging, if present this will be 64 // used instead of requesting data from the server, helpful for debugging. 65 kitFilename string 66 67 mem *dbKit 68 } 69 70 var _ libkb.MerkleStore = (*MerkleStoreImpl)(nil) 71 72 func NewMerkleStore(g *libkb.GlobalContext, tag, endpoint, kitFilename string, supportedVersion keybase1.MerkleStoreSupportedVersion, 73 getHash func(root libkb.MerkleRoot) string) libkb.MerkleStore { 74 return &MerkleStoreImpl{ 75 Contextified: libkb.NewContextified(g), 76 tag: tag, 77 endpoint: endpoint, 78 kitFilename: kitFilename, 79 supportedVersion: supportedVersion, 80 getHash: getHash, 81 } 82 } 83 84 type merkleStoreKitT struct { 85 KitVersion int `json:"kit_version"` 86 Ctime int `json:"ctime"` 87 // Versioned entries of the store 88 Tab map[int]json.RawMessage `json:"tab"` 89 } 90 91 // GetLatestEntry returns the latest entry for the given MerkleStore. 92 // Returns (nil, nil) if knownHash is the active entry. 93 func (s *MerkleStoreImpl) GetLatestEntryWithKnown(m libkb.MetaContext, knownHash *keybase1.MerkleStoreKitHash) (ret *keybase1.MerkleStoreEntry, err error) { 94 tracer := m.G().CTimeTracer(m.Ctx(), "MerkleStore.GetLatestEntryWithKnown", false) 95 defer tracer.Finish() 96 kitJSON, hash, err := s.getKitString(m, knownHash, tracer) 97 if err != nil { 98 return nil, err 99 } 100 if kitJSON == "" { 101 if knownHash != nil && hash == *knownHash { 102 return nil, nil 103 } 104 return nil, NewMerkleStoreError("unexpected empty merkle store response") 105 } 106 107 tracer.Stage("unmarshal") 108 var kit merkleStoreKitT 109 if err = json.Unmarshal([]byte(kitJSON), &kit); err != nil { 110 return nil, NewMerkleStoreError("unmarshalling kit: %s", err) 111 } 112 113 sub, ok := kit.Tab[int(s.supportedVersion)] 114 if !ok { 115 return nil, NewMerkleStoreError("missing %s for version: %d", s.tag, s.supportedVersion) 116 } 117 if len(sub) == 0 { 118 return nil, NewMerkleStoreError("empty %s for version: %d", s.tag, s.supportedVersion) 119 } 120 121 return &keybase1.MerkleStoreEntry{ 122 Hash: hash, 123 Entry: keybase1.MerkleStoreEntryString(sub), 124 }, nil 125 } 126 127 // GetLatestEntry returns the latest entry for the given MerkleStore 128 func (s *MerkleStoreImpl) GetLatestEntry(m libkb.MetaContext) (keybase1.MerkleStoreEntry, error) { 129 ret, err := s.GetLatestEntryWithKnown(m, nil) 130 if err != nil { 131 return keybase1.MerkleStoreEntry{}, err 132 } 133 if ret == nil { 134 return keybase1.MerkleStoreEntry{}, NewMerkleStoreError("unexpected empty merkle store response") 135 } 136 return *ret, nil 137 } 138 139 // Get stored kit as a string. First it makes sure that the merkle root is 140 // recent enough. Using the hash from that, it fetches from in-memory falling 141 // back to db falling back to server. 142 // A special case: Returns ("", hash, nil) if hash == knownHash. 143 func (s *MerkleStoreImpl) getKitString(m libkb.MetaContext, knownHash *keybase1.MerkleStoreKitHash, tracer profiling.TimeTracer) ( 144 keybase1.MerkleStoreKit, keybase1.MerkleStoreKitHash, error) { 145 146 // Use a file instead if specified. 147 if len(s.kitFilename) > 0 { 148 m.Debug("MerkleStore: using kit file: %s", s.kitFilename) 149 return s.readFile(s.kitFilename) 150 } 151 152 mc := m.G().GetMerkleClient() 153 if mc == nil { 154 return "", "", NewMerkleStoreError("no MerkleClient available") 155 } 156 157 s.Lock() 158 defer s.Unlock() 159 160 tracer.Stage("LastRoot") 161 root := mc.LastRoot(m) 162 163 // Try to refresh the root if it is too old, but keep going in case of error 164 if recentRoot, err := mc.FetchRootFromServer(m, libkb.MerkleStoreShouldRefresh); err == nil { 165 root = recentRoot 166 } else { 167 m.Debug("MerkleStore: could not refresh merkle root: %s", err) 168 } 169 170 if root == nil { 171 return "", "", NewMerkleStoreError("no merkle root") 172 } 173 174 if s.pastDue(m, root.Fetched(), libkb.MerkleStoreRequireRefresh) { 175 // The root is still too old, even after an attempted refresh. 176 m.Debug("MerkleStore: merkle root too old") 177 return "", "", NewMerkleStoreError("merkle root too old: %v %s", seqnoWrap(root.Seqno()), root.Fetched()) 178 } 179 180 // This is the hash we are being instructed to use. 181 tracer.Stage("hash") 182 hash := keybase1.MerkleStoreKitHash(s.getHash(*root)) 183 184 if hash == "" { 185 return "", "", NewMerkleStoreError("merkle root has empty %s hash: %v", s.tag, seqnoWrap(root.Seqno())) 186 } 187 if knownHash != nil && hash == *knownHash { 188 return "", hash, nil 189 } 190 191 // Use in-memory cache if it matches 192 tracer.Stage("mem") 193 if fromMem := s.memGet(hash); fromMem != nil { 194 m.VLogf(libkb.VLog0, "MerkleStore: mem cache hit %s, using hash: %s", s.tag, hash) 195 return *fromMem, hash, nil 196 } 197 198 tracer.Stage("db") 199 // Use db cache if it matches 200 if fromDB := s.dbGet(m, hash); fromDB != nil { 201 m.Debug("MerkleStore: db cache hit") 202 203 // Store to memory 204 s.memSet(hash, *fromDB) 205 206 m.Debug("MerkleStore: using hash: %s", hash) 207 return *fromDB, hash, nil 208 } 209 210 // Fetch from the server 211 // This validates the hash 212 tracer.Stage("fetch") 213 kitJSON, err := s.fetch(m, hash) 214 if err != nil { 215 return "", "", err 216 } 217 218 // Store to memory 219 tracer.Stage("mem-set") 220 s.memSet(hash, kitJSON) 221 222 // db write 223 tracer.Stage("db-set") 224 s.dbSet(m.BackgroundWithLogTags(), hash, kitJSON) 225 226 m.Debug("MerkleStore: using hash: %s", hash) 227 return kitJSON, hash, nil 228 } 229 230 type merkleStoreServerRes struct { 231 Status libkb.AppStatus `json:"status"` 232 KitJSON keybase1.MerkleStoreKit `json:"kit_json"` 233 } 234 235 func (r *merkleStoreServerRes) GetAppStatus() *libkb.AppStatus { 236 return &r.Status 237 } 238 239 // Fetch data and check the hash. 240 func (s *MerkleStoreImpl) fetch(m libkb.MetaContext, hash keybase1.MerkleStoreKitHash) (keybase1.MerkleStoreKit, error) { 241 m.Debug("MerkleStore: fetching from server: %s", hash) 242 var res merkleStoreServerRes 243 err := m.G().API.GetDecode(m, libkb.APIArg{ 244 Endpoint: s.endpoint, 245 SessionType: libkb.APISessionTypeNONE, 246 Args: libkb.HTTPArgs{ 247 "hash": libkb.S{Val: string(hash)}, 248 }, 249 }, &res) 250 if err != nil { 251 return "", NewMerkleStoreError(err.Error()) 252 } 253 if res.KitJSON == "" { 254 return "", NewMerkleStoreError("server returned empty kit for %s", s.tag) 255 } 256 if s.hash(res.KitJSON) != hash { 257 m.Debug("%s hash mismatch: got:%s expected:%s", s.tag, s.hash(res.KitJSON), hash) 258 return "", NewMerkleStoreError("server returned wrong kit for %s", s.tag) 259 } 260 return res.KitJSON, nil 261 } 262 263 func (s *MerkleStoreImpl) memGet(hash keybase1.MerkleStoreKitHash) *keybase1.MerkleStoreKit { 264 if s.mem != nil { 265 if s.mem.Hash == hash { 266 ret := s.mem.Kit 267 return &ret 268 } 269 } 270 return nil 271 } 272 273 func (s *MerkleStoreImpl) memSet(hash keybase1.MerkleStoreKitHash, kitJSON keybase1.MerkleStoreKit) { 274 s.mem = &dbKit{ 275 DBVersion: dbVersion, 276 Hash: hash, 277 Kit: kitJSON, 278 } 279 } 280 281 // Get from local db. Can return nil. 282 func (s *MerkleStoreImpl) dbGet(m libkb.MetaContext, hash keybase1.MerkleStoreKitHash) *keybase1.MerkleStoreKit { 283 db := m.G().LocalDb 284 if db == nil { 285 return nil 286 } 287 var entry dbKit 288 if found, err := db.GetInto(&entry, s.dbKey()); err != nil { 289 m.Debug("MerkleStore: error reading from db: %s", err) 290 return nil 291 } else if !found { 292 return nil 293 } 294 if entry.DBVersion != dbVersion { 295 return nil 296 } 297 if entry.Hash == hash { 298 return &entry.Kit 299 } 300 return nil 301 } 302 303 // Logs errors. 304 func (s *MerkleStoreImpl) dbSet(m libkb.MetaContext, hash keybase1.MerkleStoreKitHash, kitJSON keybase1.MerkleStoreKit) { 305 db := m.G().LocalDb 306 if db == nil { 307 m.Debug("dbSet: no db") 308 return 309 } 310 entry := dbKit{ 311 DBVersion: dbVersion, 312 Hash: hash, 313 Kit: kitJSON, 314 } 315 if err := db.PutObj(s.dbKey(), nil, entry); err != nil { 316 m.Debug("dbSet: %s", err) 317 } 318 } 319 320 // hex of sha512 321 func (s *MerkleStoreImpl) hash(in keybase1.MerkleStoreKit) keybase1.MerkleStoreKitHash { 322 buf := sha512.Sum512([]byte(in)) 323 out := hex.EncodeToString(buf[:]) 324 return keybase1.MerkleStoreKitHash(out) 325 } 326 327 func (s *MerkleStoreImpl) pastDue(m libkb.MetaContext, event time.Time, limit time.Duration) bool { 328 diff := m.G().Clock().Now().Sub(event) 329 isOverdue := diff > limit 330 if isOverdue { 331 m.Debug("MerkleStore: pastDue diff:(%s) t1:(%s) limit:(%s)", diff, event, limit) 332 } 333 return isOverdue 334 } 335 336 func (s *MerkleStoreImpl) readFile(path string) (keybase1.MerkleStoreKit, keybase1.MerkleStoreKitHash, error) { 337 buf, err := os.ReadFile(path) 338 kitJSON := keybase1.MerkleStoreKit(string(buf)) 339 return kitJSON, s.hash(kitJSON), err 340 } 341 342 func (s *MerkleStoreImpl) dbKey() libkb.DbKey { 343 return libkb.DbKey{ 344 Typ: libkb.DBMerkleStore, 345 Key: s.tag, 346 } 347 } 348 349 func seqnoWrap(x *keybase1.Seqno) int64 { 350 if x == nil { 351 return 0 352 } 353 return int64(*x) 354 }