github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/bug_3964_repairman_test.go (about) 1 package engine 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/keybase/client/go/libkb" 10 "github.com/keybase/client/go/logger" 11 keybase1 "github.com/keybase/client/go/protocol/keybase1" 12 "github.com/stretchr/testify/require" 13 "golang.org/x/net/context" 14 ) 15 16 type auditLog struct { 17 l logger.Logger 18 lines *[]string 19 } 20 21 func newAuditLog(l logger.Logger) *auditLog { 22 return &auditLog{l: l, lines: &[]string{}} 23 } 24 25 func (a *auditLog) GetLines() []string { 26 return *a.lines 27 } 28 29 func (a *auditLog) ClearLines() { 30 a.lines = &[]string{} 31 } 32 33 func (a *auditLog) Debug(format string, args ...interface{}) { 34 s := fmt.Sprintf(format, args...) 35 a.l.CloneWithAddedDepth(1).Debug(s) 36 *a.lines = append(*a.lines, s) 37 } 38 func (a *auditLog) CDebugf(ctx context.Context, format string, args ...interface{}) { 39 s := fmt.Sprintf(format, args...) 40 a.l.CloneWithAddedDepth(1).CDebugf(ctx, s) 41 *a.lines = append(*a.lines, s) 42 } 43 func (a *auditLog) Info(format string, args ...interface{}) { 44 a.l.CloneWithAddedDepth(1).Info(format, args...) 45 } 46 func (a *auditLog) CInfof(ctx context.Context, format string, args ...interface{}) { 47 a.l.CloneWithAddedDepth(1).CInfof(ctx, format, args...) 48 } 49 func (a *auditLog) Notice(format string, args ...interface{}) { 50 a.l.CloneWithAddedDepth(1).Notice(format, args...) 51 } 52 func (a *auditLog) CNoticef(ctx context.Context, format string, args ...interface{}) { 53 a.l.CloneWithAddedDepth(1).CNoticef(ctx, format, args...) 54 } 55 func (a *auditLog) Warning(format string, args ...interface{}) { 56 a.l.CloneWithAddedDepth(1).Warning(format, args...) 57 } 58 func (a *auditLog) CWarningf(ctx context.Context, format string, args ...interface{}) { 59 a.l.CloneWithAddedDepth(1).CWarningf(ctx, format, args...) 60 } 61 func (a *auditLog) Error(format string, args ...interface{}) { 62 a.l.CloneWithAddedDepth(1).Errorf(format, args...) 63 } 64 func (a *auditLog) Errorf(format string, args ...interface{}) { 65 a.l.CloneWithAddedDepth(1).Errorf(format, args...) 66 } 67 func (a *auditLog) CErrorf(ctx context.Context, format string, args ...interface{}) { 68 a.l.CloneWithAddedDepth(1).CErrorf(ctx, format, args...) 69 } 70 func (a *auditLog) Critical(format string, args ...interface{}) { 71 a.l.CloneWithAddedDepth(1).Critical(format, args...) 72 } 73 func (a *auditLog) CCriticalf(ctx context.Context, format string, args ...interface{}) { 74 a.l.CloneWithAddedDepth(1).CCriticalf(ctx, format, args...) 75 } 76 func (a *auditLog) Fatalf(format string, args ...interface{}) { 77 a.l.CloneWithAddedDepth(1).Fatalf(format, args...) 78 } 79 func (a *auditLog) CFatalf(ctx context.Context, format string, args ...interface{}) { 80 a.l.CloneWithAddedDepth(1).CFatalf(ctx, format, args...) 81 } 82 func (a *auditLog) Profile(fmts string, args ...interface{}) { 83 a.l.CloneWithAddedDepth(1).Profile(fmts, args...) 84 } 85 func (a *auditLog) Configure(style string, debug bool, filename string) { 86 a.l.CloneWithAddedDepth(1).Configure(style, debug, filename) 87 } 88 func (a *auditLog) CloneWithAddedDepth(depth int) logger.Logger { 89 // Keep the same list of strings. This is important, because the tests here 90 // read the list at the end, and expect all the log lines to be there, even 91 // though some of the loggin calls in the middle call CloneWithAddedDepth. 92 return &auditLog{ 93 l: a.l.CloneWithAddedDepth(depth), 94 lines: a.lines, 95 } 96 } 97 func (a *auditLog) SetExternalHandler(handler logger.ExternalHandler) { 98 a.l.SetExternalHandler(handler) 99 } 100 101 func corruptDevice2(dev1 libkb.TestContext, dev2 libkb.TestContext) (*libkb.DeviceKey, error) { 102 m2 := NewMetaContextForTest(dev2) 103 m1 := NewMetaContextForTest(dev1) 104 105 var ret libkb.DeviceKey 106 107 ss, err := m1.G().ActiveDevice.SyncSecrets(m1) 108 if err != nil { 109 return nil, err 110 } 111 ret, err = ss.FindDevice(m1.G().Env.GetDeviceID()) 112 if err != nil { 113 return nil, err 114 } 115 116 // Dev1 has a passphrase cached, but dev2 doesn't (since it was provisioned). 117 // For this test though it's fine to take the passphrase from dev1. 118 pps, err := libkb.GetPassphraseStreamStored(m1) 119 if err != nil { 120 return nil, err 121 } 122 if pps == nil { 123 return nil, errors.New("empty passphrase stream on m1, but expected one since we just signed up") 124 } 125 goodLksec := libkb.NewLKSec(pps, m2.CurrentUID()) 126 127 if err = goodLksec.LoadServerHalf(m2); err != nil { 128 return nil, err 129 } 130 131 dev1ServerHalf, err := ret.ToLKSec() 132 if err != nil { 133 return nil, err 134 } 135 136 badLskec := libkb.NewLKSecWithFullSecret( 137 goodLksec.CorruptedFullSecretForBug3964Testing(dev1ServerHalf), 138 dev2.G.Env.GetUID(), 139 ) 140 var krf *libkb.SKBKeyringFile 141 krf, err = libkb.LoadSKBKeyringFromMetaContext(m2) 142 if err != nil { 143 return nil, err 144 } 145 for _, b := range krf.Blocks { 146 raw, _, erroneousMask, err := goodLksec.Decrypt(m2, b.Priv.Data) 147 if err != nil { 148 return nil, err 149 } 150 if !erroneousMask.IsNil() { 151 return nil, errors.New("bad erroneousMask") 152 } 153 b.Priv.Data, err = badLskec.Encrypt(m2, raw) 154 if err != nil { 155 return nil, err 156 } 157 } 158 krf.MarkDirty() 159 if err = krf.Save(); err != nil { 160 return nil, err 161 } 162 return &ret, nil 163 } 164 165 // Limit the log lines to a function "trace" given by which. Meaning log lines 166 // that start with "+ which" and end with "- which" 167 func limitToTrace(lines []string, which string) []string { 168 for i, l := range lines { 169 if l == "+ "+which { 170 rest := lines[(i + 1):] 171 for j, k := range rest { 172 if strings.HasPrefix(k, "- "+which) { 173 return rest[:j] 174 } 175 } 176 return nil 177 } 178 } 179 return nil 180 } 181 182 func checkAuditLogForBug3964Recovery(t *testing.T, log []string, deviceID keybase1.DeviceID, dev1Key *libkb.DeviceKey) { 183 log = limitToTrace(log, "LKSec#tryAllDevicesForBug3964Recovery()") 184 needle := fmt.Sprintf("| Trying Bug 3964 Recovery w/ device %q {id: %s, lks: %s...}", 185 dev1Key.Description, deviceID, dev1Key.LksServerHalf[0:8]) 186 for i, line := range log { 187 if strings.HasPrefix(line, needle) { 188 if log[i+1] == "| Success" { 189 return 190 } 191 t.Fatalf("Found %q but it wasn't followed by '| Success'", needle) 192 } 193 } 194 t.Fatalf("Didn't find evidence of %q", needle) 195 } 196 197 func findLine(t *testing.T, haystack []string, needle string) []string { 198 for i, line := range haystack { 199 if strings.HasPrefix(line, needle) { 200 return haystack[(i + 1):] 201 } 202 } 203 t.Fatalf("Didn't find line %q", needle) 204 return nil 205 } 206 207 func checkAuditLogForBug3964Repair(t *testing.T, log []string, deviceID keybase1.DeviceID, dev1Key *libkb.DeviceKey) { 208 log = limitToTrace(log, "bug3964Repairman#Run") 209 require.NotZero(t, len(log)) 210 log = findLine(t, log, "| Repairman wasn't short-circuited") 211 require.NotZero(t, len(log)) 212 log = findLine(t, log, "+ bug3964Repairman#saveRepairmanVisit") 213 require.NotZero(t, len(log)) 214 } 215 216 func logoutLogin(t *testing.T, user *FakeUser, dev libkb.TestContext) { 217 Logout(dev) 218 219 uis := libkb.UIs{ 220 ProvisionUI: newTestProvisionUIPassphrase(), 221 LoginUI: &libkb.TestLoginUI{}, 222 LogUI: dev.G.UI.GetLogUI(), 223 SecretUI: user.NewSecretUI(), 224 GPGUI: &gpgtestui{}, 225 } 226 eng := NewLogin(dev.G, keybase1.DeviceTypeV2_DESKTOP, user.Username, keybase1.ClientType_CLI) 227 m := NewMetaContextForTest(dev).WithUIs(uis) 228 if err := RunEngine2(m, eng); err != nil { 229 t.Fatal(err) 230 } 231 } 232 233 func checkAuditLogCleanLogin(t *testing.T, log []string) { 234 if len(limitToTrace(log, "LKSec#Decrypt()")) == 0 { 235 t.Fatalf("at least expected a login call") 236 } 237 for _, line := range log { 238 if strings.HasPrefix(line, "+ LKSec#tryAllDevicesForBug3964Recovery()") { 239 t.Fatalf("found attempt to try bug 3964 recovery after a full repair") 240 } 241 } 242 } 243 244 func checkAuditLogForRepairmanShortCircuit(t *testing.T, log []string) { 245 for _, line := range log { 246 if strings.HasPrefix(line, "| Repairman wasn't short-circuited") { 247 t.Fatalf("short-circuit mechanism failed") 248 } 249 } 250 found := false 251 for _, line := range log { 252 if strings.HasPrefix(line, "| Repairman already visited after file update; bailing out") { 253 found = true 254 break 255 } 256 } 257 if !found { 258 t.Fatalf("Didn't find a mention of short-circuiting") 259 } 260 } 261 262 func checkLKSWorked(t *testing.T, tctx libkb.TestContext, u *FakeUser) { 263 uis := libkb.UIs{ 264 SecretUI: u.NewSecretUI(), 265 } 266 me, err := libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(NewMetaContextForTest(tctx))) 267 if err != nil { 268 t.Fatal(err) 269 } 270 // need unlocked signing key 271 ska := libkb.SecretKeyArg{ 272 Me: me, 273 KeyType: libkb.DeviceEncryptionKeyType, 274 } 275 m := NewMetaContextForTest(tctx).WithUIs(uis) 276 arg := m.SecretKeyPromptArg(ska, "tracking signature") 277 encKey, err := tctx.G.Keyrings.GetSecretKeyWithPrompt(NewMetaContextForTest(tctx), arg) 278 if err != nil { 279 t.Fatal(err) 280 } 281 if encKey == nil { 282 t.Fatal("got back a nil decryption key") 283 } 284 _, clientHalf, err := fetchLKS(m, encKey) 285 if err != nil { 286 t.Fatal(err) 287 } 288 pps, err := libkb.GetPassphraseStreamStored(m) 289 if err != nil { 290 t.Fatal(err) 291 } 292 if pps == nil { 293 t.Fatal("failed to get passphrase stream") 294 } 295 clientHalfExpected := pps.LksClientHalf() 296 if !clientHalf.Equal(clientHalfExpected) { 297 t.Fatal("got bad passphrase from LKS recovery") 298 } 299 } 300 301 func TestBug3964Repairman(t *testing.T) { 302 var log *auditLog 303 304 user, dev1, dev2, cleanup := SetupTwoDevicesWithHook(t, "bug", func(tc *libkb.TestContext) { 305 log = newAuditLog(tc.G.Log) 306 tc.G.Log = log 307 }) 308 defer cleanup() 309 310 t.Logf("-------------- Checkpoint 1 -----------------------") 311 dev1Key, err := corruptDevice2(dev1, dev2) 312 if err != nil { 313 t.Fatal(err) 314 } 315 316 dev2.G.TestOptions.NoBug3964Repair = true 317 logoutLogin(t, user, dev2) 318 t.Logf("-------------- Checkpoint 2 -----------------------") 319 checkAuditLogForBug3964Recovery(t, log.GetLines(), dev1.G.Env.GetDeviceID(), dev1Key) 320 dev2.G.TestOptions.NoBug3964Repair = false 321 322 log.ClearLines() 323 logoutLogin(t, user, dev2) 324 t.Logf("-------------- Checkpoint 3 -----------------------") 325 checkAuditLogForBug3964Repair(t, log.GetLines(), dev1.G.Env.GetDeviceID(), dev1Key) 326 327 log.ClearLines() 328 logoutLogin(t, user, dev2) 329 t.Logf("-------------- Checkpoint 4 -----------------------") 330 checkAuditLogCleanLogin(t, log.GetLines()) 331 checkAuditLogForRepairmanShortCircuit(t, log.GetLines()) 332 333 t.Logf("-------------- Checkpoint 5 -----------------------") 334 checkLKSWorked(t, dev2, user) 335 }