github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/engine/merkle_audit.go (about)

     1  // Copyright 2019 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  // MerkleAudit runs a merkle tree audit in the background once in a while.
     5  // It verifies the skips of a randomly chosen merkle tree root, making
     6  // sure that the server is not tampering with the merkle trees.
     7  
     8  package engine
     9  
    10  import (
    11  	"crypto/rand"
    12  	"errors"
    13  	"fmt"
    14  	"math/big"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/keybase/client/go/libkb"
    19  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    20  )
    21  
    22  var (
    23  	errAuditOffline    = errors.New("Merkle audit failed to run due to the lack of connectivity.")
    24  	errAuditNoLastRoot = errors.New("Merkle audit failed to run due to not being able to get the last root.")
    25  )
    26  
    27  var MerkleAuditSettings = BackgroundTaskSettings{
    28  	Start:        5 * time.Minute,
    29  	StartStagger: 1 * time.Hour,
    30  	Interval:     24 * time.Hour,
    31  	Limit:        1 * time.Minute,
    32  }
    33  
    34  // MerkleAudit is an engine.
    35  type MerkleAudit struct {
    36  	libkb.Contextified
    37  	sync.Mutex
    38  
    39  	args *MerkleAuditArgs
    40  	task *BackgroundTask
    41  }
    42  
    43  type MerkleAuditArgs struct {
    44  	// Channels used for testing. Normally nil.
    45  	testingMetaCh     chan<- string
    46  	testingRoundResCh chan<- error
    47  }
    48  
    49  // Bump this up whenever there is a change that needs to reset the current stored state.
    50  const merkleAuditCurrentVersion = 1
    51  
    52  type merkleAuditState struct {
    53  	RetrySeqno *keybase1.Seqno `json:"retrySeqno"`
    54  	LastSeqno  *keybase1.Seqno `json:"lastSeqno"`
    55  	Version    int             `json:"version"`
    56  }
    57  
    58  // NewMerkleAudit creates a new MerkleAudit engine.
    59  func NewMerkleAudit(g *libkb.GlobalContext, args *MerkleAuditArgs) *MerkleAudit {
    60  	task := NewBackgroundTask(g, &BackgroundTaskArgs{
    61  		Name:     "MerkleAudit",
    62  		F:        MerkleAuditRound,
    63  		Settings: MerkleAuditSettings,
    64  
    65  		testingMetaCh:     args.testingMetaCh,
    66  		testingRoundResCh: args.testingRoundResCh,
    67  	})
    68  	return &MerkleAudit{
    69  		Contextified: libkb.NewContextified(g),
    70  		args:         args,
    71  		task:         task,
    72  	}
    73  }
    74  
    75  // Name is the unique engine name.
    76  func (e *MerkleAudit) Name() string {
    77  	return "MerkleAudit"
    78  }
    79  
    80  // GetPrereqs returns the engine prereqs.
    81  func (e *MerkleAudit) Prereqs() Prereqs {
    82  	return Prereqs{}
    83  }
    84  
    85  // RequiredUIs returns the required UIs.
    86  func (e *MerkleAudit) RequiredUIs() []libkb.UIKind {
    87  	return []libkb.UIKind{}
    88  }
    89  
    90  // SubConsumers returns the other UI consumers for this engine.
    91  func (e *MerkleAudit) SubConsumers() []libkb.UIConsumer {
    92  	return []libkb.UIConsumer{}
    93  }
    94  
    95  // Run starts the engine.
    96  // Returns immediately, kicks off a background goroutine.
    97  func (e *MerkleAudit) Run(mctx libkb.MetaContext) (err error) {
    98  	if mctx.G().GetEnv().GetDisableMerkleAuditor() {
    99  		mctx.Debug("merkle audit disabled, aborting run")
   100  		return nil
   101  	}
   102  	if mctx.G().IsMobileAppType() {
   103  		state := mctx.G().MobileNetState.State()
   104  		if state.IsLimited() {
   105  			mctx.Debug("merkle audit skipping without wifi, network state: %v", state)
   106  			return nil
   107  		}
   108  	}
   109  	return RunEngine2(mctx, e.task)
   110  }
   111  
   112  func (e *MerkleAudit) Shutdown(mctx libkb.MetaContext) error {
   113  	mctx.Debug("stopping merkle root background audit engine")
   114  	e.task.Shutdown()
   115  	return nil
   116  }
   117  
   118  // randSeqno picks a random number between [low, high) that's different from prev.
   119  func randSeqno(lo keybase1.Seqno, hi keybase1.Seqno, prev *keybase1.Seqno) (keybase1.Seqno, error) {
   120  	// Prevent an infinite loop if [0,1) and prev = 0
   121  	if hi-lo == 1 && prev != nil && *prev == lo {
   122  		return keybase1.Seqno(0), fmt.Errorf("unable to generate a non-duplicate seqno other than %d", *prev)
   123  	}
   124  	for {
   125  		rangeBig := big.NewInt(int64(hi - lo))
   126  		n, err := rand.Int(rand.Reader, rangeBig)
   127  		if err != nil {
   128  			return keybase1.Seqno(0), err
   129  		}
   130  		newSeqno := keybase1.Seqno(n.Int64()) + lo
   131  		if prev == nil || *prev != newSeqno {
   132  			return newSeqno, nil
   133  		}
   134  	}
   135  }
   136  
   137  var merkleAuditKey = libkb.DbKey{
   138  	Typ: libkb.DBMerkleAudit,
   139  	Key: "root",
   140  }
   141  
   142  func lookupMerkleAuditRetryFromState(m libkb.MetaContext) (*keybase1.Seqno, *keybase1.Seqno, error) {
   143  	var state merkleAuditState
   144  	found, err := m.G().LocalDb.GetInto(&state, merkleAuditKey)
   145  	if err != nil {
   146  		return nil, nil, err
   147  	}
   148  	if !found {
   149  		// Nothing found, no error
   150  		return nil, nil, nil
   151  	}
   152  	if state.Version != merkleAuditCurrentVersion {
   153  		m.Debug("discarding state with version %d, which isn't %d", state.Version, merkleAuditCurrentVersion)
   154  		return nil, nil, nil
   155  	}
   156  
   157  	// Can still be nil
   158  	return state.RetrySeqno, state.LastSeqno, nil
   159  }
   160  
   161  func saveMerkleAuditState(m libkb.MetaContext, state merkleAuditState) error {
   162  	state.Version = merkleAuditCurrentVersion
   163  	return m.G().LocalDb.PutObj(merkleAuditKey, nil, state)
   164  }
   165  
   166  func performMerkleAudit(m libkb.MetaContext, startSeqno keybase1.Seqno) error {
   167  	if m.G().ConnectivityMonitor.IsConnected(m.Ctx()) == libkb.ConnectivityMonitorNo {
   168  		m.Debug("MerkleAudit giving up offline")
   169  		return errAuditOffline
   170  	}
   171  
   172  	// Acquire the most recent merkle tree root
   173  	lastRoot := m.G().MerkleClient.LastRoot(m)
   174  	if lastRoot == nil {
   175  		m.Debug("MerkleAudit unable to retrieve the last root")
   176  		return errAuditNoLastRoot
   177  	}
   178  
   179  	// We can copy the pointer's value as it can only return nil if root == nil.
   180  	lastSeqno := *lastRoot.Seqno()
   181  
   182  	// Acquire the first root and calculate its hash
   183  	startRoot, err := m.G().MerkleClient.LookupRootAtSeqno(m, startSeqno)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	startHash := startRoot.ShortHash()
   188  
   189  	// Traverse the merkle tree seqnos
   190  	currentSeqno := startSeqno + 1
   191  	step := 1
   192  	for {
   193  		// Proceed until the last known root
   194  		if currentSeqno > lastSeqno {
   195  			break
   196  		}
   197  
   198  		currentRoot, err := m.G().MerkleClient.LookupRootAtSeqno(m, currentSeqno)
   199  		if err != nil {
   200  			return err
   201  		}
   202  		currentHash := currentRoot.SkipToSeqno(startSeqno)
   203  		if currentHash == nil {
   204  			return libkb.NewClientMerkleSkipMissingError(
   205  				fmt.Sprintf("Root %d missing skip hash to %d", currentSeqno, startSeqno),
   206  			)
   207  		}
   208  
   209  		if !startHash.Eq(currentHash) {
   210  			// Warn the user about the possibility of the server tampering with the roots.
   211  			return libkb.NewClientMerkleSkipHashMismatchError(
   212  				fmt.Sprintf("Invalid skip hash from %d to %d", currentSeqno, startSeqno),
   213  			)
   214  		}
   215  
   216  		// We're doing this exponentially to make use of the skips.
   217  		currentSeqno += keybase1.Seqno(step)
   218  		step *= 2
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  func MerkleAuditRound(m libkb.MetaContext) (err error) {
   225  	m = m.WithLogTag("MAUDT")
   226  	defer m.Trace("MerkleAuditRound", &err)()
   227  
   228  	// Look up any previously requested retries
   229  	startSeqno, prevSeqno, err := lookupMerkleAuditRetryFromState(m)
   230  	if err != nil {
   231  		m.Debug("MerkleAudit unable to acquire saved state from localdb")
   232  		return nil
   233  	}
   234  
   235  	// If no retry was requested
   236  	if startSeqno == nil {
   237  		// nil seqno, generate a new one:
   238  		// 1. Acquire the most recent merkle tree root
   239  		lastRoot := m.G().MerkleClient.LastRoot(m)
   240  		if lastRoot == nil {
   241  			m.Debug("MerkleAudit unable to retrieve the last root")
   242  			return nil
   243  		}
   244  		lastSeqno := *lastRoot.Seqno()
   245  
   246  		// 2. Figure out the first merkle root seqno with skips, fall back to 1
   247  		firstSeqno := m.G().MerkleClient.FirstExaminableHistoricalRoot(m)
   248  		if firstSeqno == nil {
   249  			val := keybase1.Seqno(1)
   250  			firstSeqno = &val
   251  		}
   252  
   253  		// 3. Generate a random seqno for the starting root in the audit.
   254  		randomSeqno, err := randSeqno(*firstSeqno, lastSeqno, prevSeqno)
   255  		if err != nil {
   256  			return err
   257  		}
   258  		startSeqno = &randomSeqno
   259  	} else {
   260  		m.Debug("Audit retry requested for %d", *startSeqno)
   261  	}
   262  
   263  	// If this time it fails, save it
   264  	err = performMerkleAudit(m, *startSeqno)
   265  	if err == nil {
   266  		// Early return for fewer ifs
   267  		return saveMerkleAuditState(m, merkleAuditState{
   268  			RetrySeqno: nil,
   269  			LastSeqno:  startSeqno,
   270  		})
   271  	}
   272  
   273  	// All MerkleClientErrors would suggest that the server is tampering with the roots
   274  	if _, ok := err.(libkb.MerkleClientError); ok {
   275  		m.Error("MerkleAudit fatally failed: %s", err)
   276  		// Send the notification to the client
   277  		m.G().NotifyRouter.HandleRootAuditError(fmt.Sprintf(
   278  			"Merkle tree audit from %d failed: %s",
   279  			startSeqno, err.Error(),
   280  		))
   281  	} else {
   282  		m.Debug("MerkleAudit could not complete: %s", err)
   283  	}
   284  
   285  	// Use another error variable to prevent shadowing
   286  	if serr := saveMerkleAuditState(m, merkleAuditState{
   287  		RetrySeqno: startSeqno,
   288  		LastSeqno:  prevSeqno,
   289  	}); serr != nil {
   290  		return serr
   291  	}
   292  
   293  	return err
   294  }