github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/secret_store.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 libkb 5 6 import ( 7 "errors" 8 "fmt" 9 "sort" 10 "strings" 11 "sync" 12 "time" 13 14 keybase1 "github.com/keybase/client/go/protocol/keybase1" 15 ) 16 17 type SecretRetriever interface { 18 RetrieveSecret(m MetaContext) (LKSecFullSecret, error) 19 } 20 21 type SecretStoreOptions struct { 22 RandomPw bool 23 } 24 25 func DefaultSecretStoreOptions() SecretStoreOptions { 26 return SecretStoreOptions{} 27 } 28 29 type SecretStorer interface { 30 StoreSecret(m MetaContext, secret LKSecFullSecret) error 31 } 32 33 // SecretStore stores/retreives the keyring-resident secrets for a given user. 34 type SecretStore interface { 35 SecretRetriever 36 SecretStorer 37 GetOptions(mctx MetaContext) *SecretStoreOptions 38 SetOptions(mctx MetaContext, options *SecretStoreOptions) 39 } 40 41 // SecretStoreall stores/retreives the keyring-resider secrets for **all** users 42 // on this system. 43 type SecretStoreAll interface { 44 RetrieveSecret(mctx MetaContext, username NormalizedUsername) (LKSecFullSecret, error) 45 StoreSecret(mctx MetaContext, username NormalizedUsername, secret LKSecFullSecret) error 46 GetOptions(mctx MetaContext) *SecretStoreOptions 47 SetOptions(mctx MetaContext, options *SecretStoreOptions) 48 ClearSecret(mctx MetaContext, username NormalizedUsername) error 49 GetUsersWithStoredSecrets(mctx MetaContext) ([]string, error) 50 } 51 52 // SecretStoreImp is a specialization of a SecretStoreAll for just one username. 53 // You specify that username at the time on construction and then it doesn't change. 54 type SecretStoreImp struct { 55 username NormalizedUsername 56 store *SecretStoreLocked 57 secret LKSecFullSecret 58 sync.Mutex 59 } 60 61 var _ SecretStore = (*SecretStoreImp)(nil) 62 63 func (s *SecretStoreImp) RetrieveSecret(m MetaContext) (LKSecFullSecret, error) { 64 s.Lock() 65 defer s.Unlock() 66 67 if !s.secret.IsNil() { 68 return s.secret, nil 69 } 70 sec, err := s.store.RetrieveSecret(m, s.username) 71 if err != nil { 72 return sec, err 73 } 74 s.secret = sec 75 return sec, nil 76 } 77 78 func (s *SecretStoreImp) StoreSecret(m MetaContext, secret LKSecFullSecret) error { 79 s.Lock() 80 defer s.Unlock() 81 82 // clear out any in-memory secret in this instance 83 s.secret = LKSecFullSecret{} 84 return s.store.StoreSecret(m, s.username, secret) 85 } 86 87 func (s *SecretStoreImp) GetOptions(mctx MetaContext) *SecretStoreOptions { 88 if s.store != nil { 89 return s.store.GetOptions(mctx) 90 } 91 return nil 92 93 } 94 func (s *SecretStoreImp) SetOptions(mctx MetaContext, options *SecretStoreOptions) { 95 if s.store != nil { 96 s.store.SetOptions(mctx, options) 97 } 98 } 99 100 // NewSecretStore returns a SecretStore interface that is only used for 101 // a short period of time (i.e. one function block). Multiple calls to RetrieveSecret() 102 // will only call the underlying store.RetrieveSecret once. 103 func NewSecretStore(m MetaContext, username NormalizedUsername) SecretStore { 104 store := m.G().SecretStore() 105 if store != nil { 106 m.Debug("NewSecretStore: reifying SecretStoreImp for %q", username) 107 return &SecretStoreImp{ 108 username: username, 109 store: store, 110 } 111 } 112 return nil 113 } 114 115 func GetConfiguredAccountsFromProvisionedUsernames(m MetaContext, s SecretStoreAll, currentUsername NormalizedUsername, allUsernames []NormalizedUsername) ([]keybase1.ConfiguredAccount, error) { 116 if !currentUsername.IsNil() { 117 allUsernames = append(allUsernames, currentUsername) 118 } 119 120 accounts := make(map[NormalizedUsername]keybase1.ConfiguredAccount) 121 for _, username := range allUsernames { 122 accounts[username] = keybase1.ConfiguredAccount{ 123 Username: username.String(), 124 IsCurrent: username.Eq(currentUsername), 125 } 126 } 127 128 // Get the full names 129 uids := make([]keybase1.UID, len(allUsernames)) 130 for idx, username := range allUsernames { 131 uids[idx] = GetUIDByNormalizedUsername(m.G(), username) 132 } 133 usernamePackages, err := m.G().UIDMapper.MapUIDsToUsernamePackages(m.Ctx(), m.G(), 134 uids, time.Hour*24, time.Second*10, false) 135 if err != nil { 136 if usernamePackages != nil { 137 // If data is returned, interpret the error as a warning 138 m.G().Log.CInfof(m.Ctx(), 139 "error while retrieving full names: %+v", err) 140 } else { 141 return nil, err 142 } 143 } 144 for _, uPackage := range usernamePackages { 145 if uPackage.FullName == nil { 146 continue 147 } 148 if account, ok := accounts[uPackage.NormalizedUsername]; ok { 149 account.Fullname = uPackage.FullName.FullName 150 accounts[uPackage.NormalizedUsername] = account 151 } 152 } 153 154 // Check for secrets 155 156 var storedSecretUsernames []string 157 if s != nil { 158 storedSecretUsernames, err = s.GetUsersWithStoredSecrets(m) 159 } 160 if err != nil { 161 return nil, err 162 } 163 164 for _, username := range storedSecretUsernames { 165 nu := NewNormalizedUsername(username) 166 account, ok := accounts[nu] 167 if ok { 168 account.HasStoredSecret = true 169 accounts[nu] = account 170 } 171 } 172 173 configuredAccounts := make([]keybase1.ConfiguredAccount, 0, len(accounts)) 174 for _, account := range accounts { 175 configuredAccounts = append(configuredAccounts, account) 176 } 177 178 loginTimes, err := getLoginTimes(m) 179 if err != nil { 180 m.Warning("Failed to get login times: %s", err) 181 loginTimes = make(loginTimeMap) 182 } 183 184 sort.Slice(configuredAccounts, func(i, j int) bool { 185 iUsername := configuredAccounts[i].Username 186 jUsername := configuredAccounts[j].Username 187 iTime, iOk := loginTimes[NormalizedUsername(iUsername)] 188 jTime, jOk := loginTimes[NormalizedUsername(jUsername)] 189 if !iOk && !jOk { 190 iSignedIn := configuredAccounts[i].HasStoredSecret 191 jSignedIn := configuredAccounts[j].HasStoredSecret 192 if iSignedIn != jSignedIn { 193 return iSignedIn 194 } 195 return strings.Compare(iUsername, jUsername) < 0 196 } 197 if !iOk { 198 return false 199 } 200 if !jOk { 201 return true 202 } 203 return iTime.After(jTime) 204 }) 205 206 return configuredAccounts, nil 207 } 208 209 func GetConfiguredAccounts(m MetaContext, s SecretStoreAll) ([]keybase1.ConfiguredAccount, error) { 210 currentUsername, allUsernames, err := GetAllProvisionedUsernames(m) 211 if err != nil { 212 return nil, err 213 } 214 return GetConfiguredAccountsFromProvisionedUsernames(m, s, currentUsername, allUsernames) 215 } 216 217 func ClearStoredSecret(m MetaContext, username NormalizedUsername) error { 218 ss := m.G().SecretStore() 219 if ss == nil { 220 return nil 221 } 222 return ss.ClearSecret(m, username) 223 } 224 225 // SecretStoreLocked protects a SecretStoreAll with a mutex. It wraps two different 226 // SecretStoreAlls: one in memory and one in disk. In all cases, we always have a memory 227 // backing. If the OS and options provide one, we can additionally have a disk-backed 228 // secret store. It's a write-through cache, so on RetrieveSecret, the memory store 229 // will be checked first, and then the disk store. 230 type SecretStoreLocked struct { 231 sync.Mutex 232 mem SecretStoreAll 233 disk SecretStoreAll 234 } 235 236 func NewSecretStoreLocked(m MetaContext) *SecretStoreLocked { 237 // We always make an on-disk secret store, but if the user has opted not 238 // to remember their passphrase, we don't store it on-disk. 239 return &SecretStoreLocked{ 240 mem: NewSecretStoreMem(), 241 disk: NewSecretStoreAll(m), 242 } 243 } 244 245 func (s *SecretStoreLocked) isNil() bool { 246 return s.mem == nil && s.disk == nil 247 } 248 249 func (s *SecretStoreLocked) ClearMem() { 250 s.mem = NewSecretStoreMem() 251 } 252 253 func (s *SecretStoreLocked) RetrieveSecret(m MetaContext, username NormalizedUsername) (LKSecFullSecret, error) { 254 if s == nil || s.isNil() { 255 return LKSecFullSecret{}, nil 256 } 257 s.Lock() 258 defer s.Unlock() 259 260 res, err := s.mem.RetrieveSecret(m, username) 261 if !res.IsNil() && err == nil { 262 return res, nil 263 } 264 if err != nil { 265 m.Debug("SecretStoreLocked#RetrieveSecret: memory fetch error: %s", err.Error()) 266 } 267 if s.disk == nil { 268 return res, err 269 } 270 271 res, err = s.disk.RetrieveSecret(m, username) 272 if err != nil { 273 return res, err 274 } 275 tmp := s.mem.StoreSecret(m, username, res) 276 if tmp != nil { 277 m.Debug("SecretStoreLocked#RetrieveSecret: failed to store secret in memory: %s", tmp.Error()) 278 } 279 return res, err 280 } 281 282 func (s *SecretStoreLocked) StoreSecret(m MetaContext, username NormalizedUsername, secret LKSecFullSecret) error { 283 if s == nil || s.isNil() { 284 return nil 285 } 286 s.Lock() 287 defer s.Unlock() 288 err := s.mem.StoreSecret(m, username, secret) 289 if err != nil { 290 m.Debug("SecretStoreLocked#StoreSecret: failed to store secret in memory: %s", err.Error()) 291 } 292 if s.disk == nil { 293 return err 294 } 295 if !m.G().Env.GetRememberPassphrase(username) { 296 m.Debug("SecretStoreLocked: should not remember passphrase for %s; not storing on disk", username) 297 return err 298 } 299 return s.disk.StoreSecret(m, username, secret) 300 } 301 302 func (s *SecretStoreLocked) ClearSecret(m MetaContext, username NormalizedUsername) error { 303 304 if username.IsNil() { 305 m.Debug("NOOPing SecretStoreLocked#ClearSecret for empty username") 306 return nil 307 } 308 309 if s == nil || s.isNil() { 310 return nil 311 } 312 s.Lock() 313 defer s.Unlock() 314 315 err := s.mem.ClearSecret(m, username) 316 if err != nil { 317 m.Debug("SecretStoreLocked#ClearSecret: failed to clear memory: %s", err.Error()) 318 } 319 if s.disk == nil { 320 return err 321 } 322 return s.disk.ClearSecret(m, username) 323 } 324 325 func (s *SecretStoreLocked) GetUsersWithStoredSecrets(m MetaContext) ([]string, error) { 326 if s == nil || s.isNil() { 327 return nil, nil 328 } 329 s.Lock() 330 defer s.Unlock() 331 users := make(map[string]struct{}) 332 333 memUsers, memErr := s.mem.GetUsersWithStoredSecrets(m) 334 if memErr == nil { 335 for _, memUser := range memUsers { 336 users[memUser] = struct{}{} 337 } 338 } 339 if s.disk == nil { 340 return memUsers, memErr 341 } 342 diskUsers, diskErr := s.disk.GetUsersWithStoredSecrets(m) 343 if diskErr == nil { 344 for _, diskUser := range diskUsers { 345 users[diskUser] = struct{}{} 346 } 347 } 348 if memErr != nil && diskErr != nil { 349 return nil, CombineErrors(memErr, diskErr) 350 } 351 var ret []string 352 for user := range users { 353 ret = append(ret, user) 354 } 355 return ret, nil 356 } 357 358 func (s *SecretStoreLocked) PrimeSecretStores(mctx MetaContext) (err error) { 359 if mctx.G().Env.GetSecretStorePrimingDisabled() { 360 mctx.Debug("Skipping PrimeSecretStores, disabled in env") 361 return nil 362 } 363 if s == nil || s.isNil() { 364 return errors.New("secret store is not available") 365 } 366 if s.disk != nil { 367 err = PrimeSecretStore(mctx, s.disk) 368 if err != nil { 369 return err 370 } 371 } 372 err = PrimeSecretStore(mctx, s.mem) 373 return err 374 } 375 376 func (s *SecretStoreLocked) IsPersistent() bool { 377 if s == nil || s.isNil() { 378 return false 379 } 380 return s.disk != nil 381 } 382 383 func (s *SecretStoreLocked) GetOptions(mctx MetaContext) *SecretStoreOptions { 384 if s.disk != nil { 385 return s.disk.GetOptions(mctx) 386 } 387 return nil 388 } 389 func (s *SecretStoreLocked) SetOptions(mctx MetaContext, options *SecretStoreOptions) { 390 if s.disk != nil { 391 s.disk.SetOptions(mctx, options) 392 } 393 } 394 395 // PrimeSecretStore runs a test with current platform's secret store, trying to 396 // store, retrieve, and then delete a secret with an arbitrary name. This should 397 // be done before provisioning or logging in 398 func PrimeSecretStore(mctx MetaContext, ss SecretStoreAll) (err error) { 399 defer func() { 400 if err != nil { 401 go reportPrimeSecretStoreFailure(mctx.BackgroundWithLogTags(), ss, err) 402 } 403 }() 404 defer mctx.Trace("PrimeSecretStore", &err)() 405 406 // Generate test username and test secret 407 testUsername, err := RandString("test_ss_", 5) 408 // RandString returns base32 encoded random bytes, make it look like a 409 // Keybase username. This is not required, though. 410 testUsername = strings.ToLower(strings.ReplaceAll(testUsername, "=", "")) 411 if err != nil { 412 return err 413 } 414 randBytes, err := RandBytes(LKSecLen) 415 if err != nil { 416 return err 417 } 418 mctx.Debug("PrimeSecretStore: priming secret store with username %q and secret %v", testUsername, randBytes) 419 testNormUsername := NormalizedUsername(testUsername) 420 var secretF [LKSecLen]byte 421 copy(secretF[:], randBytes) 422 testSecret := LKSecFullSecret{f: &secretF} 423 424 defer func() { 425 err2 := ss.ClearSecret(mctx, testNormUsername) 426 mctx.Debug("PrimeSecretStore: clearing test secret store entry") 427 if err2 != nil { 428 mctx.Debug("PrimeSecretStore: clearing secret store entry returned an error: %s", err2) 429 if err == nil { 430 err = err2 431 } else { 432 mctx.Debug("suppressing store clearing error because something else has errored prior") 433 } 434 } 435 }() 436 437 // Try to fetch first, we should get an error back. 438 _, err = ss.RetrieveSecret(mctx, testNormUsername) 439 if err == nil { 440 return errors.New("managed to retrieve secret before storing it") 441 } else if err != nil { 442 mctx.Debug("PrimeSecretStore: error when retrieving secret that wasn't stored yet: %q, as expected", err) 443 } 444 445 // Put secret in secret store through `SecretStore` interface. 446 err = ss.StoreSecret(mctx, testNormUsername, testSecret) 447 if err != nil { 448 return fmt.Errorf("error while storing secret: %s", err) 449 } 450 451 // Recreate test store with same username, try to retrieve secret. 452 retrSecret, err := ss.RetrieveSecret(mctx, testNormUsername) 453 if err != nil { 454 return fmt.Errorf("error while retrieving secret: %s", err) 455 } 456 mctx.Debug("PrimeSecretStore: retrieved secret: %v", retrSecret.f) 457 if !retrSecret.Equal(testSecret) { 458 return errors.New("managed to retrieve test secret but it didn't match the stored one") 459 } 460 461 mctx.Debug("PrimeSecretStore: retrieved secret matched!") 462 return nil 463 } 464 465 func reportPrimeSecretStoreFailure(mctx MetaContext, ss SecretStoreAll, reportErr error) { 466 var err error 467 defer mctx.Trace("reportPrimeSecretStoreFailure", &err)() 468 osVersion, osBuild, err := OSVersionAndBuild() 469 if err != nil { 470 mctx.Debug("os info error: %v", err) 471 } 472 apiArg := APIArg{ 473 Endpoint: "device/error", 474 SessionType: APISessionTypeNONE, 475 Args: HTTPArgs{ 476 "event": S{Val: "prime_secret_store"}, 477 "msg": S{Val: fmt.Sprintf("[%T] [%T] %v", ss, reportErr, reportErr.Error())}, 478 "run_mode": S{Val: string(mctx.G().GetRunMode())}, 479 "kb_version": S{Val: VersionString()}, 480 "os_version": S{Val: osVersion}, 481 "os_build": S{Val: osBuild}, 482 }, 483 RetryCount: 3, 484 InitialTimeout: 10 * time.Second, 485 } 486 var apiRes AppStatusEmbed 487 err = mctx.G().API.PostDecode(mctx, apiArg, &apiRes) 488 } 489 490 type loginTimeMap map[NormalizedUsername]time.Time 491 492 func loginTimesDbKey(mctx MetaContext) DbKey { 493 return DbKey{ 494 // Should not be per-user, so not using uid in the db key 495 Typ: DBLoginTimes, 496 Key: "", 497 } 498 } 499 500 func getLoginTimes(mctx MetaContext) (ret loginTimeMap, err error) { 501 found, err := mctx.G().LocalDb.GetInto(&ret, loginTimesDbKey(mctx)) 502 if err != nil { 503 return ret, err 504 } 505 if !found { 506 ret = make(loginTimeMap) 507 } 508 return ret, nil 509 } 510 511 func RecordLoginTime(mctx MetaContext, username NormalizedUsername) (err error) { 512 ret, err := getLoginTimes(mctx) 513 if err != nil { 514 mctx.Warning("failed to get login times from db; overwriting existing data: %s", err) 515 ret = make(loginTimeMap) 516 } 517 ret[username] = time.Now() 518 return mctx.G().LocalDb.PutObj(loginTimesDbKey(mctx), nil, ret) 519 }