github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/rekey_master.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package service 5 6 import ( 7 "errors" 8 "fmt" 9 "time" 10 11 gregor "github.com/keybase/client/go/gregor" 12 "github.com/keybase/client/go/libkb" 13 gregor1 "github.com/keybase/client/go/protocol/gregor1" 14 keybase1 "github.com/keybase/client/go/protocol/keybase1" 15 "github.com/keybase/go-framed-msgpack-rpc/rpc" 16 context "golang.org/x/net/context" 17 ) 18 19 const TLFRekeyGregorCategory = "kbfs_tlf_rekey_needed" 20 21 // rekeyMaster is the object that controls all rekey harassment in the keybase service. 22 // There should only be one such object per service process. 23 type rekeyMaster struct { 24 libkb.Contextified 25 26 // The rekeyMaster is usually sleeping, but its sleep can be interrupted by various 27 // exogenous and internal events. These events are sent into this channel. 28 interruptCh chan interruptArg 29 30 ui *RekeyUI 31 uiRouter *UIRouter 32 sleepUntil time.Time 33 plannedWakeup time.Time 34 uiNeeded bool 35 uiVisible bool 36 37 // We need to be able to access the gregor full state to see if there are any 38 // TLF rekey events. We should only be running if there are (since we want to respect 39 // the 3-minute delay on rekey harassment after a new key is added). 40 gregor *gregorHandler 41 } 42 43 type RekeyInterrupt int 44 45 const ( 46 RekeyInterruptNone RekeyInterrupt = 0 47 RekeyInterruptTimeout RekeyInterrupt = 1 48 RekeyInterruptCreation RekeyInterrupt = 2 49 RekeyInterruptDismissal RekeyInterrupt = 3 50 RekeyInterruptLogout RekeyInterrupt = 4 51 RekeyInterruptLogin RekeyInterrupt = 5 52 RekeyInterruptUIFinished RekeyInterrupt = 6 53 RekeyInterruptShowUI RekeyInterrupt = 7 54 RekeyInterruptNewUI RekeyInterrupt = 8 55 RekeyInterruptSync RekeyInterrupt = 9 56 RekeyInterruptSyncForce RekeyInterrupt = 10 57 ) 58 59 type interruptArg struct { 60 retCh chan struct{} 61 rekeyInterrupt RekeyInterrupt 62 } 63 64 func newRekeyMaster(g *libkb.GlobalContext) *rekeyMaster { 65 return &rekeyMaster{ 66 Contextified: libkb.NewContextified(g), 67 interruptCh: make(chan interruptArg), 68 } 69 } 70 71 func (r *rekeyMaster) Start() { 72 go r.mainLoop() 73 } 74 75 func (r *rekeyMaster) IsAlive() bool { 76 return true 77 } 78 79 func (r *rekeyMaster) Name() string { 80 return "rekeyMaster" 81 } 82 83 func (r *rekeyMaster) Create(ctx context.Context, cli gregor1.IncomingInterface, category string, ibm gregor.Item) (bool, error) { 84 switch category { 85 case TLFRekeyGregorCategory: 86 r.G().Log.Debug("incoming gregor: %+v", ibm) 87 return true, r.handleGregorCreation() 88 default: 89 return true, nil 90 } 91 } 92 93 func (r *rekeyMaster) Dismiss(ctx context.Context, cli gregor1.IncomingInterface, category string, ibm gregor.Item) (bool, error) { 94 switch category { 95 case TLFRekeyGregorCategory: 96 return true, r.handleGregorDismissal() 97 default: 98 return true, nil 99 } 100 } 101 102 var _ libkb.GregorInBandMessageHandler = (*rekeyMaster)(nil) 103 104 func (r *rekeyMaster) handleGregorCreation() error { 105 r.interruptCh <- interruptArg{rekeyInterrupt: RekeyInterruptCreation} 106 return nil 107 } 108 109 func (r *rekeyMaster) handleGregorDismissal() error { 110 r.interruptCh <- interruptArg{rekeyInterrupt: RekeyInterruptDismissal} 111 return nil 112 } 113 114 func (r *rekeyMaster) Logout() { 115 // Beware deadlocks here! See CORE-3690 for an example. We sometimes 116 // block on login state to make an API call. But we don't want 117 // LoginState to block on us during a logout call, so send this one 118 // async 119 go func() { 120 r.interruptCh <- interruptArg{rekeyInterrupt: RekeyInterruptLogout} 121 }() 122 } 123 124 func (r *rekeyMaster) Login() { 125 // See comment about Logout() for deadlock avoidance. 126 go func() { 127 r.interruptCh <- interruptArg{rekeyInterrupt: RekeyInterruptLogin} 128 }() 129 } 130 131 func (r *rekeyMaster) newUIRegistered() { 132 r.interruptCh <- interruptArg{rekeyInterrupt: RekeyInterruptNewUI} 133 } 134 135 const ( 136 rekeyTimeoutBackground = 24 * time.Hour 137 rekeyTimeoutAPIError = 3 * time.Minute 138 rekeyTimeoutLoadMeError = 3 * time.Minute 139 rekeyTimeoutDeviceLoadError = 3 * time.Minute 140 rekeyTimeoutActive = 1 * time.Minute 141 rekeyTimeoutUIFinished = 24 * time.Hour 142 ) 143 144 type rekeyQueryResult struct { 145 Status libkb.AppStatus `json:"status"` 146 ProblemSet keybase1.ProblemSet `json:"problem_set"` 147 } 148 149 func (r *rekeyQueryResult) GetAppStatus() *libkb.AppStatus { 150 return &r.Status 151 } 152 153 func queryAPIServerForRekeyInfo(g *libkb.GlobalContext) (keybase1.ProblemSet, error) { 154 155 // Calling with the clear=true boolean means the server will potentially 156 // clear all gregor messages as a side-effect of the lookup. Hence the 157 // POST rather than GET for this operation. 158 args := libkb.HTTPArgs{ 159 "clear": libkb.B{Val: true}, 160 } 161 162 var tmp rekeyQueryResult 163 mctx := libkb.NewMetaContextBackground(g) 164 err := g.API.PostDecode(mctx, libkb.APIArg{ 165 Endpoint: "kbfs/problem_sets", 166 SessionType: libkb.APISessionTypeREQUIRED, 167 Args: args, 168 }, &tmp) 169 170 return tmp.ProblemSet, err 171 } 172 173 func (r *rekeyMaster) continueSleep(ri RekeyInterrupt) (ret time.Duration) { 174 175 r.G().Log.Debug("+ rekeyMaster#continueSleep") 176 defer func() { 177 r.G().Log.Debug("- rekeyMaster#continueSleep -> %s", ret) 178 }() 179 180 if r.sleepUntil.IsZero() { 181 return ret 182 } 183 184 dur := r.sleepUntil.Sub(r.G().Clock().Now()) 185 186 if dur <= 0 { 187 r.G().Log.Debug("| Snooze deadline exceeded (%s ago)", -dur) 188 r.sleepUntil = time.Time{} 189 return ret 190 } 191 192 if ri == RekeyInterruptLogin { 193 r.G().Log.Debug("| resetting sleep until after new login") 194 r.sleepUntil = time.Time{} 195 return ret 196 } 197 198 r.G().Log.Debug("| Sleeping until %s (%s more)", r.sleepUntil, dur) 199 return dur 200 } 201 202 func (r *rekeyMaster) resumeSleep() time.Duration { 203 if r.plannedWakeup.IsZero() { 204 return rekeyTimeoutBackground 205 } 206 if ret := r.plannedWakeup.Sub(r.G().Clock().Now()); ret > 0 { 207 return ret 208 } 209 return rekeyTimeoutActive 210 } 211 212 func (r *rekeyMaster) runOnce(ri RekeyInterrupt) (ret time.Duration, err error) { 213 defer r.G().Trace(fmt.Sprintf("rekeyMaster#runOnce(%d) [%p]", ri, r), &err)() 214 215 var problemsAndDevices *keybase1.ProblemSetDevices 216 var event keybase1.RekeyEvent 217 218 if ri == RekeyInterruptUIFinished { 219 ret = rekeyTimeoutUIFinished 220 r.uiVisible = false 221 r.sleepUntil = r.G().Clock().Now().Add(ret) 222 r.G().Log.Debug("| UI said finished; sleeping %s [%p]", ret, r) 223 return ret, nil 224 } 225 226 if ri == RekeyInterruptNewUI && !r.uiNeeded { 227 r.G().Log.Debug("| we got a new UI but didn't need it; resuming sleep") 228 return r.resumeSleep(), nil 229 } 230 231 if ri != RekeyInterruptSyncForce && ri != RekeyInterruptShowUI { 232 if ret = r.continueSleep(ri); ret > 0 { 233 r.G().Log.Debug("| Skipping compute and act due to sleep state") 234 return ret, nil 235 } 236 } 237 238 // compute which folders if any have problems 239 ret, problemsAndDevices, event, err = r.computeProblems() 240 if err != nil { 241 return ret, err 242 } 243 244 // sendRekeyEvent sends a debug message to the UI (useful only in testing) 245 event.InterruptType = int(ri) 246 err = r.sendRekeyEvent(event) 247 if err != nil { 248 return ret, err 249 } 250 251 err = r.actOnProblems(problemsAndDevices, event) 252 return ret, err 253 } 254 255 func (r *rekeyMaster) getUI() (ret *RekeyUI, err error) { 256 ret, err = r.uiRouter.getOrReuseRekeyUI(r.ui) 257 r.ui = ret 258 return ret, err 259 } 260 261 func (r *rekeyMaster) clearUI() (err error) { 262 defer r.G().Trace("rekeyMaster#clearUI", &err)() 263 264 if !r.uiVisible { 265 r.G().Log.Debug("| no need to clear the UI; UI wasn't visible") 266 return nil 267 } 268 269 var ui *RekeyUI 270 ui, err = r.getUI() 271 272 if err != nil { 273 return err 274 } 275 276 if ui == nil { 277 r.uiVisible = false 278 r.G().Log.Debug("| UI wasn't active, so nothing to do") 279 return nil 280 } 281 282 err = ui.Refresh(context.Background(), keybase1.RefreshArg{}) 283 284 if err == nil { 285 r.uiVisible = false 286 } 287 288 return err 289 } 290 291 func (r *rekeyMaster) spawnOrRefreshUI(problemSetDevices keybase1.ProblemSetDevices) (err error) { 292 defer r.G().Trace("rekeyMaster#spawnOrRefreshUI", &err)() 293 294 var ui *RekeyUI 295 ui, err = r.getUI() 296 if err != nil { 297 return err 298 } 299 300 if ui == nil { 301 r.G().Log.Info("| Rekey needed, but no active UI; consult logs") 302 r.uiNeeded = true 303 return nil 304 } 305 306 r.uiNeeded = false 307 r.uiVisible = true 308 309 err = ui.Refresh(context.Background(), keybase1.RefreshArg{ProblemSetDevices: problemSetDevices}) 310 return err 311 } 312 313 // sendRekeyEvent sends notification of a rekey event to the UI. It's largely 314 // used for testing. 315 func (r *rekeyMaster) sendRekeyEvent(e keybase1.RekeyEvent) (err error) { 316 defer r.G().Trace(fmt.Sprintf("rekeyMaster#sendRekeyEvent(%v)", e), &err)() 317 318 if e.InterruptType == int(RekeyInterruptSync) { 319 r.G().Log.Debug("| No need to send a rekey event on a Sync() RPC") 320 return nil 321 } 322 323 var ui *RekeyUI 324 ui, err = r.getUI() 325 if err != nil { 326 return err 327 } 328 if ui == nil { 329 r.G().Log.Debug("| no UI; not sending event information") 330 return nil 331 } 332 err = ui.RekeySendEvent(context.Background(), keybase1.RekeySendEventArg{Event: e}) 333 return err 334 } 335 336 func (r *rekeyMaster) actOnProblems(problemsAndDevices *keybase1.ProblemSetDevices, event keybase1.RekeyEvent) (err error) { 337 defer r.G().Trace(fmt.Sprintf("rekeyMaster#actOnProblems(%v)", problemsAndDevices != nil), &err)() 338 339 if problemsAndDevices == nil { 340 err = r.clearUI() 341 return err 342 } 343 344 err = r.spawnOrRefreshUI(*problemsAndDevices) 345 return err 346 } 347 348 func (r *rekeyMaster) hasGregorTLFRekeyMessages() (ret bool, err error) { 349 defer r.G().Trace("hasGregorTLFRekeyMessages", &err)() 350 351 var state gregor1.State 352 state, err = r.gregor.getState(context.Background()) 353 if err != nil { 354 return false, err 355 } 356 357 for _, item := range state.Items_ { 358 if item.Item_ != nil && string(item.Item_.Category_) == TLFRekeyGregorCategory { 359 return true, nil 360 } 361 } 362 return false, nil 363 } 364 365 func (r *rekeyMaster) computeProblems() (nextWait time.Duration, problemsAndDevices *keybase1.ProblemSetDevices, event keybase1.RekeyEvent, err error) { 366 defer r.G().Trace("rekeyMaster#computeProblems", &err)() 367 368 if !r.G().ActiveDevice.Valid() { 369 r.G().Log.Debug("| not logged in") 370 nextWait = rekeyTimeoutBackground 371 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_NOT_LOGGED_IN}, err 372 } 373 374 r.G().Log.Debug("| rekeyMaster#computeProblems: logged in") 375 376 hasGregor, err := r.hasGregorTLFRekeyMessages() 377 if err != nil { 378 nextWait = rekeyTimeoutAPIError 379 r.G().Log.Debug("| snoozing rekeyMaster for %ds on gregor error", nextWait) 380 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_API_ERROR}, err 381 } 382 383 if !hasGregor { 384 r.G().Log.Debug("| has no gregor TLF rekey messages") 385 nextWait = rekeyTimeoutBackground 386 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_NO_GREGOR_MESSAGES}, err 387 } 388 389 r.G().Log.Debug("| rekeyMaster#computeProblems: has gregor") 390 391 var problems keybase1.ProblemSet 392 problems, err = queryAPIServerForRekeyInfo(r.G()) 393 if err != nil { 394 nextWait = rekeyTimeoutAPIError 395 r.G().Log.Debug("| snoozing rekeyMaster for %ds on API error", nextWait) 396 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_API_ERROR}, err 397 } 398 399 r.G().Log.Debug("| rekeyMaster#computeProblems: queried API server for rekey info") 400 401 if len(problems.Tlfs) == 0 { 402 r.G().Log.Debug("| no problem TLFs found") 403 404 nextWait = rekeyTimeoutBackground 405 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_NO_PROBLEMS}, err 406 } 407 408 var me *libkb.User 409 me, err = libkb.LoadMe(libkb.NewLoadUserArg(r.G())) 410 r.G().Log.Debug("| rekeyMaster#computeProblems: loaded me") 411 412 if err != nil { 413 nextWait = rekeyTimeoutLoadMeError 414 r.G().Log.Debug("| snoozing rekeyMaster for %ds on LoadMe error", nextWait) 415 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_LOAD_ME_ERROR}, err 416 } 417 418 if r.currentDeviceSolvesProblemSet(me, problems) { 419 nextWait = rekeyTimeoutBackground 420 r.G().Log.Debug("| snoozing rekeyMaster since current device can rekey all") 421 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_CURRENT_DEVICE_CAN_REKEY}, err 422 } 423 424 r.G().Log.Debug("| rekeyMaster#computeProblems: current device computed") 425 426 var tmp keybase1.ProblemSetDevices 427 tmp, err = newProblemSetDevices(me, problems) 428 if err != nil { 429 nextWait = rekeyTimeoutDeviceLoadError 430 r.G().Log.Debug("| hit error in loading devices") 431 return nextWait, nil, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_DEVICE_LOAD_ERROR}, err 432 } 433 434 r.G().Log.Debug("| rekeyMaster#computeProblems: made problem set devices") 435 436 nextWait = rekeyTimeoutActive 437 return nextWait, &tmp, keybase1.RekeyEvent{EventType: keybase1.RekeyEventType_HARASS}, err 438 } 439 440 // currentDeviceSolvesProblemSet returns true if the current device can fix all 441 // of the folders in the ProblemSet. 442 func (r *rekeyMaster) currentDeviceSolvesProblemSet(me *libkb.User, ps keybase1.ProblemSet) (ret bool) { 443 r.G().Log.Debug("+ currentDeviceSolvesProblemSet") 444 defer func() { 445 r.G().Log.Debug("- currentDeviceSolvesProblemSet -> %v\n", ret) 446 }() 447 448 var paperKey libkb.GenericKey 449 deviceKey, err := me.GetDeviceSubkey() 450 if err != nil { 451 r.G().Log.Info("| Problem getting device subkey: %s\n", err) 452 return ret 453 } 454 455 m := libkb.NewMetaContextBackground(r.G()) 456 if d := m.ActiveDevice().ProvisioningKey(m); d != nil { 457 paperKey = d.EncryptionKey() 458 } 459 460 // We can continue though, so no need to error out 461 if paperKey == nil { 462 m.Debug("| No cached paper key") 463 } 464 if deviceKey != nil { 465 r.G().Log.Debug("| currentDeviceSolvesProblemSet: checking device key: %s", deviceKey.GetKID()) 466 } 467 if paperKey != nil { 468 r.G().Log.Debug("| currentDeviceSolvesProblemSet: checking paper key: %s", paperKey.GetKID()) 469 } 470 471 for _, tlf := range ps.Tlfs { 472 if !keysSolveProblemTLF([]libkb.GenericKey{deviceKey, paperKey}, tlf) { 473 r.G().Log.Debug("| Doesn't solve problem TLF: %s (%s)\n", tlf.Tlf.Name, tlf.Tlf.Id) 474 return ret 475 } 476 } 477 ret = true 478 return ret 479 } 480 481 func (r *rekeyMaster) mainLoop() { 482 483 // Sleep about ten seconds on startup so as to wait for startup sequence. 484 // It's ok if we race here, but it's less work if we don't. 485 timeout := 10 * time.Second 486 487 for { 488 489 var it RekeyInterrupt 490 var interruptArg interruptArg 491 492 select { 493 case interruptArg = <-r.interruptCh: 494 it = interruptArg.rekeyInterrupt 495 case <-r.G().Clock().After(timeout): 496 it = RekeyInterruptTimeout 497 } 498 499 timeout, _ = r.runOnce(it) 500 if retCh := interruptArg.retCh; retCh != nil { 501 retCh <- struct{}{} 502 } 503 r.plannedWakeup = r.G().Clock().Now().Add(timeout) 504 } 505 } 506 507 type RekeyHandler2 struct { 508 libkb.Contextified 509 *BaseHandler 510 rm *rekeyMaster 511 } 512 513 func NewRekeyHandler2(xp rpc.Transporter, g *libkb.GlobalContext, rm *rekeyMaster) *RekeyHandler2 { 514 return &RekeyHandler2{ 515 Contextified: libkb.NewContextified(g), 516 BaseHandler: NewBaseHandler(g, xp), 517 rm: rm, 518 } 519 } 520 521 func (r *RekeyHandler2) ShowPendingRekeyStatus(context.Context, int) error { 522 r.rm.interruptCh <- interruptArg{rekeyInterrupt: RekeyInterruptShowUI} 523 return nil 524 } 525 526 func (r *RekeyHandler2) GetPendingRekeyStatus(_ context.Context, _ int) (ret keybase1.ProblemSetDevices, err error) { 527 var me *libkb.User 528 me, err = libkb.LoadMe(libkb.NewLoadUserArg(r.G())) 529 if err != nil { 530 return ret, err 531 } 532 var problemSet keybase1.ProblemSet 533 problemSet, err = queryAPIServerForRekeyInfo(r.G()) 534 if err != nil { 535 return ret, err 536 } 537 ret, err = newProblemSetDevices(me, problemSet) 538 return ret, err 539 } 540 541 func (r *RekeyHandler2) RekeyStatusFinish(_ context.Context, _ int) (ret keybase1.Outcome, err error) { 542 r.rm.interruptCh <- interruptArg{rekeyInterrupt: RekeyInterruptUIFinished} 543 ret = keybase1.Outcome_NONE 544 return ret, err 545 } 546 547 func (r *RekeyHandler2) DebugShowRekeyStatus(ctx context.Context, sessionID int) error { 548 if r.G().Env.GetRunMode() == libkb.ProductionRunMode { 549 return errors.New("DebugShowRekeyStatus is a devel-only RPC") 550 } 551 552 me, err := libkb.LoadMe(libkb.NewLoadUserArg(r.G())) 553 if err != nil { 554 return err 555 } 556 557 arg := keybase1.RefreshArg{ 558 SessionID: sessionID, 559 ProblemSetDevices: keybase1.ProblemSetDevices{ 560 ProblemSet: keybase1.ProblemSet{ 561 User: keybase1.User{ 562 Uid: me.GetUID(), 563 Username: me.GetName(), 564 }, 565 Tlfs: []keybase1.ProblemTLF{ 566 { 567 Tlf: keybase1.TLF{ 568 // this is only for debugging 569 Name: "/keybase/private/" + me.GetName(), 570 Writers: []string{me.GetName()}, 571 Readers: []string{me.GetName()}, 572 IsPrivate: true, 573 }, 574 }, 575 }, 576 }, 577 }, 578 } 579 580 devices := me.GetComputedKeyFamily().GetAllActiveDevices() 581 arg.ProblemSetDevices.Devices = make([]keybase1.Device, len(devices)) 582 for i, dev := range devices { 583 arg.ProblemSetDevices.Devices[i] = *(dev.ProtExport()) 584 } 585 586 rekeyUI, err := r.G().UIRouter.GetRekeyUINoSessionID() 587 if err != nil { 588 return err 589 } 590 if rekeyUI == nil { 591 r.G().Log.Debug("no rekey ui, would have called refresh with this:") 592 r.G().Log.Debug("arg: %+v", arg) 593 return errors.New("no rekey ui") 594 } 595 596 return rekeyUI.Refresh(ctx, arg) 597 } 598 599 type unkeyedTLFsQueryResult struct { 600 Status libkb.AppStatus `json:"status"` 601 TLFs []keybase1.TLF `json:"tlfs"` 602 } 603 604 func (u *unkeyedTLFsQueryResult) GetAppStatus() *libkb.AppStatus { 605 return &u.Status 606 } 607 608 func (r *RekeyHandler2) GetRevokeWarning(ctx context.Context, arg keybase1.GetRevokeWarningArg) (res keybase1.RevokeWarning, err error) { 609 var u unkeyedTLFsQueryResult 610 actingDevice := arg.ActingDevice 611 if actingDevice.IsNil() { 612 actingDevice = r.G().Env.GetDeviceID() 613 } 614 mctx := libkb.NewMetaContext(ctx, r.G()) 615 616 err = r.G().API.GetDecode(mctx, libkb.APIArg{ 617 Endpoint: "kbfs/unkeyed_tlfs_from_pair", 618 SessionType: libkb.APISessionTypeREQUIRED, 619 Args: libkb.HTTPArgs{ 620 "self_device_id": libkb.S{Val: string(actingDevice)}, 621 "target_device_id": libkb.S{Val: string(arg.TargetDevice)}, 622 }, 623 }, &u) 624 res.EndangeredTLFs = u.TLFs 625 return res, err 626 } 627 628 func (r *RekeyHandler2) RekeySync(_ context.Context, arg keybase1.RekeySyncArg) error { 629 ch := make(chan struct{}) 630 ri := RekeyInterruptSync 631 if arg.Force { 632 ri = RekeyInterruptSyncForce 633 } 634 r.rm.interruptCh <- interruptArg{retCh: ch, rekeyInterrupt: ri} 635 <-ch 636 return nil 637 } 638 639 var _ keybase1.RekeyInterface = (*RekeyHandler2)(nil) 640 641 func keysSolveProblemTLF(keys []libkb.GenericKey, tlf keybase1.ProblemTLF) bool { 642 var ourKIDs []keybase1.KID 643 for _, key := range keys { 644 if key != nil { 645 ourKIDs = append(ourKIDs, key.GetKID()) 646 } 647 } 648 for _, theirKID := range tlf.Solution_kids { 649 for _, ourKID := range ourKIDs { 650 if ourKID.Equal(theirKID) { 651 return true 652 } 653 } 654 } 655 return false 656 } 657 658 func newProblemSetDevices(u *libkb.User, pset keybase1.ProblemSet) (keybase1.ProblemSetDevices, error) { 659 var set keybase1.ProblemSetDevices 660 set.ProblemSet = pset 661 662 ckf := u.GetComputedKeyFamily() 663 664 dset := make(map[keybase1.DeviceID]bool) 665 for _, f := range pset.Tlfs { 666 for _, kid := range f.Solution_kids { 667 dev, err := ckf.GetDeviceForKID(kid) 668 if err != nil { 669 return keybase1.ProblemSetDevices{}, err 670 } 671 if dset[dev.ID] { 672 continue 673 } 674 dset[dev.ID] = true 675 set.Devices = append(set.Devices, *(dev.ProtExport())) 676 } 677 } 678 return set, nil 679 }