/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/libkb/active_device.go
752 строки
21 KB
zoom-ua
Avoid owner device lookup during provisioning (#29468)
28 июл 2026, 18:48
Не верифицирован
28 июл 2026, 18:48
a701bba
Код
Авторство
О чём код?
package libkb import ( "context" "errors" "fmt" "sync" "github.com/keybase/client/go/protocol/keybase1" ) type ActiveDevice struct { uv keybase1.UserVersion deviceID keybase1.DeviceID deviceName string deviceCtime keybase1.Time signingKey GenericKey // cached secret signing key encryptionKey GenericKey // cached secret encryption key nistFactory *NISTFactory // Non-Interactive Session Token secretSyncer *SecretSyncer passphrase *PassphraseStreamCache // This can be a paper key or a regular device key if we are self // provisioning. provisioningKey *SelfDestructingDeviceWithKeys secretPromptCancelTimer CancelTimer keychainMode KeychainMode sync.RWMutex } func (a *ActiveDevice) Dump(m MetaContext, prefix string) { m.Debug("%sActiveDevice: %p", prefix, a) m.Debug("%sUserVersion: %+v", prefix, a.uv) m.Debug("%sUsername (via env): %s", prefix, a.Username(m)) m.Debug("%sDeviceID: %s", prefix, a.deviceID) m.Debug("%sDeviceName: %s", prefix, a.deviceName) m.Debug("%sDeviceCtime: %s", prefix, keybase1.FormatTime(a.deviceCtime)) if a.signingKey != nil { m.Debug("%sSigKey: %s", prefix, a.signingKey.GetKID()) } if a.encryptionKey != nil { m.Debug("%sEncKey: %s", prefix, a.encryptionKey.GetKID()) } m.Debug("%sPassphraseCache: cacheObj=%v; valid=%v", prefix, (a.passphrase != nil), (a.passphrase != nil && a.passphrase.ValidPassphraseStream())) m.Debug("%sProvisioningKeyCache: %v", prefix, (a.provisioningKey != nil && a.provisioningKey.DeviceWithKeys() != nil)) m.Debug("%sKeychainMode: %v", prefix, a.keychainMode) } // NewProvisionalActiveDevice creates an ActiveDevice that is "provisional", in // that it should not be considered the global ActiveDevice. Instead, it should // reside in thread-local context, and can be weaved through the login // machinery without trampling the actual global ActiveDevice. func NewProvisionalActiveDevice(m MetaContext, uv keybase1.UserVersion, d keybase1.DeviceID, sigKey GenericKey, encKey GenericKey, deviceName string, keychainMode KeychainMode) *ActiveDevice { return &ActiveDevice{ uv: uv, deviceID: d, deviceName: deviceName, signingKey: sigKey, encryptionKey: encKey, nistFactory: NewNISTFactory(m.G(), uv.Uid, d, sigKey), secretSyncer: NewSecretSyncer(m.G()), keychainMode: keychainMode, } } func NewActiveDevice() *ActiveDevice { return &ActiveDevice{} } func NewProvisioningKeyActiveDevice(m MetaContext, uv keybase1.UserVersion, d *DeviceWithKeys) *ActiveDevice { ret := NewActiveDeviceWithDeviceWithKeys(m, uv, d) ret.provisioningKey = NewSelfDestructingDeviceWithKeys(m, d, ProvisioningKeyMemoryTimeout) return ret } func NewActiveDeviceWithDeviceWithKeys(m MetaContext, uv keybase1.UserVersion, d *DeviceWithKeys) *ActiveDevice { return &ActiveDevice{ uv: uv, deviceID: d.deviceID, deviceName: d.deviceName, deviceCtime: d.deviceCtime, signingKey: d.signingKey, encryptionKey: d.encryptionKey, nistFactory: NewNISTFactory(m.G(), uv.Uid, d.deviceID, d.signingKey), secretSyncer: NewSecretSyncer(m.G()), keychainMode: d.keychainMode, } } func (a *ActiveDevice) ClearCaches() { a.Lock() defer a.Unlock() a.passphrase = nil a.provisioningKey = nil a.secretPromptCancelTimer.Reset() } // Copy ActiveDevice info from the given ActiveDevice. func (a *ActiveDevice) Copy(m MetaContext, src *ActiveDevice) error { // Take a consistent snapshot of the src device. Be careful not to hold // locks on both devices at once. src.Lock() uv := src.uv deviceID := src.deviceID sigKey := src.signingKey encKey := src.encryptionKey name := src.deviceName ctime := src.deviceCtime keychainMode := src.keychainMode src.Unlock() return a.Set(m, uv, deviceID, sigKey, encKey, name, ctime, keychainMode) } func (a *ActiveDevice) SetOrClear(m MetaContext, a2 *ActiveDevice) error { // Always clear, if we are also setting we set all new values. err := a.Clear() if err != nil { return err } if a2 == nil { return nil } return a.Copy(m, a2) } // Set acquires the write lock and sets all the fields in ActiveDevice. // The acct parameter is not used for anything except to help ensure // that this is called from inside a LoginState account request. func (a *ActiveDevice) Set(m MetaContext, uv keybase1.UserVersion, deviceID keybase1.DeviceID, sigKey, encKey GenericKey, deviceName string, deviceCtime keybase1.Time, keychainMode KeychainMode, ) error { a.Lock() defer a.Unlock() if err := a.internalUpdateUserVersionDeviceID(uv, deviceID); err != nil { return err } a.signingKey = sigKey a.encryptionKey = encKey a.deviceName = deviceName a.deviceCtime = deviceCtime a.nistFactory = NewNISTFactory(m.G(), uv.Uid, deviceID, sigKey) a.secretSyncer = NewSecretSyncer(m.G()) a.keychainMode = keychainMode return nil } func (a *ActiveDevice) KeychainMode() KeychainMode { a.Lock() defer a.Unlock() return a.keychainMode } // setSigningKey acquires the write lock and sets the signing key. // The acct parameter is not used for anything except to help ensure // that this is called from inside a LogingState account request. func (a *ActiveDevice) setSigningKey(g *GlobalContext, uv keybase1.UserVersion, deviceID keybase1.DeviceID, sigKey GenericKey, deviceName string, ) error { a.Lock() defer a.Unlock() if err := a.internalUpdateUserVersionDeviceID(uv, deviceID); err != nil { return err } a.signingKey = sigKey if len(deviceName) > 0 { a.deviceName = deviceName } a.nistFactory = NewNISTFactory(g, uv.Uid, deviceID, sigKey) return nil } // setEncryptionKey acquires the write lock and sets the encryption key. // The acct parameter is not used for anything except to help ensure // that this is called from inside a LogingState account request. func (a *ActiveDevice) setEncryptionKey(uv keybase1.UserVersion, deviceID keybase1.DeviceID, encKey GenericKey) error { a.Lock() defer a.Unlock() if err := a.internalUpdateUserVersionDeviceID(uv, deviceID); err != nil { return err } a.encryptionKey = encKey return nil } // should only called by the functions in this type, with the write lock. func (a *ActiveDevice) internalUpdateUserVersionDeviceID(uv keybase1.UserVersion, deviceID keybase1.DeviceID) error { if uv.IsNil() { return errors.New("ActiveDevice.set with nil uid") } if deviceID.IsNil() { return errors.New("ActiveDevice.set with nil deviceID") } if a.uv.IsNil() && a.deviceID.IsNil() { a.uv = uv a.deviceID = deviceID } else if !a.uv.Eq(uv) { return errors.New("ActiveDevice.set uid mismatch") } else if !a.deviceID.Eq(deviceID) { return errors.New("ActiveDevice.set deviceID mismatch") } return nil } func (a *ActiveDevice) Clear() error { _, err := a.clear() return err } func (a *ActiveDevice) ClearGetKeychainMode() (KeychainMode, error) { return a.clear() } // Clear acquires the write lock and resets all the fields to zero values. func (a *ActiveDevice) clear() (KeychainMode, error) { a.Lock() defer a.Unlock() a.uv = keybase1.UserVersion{} a.deviceID = "" a.deviceName = "" a.signingKey = nil a.encryptionKey = nil a.nistFactory = nil a.passphrase = nil a.provisioningKey = nil a.secretPromptCancelTimer.Reset() ret := a.keychainMode a.keychainMode = KeychainModeNone return ret, nil } func (a *ActiveDevice) SecretPromptCancelTimer() *CancelTimer { a.RLock() defer a.RUnlock() return &a.secretPromptCancelTimer } // UID returns the user ID that was provided when the device keys were cached. // Safe for use by concurrent goroutines. func (a *ActiveDevice) UID() keybase1.UID { a.RLock() defer a.RUnlock() return a.uv.Uid } func (a *ActiveDevice) UIDAndEncryptionKey() (keybase1.UID, GenericKey) { a.RLock() defer a.RUnlock() return a.uv.Uid, a.encryptionKey } func (a *ActiveDevice) UserVersion() keybase1.UserVersion { a.RLock() defer a.RUnlock() return a.uv } // Username tries to get the active user's username by looking into the current // environment and mapping an UID to a username based on our config file. It // won't work halfway through a provisioning. func (a *ActiveDevice) Username(m MetaContext) NormalizedUsername { return m.G().Env.GetUsernameForUID(a.UID()) } // DeviceID returns the device ID that was provided when the device keys were // cached. Safe for use by concurrent goroutines. func (a *ActiveDevice) DeviceID() keybase1.DeviceID { a.RLock() defer a.RUnlock() return a.deviceID } func (a *ActiveDevice) DeviceType(mctx MetaContext) (keybase1.DeviceTypeV2, error) { if a.secretSyncer.keys == nil { mctx.Debug("keys are not synced with the server for this ActiveDevice. lets do that right now") _, err := a.SyncSecretsForce(mctx) if err != nil { return keybase1.DeviceTypeV2_NONE, err } } devices, err := a.secretSyncer.Devices() if err != nil { return keybase1.DeviceTypeV2_NONE, err } for devID, dev := range devices { if devID == a.DeviceID() { return dev.Type, nil } } return keybase1.DeviceTypeV2_NONE, NotFoundError{ Msg: "Not found: device type", } } // SigningKey returns the signing key for the active device. // Safe for use by concurrent goroutines. func (a *ActiveDevice) SigningKey() (GenericKey, error) { a.RLock() defer a.RUnlock() if a.signingKey == nil { return nil, NotFoundError{ Msg: "Not found: device signing key", } } return a.signingKey, nil } // SigningKeyWithUID returns the signing key for the active device. // Returns an error if uid is not active. // Safe for use by concurrent goroutines. func (a *ActiveDevice) SigningKeyWithUID(uid keybase1.UID) (GenericKey, error) { a.RLock() defer a.RUnlock() if a.uv.Uid.IsNil() { return nil, NotFoundError{ Msg: "Not found: device signing key (no active user)", } } if a.uv.Uid != uid { return nil, fmt.Errorf("device signing key for non-active user: %v != %v", a.uv.Uid, uid) } if a.signingKey == nil { return nil, NotFoundError{ Msg: "Not found: device signing key", } } return a.signingKey, nil } // EncryptionKey returns the encryption key for the active device. // Safe for use by concurrent goroutines. func (a *ActiveDevice) EncryptionKey() (GenericKey, error) { a.RLock() defer a.RUnlock() if a.encryptionKey == nil { return nil, NotFoundError{ Msg: "Not found: device encryption key", } } return a.encryptionKey, nil } // EncryptionKeyWithUID returns the encryption key for the active device. // Returns an error if uid is not active. // Safe for use by concurrent goroutines. func (a *ActiveDevice) EncryptionKeyWithUID(uid keybase1.UID) (GenericKey, error) { a.RLock() defer a.RUnlock() if a.uv.Uid.IsNil() { return nil, NotFoundError{ Msg: "Not found: device encryption key (no active user)", } } if a.uv.Uid != uid { return nil, fmt.Errorf("device encryption key for non-active user: %v != %v", a.uv.Uid, uid) } if a.encryptionKey == nil { return nil, NotFoundError{ Msg: "Not found: device encryption key", } } return a.encryptionKey, nil } // NaclEncryptionKey returns the encryption key for the active device, as a // NaclDHKeyPair. If the cast fails (though that should never happen), it // returns an error. func (a *ActiveDevice) NaclEncryptionKey() (*NaclDHKeyPair, error) { genericKey, err := a.EncryptionKey() if err != nil { return nil, err } naclKey, ok := genericKey.(NaclDHKeyPair) if !ok { return nil, fmt.Errorf("expected NaclDHKeyPair, got %T", genericKey) } return &naclKey, nil } // KeyByType returns a cached key based on SecretKeyType. // Safe for use by concurrent goroutines. func (a *ActiveDevice) KeyByType(t SecretKeyType) (GenericKey, error) { switch t { case DeviceSigningKeyType: return a.SigningKey() case DeviceEncryptionKeyType: return a.EncryptionKey() default: return nil, fmt.Errorf("Invalid type %v", t) } } // KeyByTypeWithUID is like KeyByType but returns an error if uid is not active. // Safe for use by concurrent goroutines. func (a *ActiveDevice) KeyByTypeWithUID(uid keybase1.UID, t SecretKeyType) (GenericKey, error) { switch t { case DeviceSigningKeyType: return a.SigningKeyWithUID(uid) case DeviceEncryptionKeyType: return a.EncryptionKeyWithUID(uid) default: return nil, fmt.Errorf("Invalid type %v", t) } } // AllFields returns all the ActiveDevice fields via one lock for consistency. // Safe for use by concurrent goroutines. func (a *ActiveDevice) AllFields() (uv keybase1.UserVersion, deviceID keybase1.DeviceID, deviceName string, sigKey GenericKey, encKey GenericKey) { a.RLock() defer a.RUnlock() return a.uv, a.deviceID, a.deviceName, a.signingKey, a.encryptionKey } func (a *ActiveDevice) Name() string { a.RLock() defer a.RUnlock() return a.deviceName } func (a *ActiveDevice) HaveKeys() bool { a.RLock() defer a.RUnlock() return a.signingKey != nil && a.encryptionKey != nil } func (a *ActiveDevice) Valid() bool { a.RLock() defer a.RUnlock() return a.valid() } func (a *ActiveDevice) valid() bool { return a.signingKey != nil && a.encryptionKey != nil && !a.uv.IsNil() && !a.deviceID.IsNil() && a.deviceName != "" } func (a *ActiveDevice) Ctime(m MetaContext) (keybase1.Time, error) { uv, deviceID, ctime := a.ctimeInfo() if uv.IsNil() || deviceID.IsNil() { return 0, errors.New("active device is not valid") } if ctime > 0 { return ctime, nil } // Resolve the device through the validated UPAK cache. This retries with a // forced poll if the device is missing, which handles newly provisioned // devices without relying on the unauthenticated key/owner/device endpoint. upak, err := m.G().GetUPAKLoader().LoadUPAKWithDeviceID(m.Ctx(), uv.Uid, deviceID) if err != nil { return 0, err } if !upak.Current.ToUserVersion().Eq(uv) { return 0, NewUIDMismatchError("active user changed during ctime lookup") } device := upak.Current.FindSigningDeviceKey(deviceID) if device == nil { return 0, NoKeyError{"no signing device key found for user"} } if device.Base.Revocation != nil { return 0, NewKeyRevokedError("active device") } if device.Base.CTime <= 0 { return 0, NotFoundError{Msg: "device ctime not found"} } // set the ctime value under a write lock a.Lock() defer a.Unlock() if !a.uv.Eq(uv) || !a.deviceID.Eq(deviceID) { return 0, errors.New("active device changed during ctime lookup") } a.deviceCtime = device.Base.CTime return a.deviceCtime, nil } func (a *ActiveDevice) ctimeInfo() (keybase1.UserVersion, keybase1.DeviceID, keybase1.Time) { a.RLock() defer a.RUnlock() return a.uv, a.deviceID, a.deviceCtime } func (a *ActiveDevice) DeviceKeys() (*DeviceWithKeys, error) { a.RLock() defer a.RUnlock() if !a.valid() { return nil, errors.New("active device is not valid") } return NewDeviceWithKeysOnly(a.signingKey, a.encryptionKey, a.keychainMode), nil } func (a *ActiveDevice) IsValidFor(uid keybase1.UID, deviceID keybase1.DeviceID) bool { a.RLock() defer a.RUnlock() if a.signingKey == nil || a.encryptionKey == nil { return false } if !uid.Equal(a.uv.Uid) { return false } if !deviceID.Eq(a.deviceID) { return false } return true } func (a *ActiveDevice) NIST(ctx context.Context) (*NIST, error) { a.RLock() defer a.RUnlock() return a.nistLocked(ctx) } func (a *ActiveDevice) NISTWebAuthToken(ctx context.Context) (*NIST, error) { a.RLock() defer a.RUnlock() return a.nistFactory.GenerateWebAuthToken(ctx) } func (a *ActiveDevice) nistLocked(ctx context.Context) (*NIST, error) { nist, err := a.nistFactory.NIST(ctx) if err != nil { return nil, err } if nist == nil { return nil, nil } uid := a.nistFactory.UID() if !uid.Equal(a.uv.Uid) { return nil, NewUIDMismatchError(fmt.Sprintf("NIST generation error, UIDs didn't match; ActiveDevice said %s, but NIST factory said %s", a.uv.Uid, uid)) } return nist, nil } func (a *ActiveDevice) NISTAndUIDDeviceID(ctx context.Context) (*NIST, keybase1.UID, keybase1.DeviceID, error) { a.RLock() defer a.RUnlock() nist, err := a.nistLocked(ctx) return nist, a.uv.Uid, a.deviceID, err } func (a *ActiveDevice) SyncSecretsForUID(m MetaContext, u keybase1.UID, force bool) (ret *SecretSyncer, err error) { defer m.Trace("ActiveDevice#SyncSecretsForUID", &err)() a.RLock() s := a.secretSyncer uid := a.uv.Uid a.RUnlock() if !u.IsNil() && !uid.Equal(u) { return nil, fmt.Errorf("Wrong UID for sync secrets: %s != %s", uid, u) } if s == nil { return nil, fmt.Errorf("Can't sync secrets: nil secret syncer") } if uid.IsNil() { return nil, fmt.Errorf("can't run secret syncer without a UID") } if err = RunSyncer(m, s, uid, true, force); err != nil { return nil, err } return s, nil } func (a *ActiveDevice) SyncSecrets(m MetaContext) (ret *SecretSyncer, err error) { defer m.Trace("ActiveDevice#SyncSecrets", &err)() var zed keybase1.UID return a.SyncSecretsForUID(m, zed, false /* force */) } func (a *ActiveDevice) SyncSecretsForce(m MetaContext) (ret *SecretSyncer, err error) { defer m.Trace("ActiveDevice#SyncSecretsForce", &err)() var zed keybase1.UID return a.SyncSecretsForUID(m, zed, true /* force */) } func (a *ActiveDevice) SyncSecretsFromCache(m MetaContext) (ret *SecretSyncer, err error) { defer m.Trace("ActiveDevice#SyncSecretsFromCache", &err)() a.RLock() s := a.secretSyncer uid := a.uv.Uid a.RUnlock() if s == nil { return nil, fmt.Errorf("Can't sync secrets: nil secret syncer") } if uid.IsNil() { return nil, fmt.Errorf("can't run secret syncer without a UID") } if err = RunSyncerCached(m, s, uid); err != nil { return nil, err } return s, nil } func (a *ActiveDevice) CheckForUsername(m MetaContext, n NormalizedUsername, suppressNetworkErrors bool) (err error) { a.RLock() uid := a.uv.Uid deviceID := a.deviceID valid := a.valid() a.RUnlock() if !valid { return NoActiveDeviceError{} } return m.G().GetUPAKLoader().CheckDeviceForUIDAndUsername(m.Ctx(), uid, deviceID, n, suppressNetworkErrors) } func (a *ActiveDevice) ProvisioningKeyWrapper(m MetaContext) *SelfDestructingDeviceWithKeys { a.RLock() defer a.RUnlock() return a.provisioningKey } func (a *ActiveDevice) ProvisioningKey(m MetaContext) *DeviceWithKeys { a.RLock() defer a.RUnlock() if a.provisioningKey == nil { return nil } return a.provisioningKey.DeviceWithKeys() } func (a *ActiveDevice) ClearProvisioningKey(m MetaContext) { a.Lock() defer a.Unlock() a.provisioningKey = nil } func (a *ActiveDevice) CacheProvisioningKey(m MetaContext, k *DeviceWithKeys) { a.Lock() defer a.Unlock() a.provisioningKey = NewSelfDestructingDeviceWithKeys(m, k, ProvisioningKeyMemoryTimeout) } func (a *ActiveDevice) PassphraseStreamCache() *PassphraseStreamCache { a.RLock() defer a.RUnlock() return a.passphrase } func (a *ActiveDevice) PassphraseStream() *PassphraseStream { a.RLock() defer a.RUnlock() c := a.PassphraseStreamCache() if c == nil || !c.ValidPassphraseStream() { return nil } return c.PassphraseStream() } func (a *ActiveDevice) TriplesecAndGeneration() (Triplesec, PassphraseGeneration) { a.RLock() defer a.RUnlock() var zed PassphraseGeneration c := a.PassphraseStreamCache() if c == nil { return nil, zed } return c.TriplesecAndGeneration() } func (a *ActiveDevice) CachePassphraseStream(c *PassphraseStreamCache) { a.Lock() defer a.Unlock() a.passphrase = c } func (a *ActiveDevice) ClearPassphraseStreamCache() { a.Lock() defer a.Unlock() a.passphrase = nil } func (a *ActiveDevice) ClearPassphraseStreamCacheIfOutdated(mctx MetaContext) error { pps := a.PassphraseStream() if pps == nil { return nil } outdated, err := pps.SyncAndCheckIfOutdated(mctx) if err != nil { return err } if outdated { a.ClearPassphraseStreamCache() } return nil } func (a *ActiveDevice) SigningKeyForUID(u keybase1.UID) GenericKey { a.RLock() defer a.RUnlock() if !a.UID().Equal(u) { return nil } return a.signingKey } func (a *ActiveDevice) Keyring(m MetaContext) (ret *SKBKeyringFile, err error) { defer m.Trace("ActiveDevice#Keyring", &err)() un := a.Username(m) if un.IsNil() { return nil, NewNoUsernameError() } m.Debug("Account: loading keyring for %s", un) ret, err = LoadSKBKeyring(m, un) if err != nil { return nil, err } return ret, nil } func (a *ActiveDevice) CopyCacheToLoginContextIfForUserVersion(m MetaContext, lc LoginContext, uv keybase1.UserVersion) (err error) { defer m.Trace("ActiveDevice#CopyCacheToLoginContextIfForUID", &err)() a.RLock() defer a.RUnlock() if !a.uv.Eq(uv) { return NewUIDMismatchError(fmt.Sprintf("%s v %s", a.uv, uv)) } if a.passphrase != nil { m.Debug("| copying non-nil passphrase cache") lc.SetStreamCache(a.passphrase) } return nil } func (a *ActiveDevice) GetUsernameAndUserVersionIfValid(m MetaContext) (uv keybase1.UserVersion, un NormalizedUsername) { a.RLock() defer a.RUnlock() if a.uv.IsNil() { return uv, un } un = m.G().Env.GetUsernameForUID(a.uv.Uid) if un.IsNil() { return keybase1.UserVersion{}, NormalizedUsername("") } return a.uv, un }