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  }