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 }