/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/libkb/sync_secret.go
364 строки
10 KB
chrisnojima
backport retry fixes from https://github.com/keybase/client/pull/29246 (#29251)
27 май 2026, 04:53
Не верифицирован
27 май 2026, 04:53
ecfd22f
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. // A module for syncing secrets with the server, such as SKB PGP keys, // and server-halves of our various secret keys. package libkb import ( "fmt" "strings" "sync" keybase1 "github.com/keybase/client/go/protocol/keybase1" ) type ServerPrivateKey struct { Kid string `json:"kid"` KeyType KeyType `json:"key_type"` Bundle string `json:"bundle"` Mtime int `json:"mtime"` Ctime int `json:"ctime"` KeyBits int `json:"key_bits"` KeyAlgo int `json:"key_algo"` } type ServerPrivateKeyMap map[string]ServerPrivateKey type DeviceKey struct { Type keybase1.DeviceTypeV2 `json:"type"` CTime int64 `json:"ctime"` MTime int64 `json:"mtime"` Description string `json:"name"` Status int `json:"status"` LksServerHalf string `json:"lks_server_half"` PPGen PassphraseGeneration `json:"passphrase_generation"` LastUsedTime int64 `json:"last_used_time"` } func (d DeviceKey) Display() string { if d.Type == keybase1.DeviceTypeV2_PAPER { // XXX not sure if we need to support our existing paper keys, but without this // someone is surely going to complain: if strings.HasPrefix(d.Description, "Paper Key") { return d.Description } return fmt.Sprintf("Paper Key (%s...)", d.Description) } return d.Description } type DeviceKeyMap map[keybase1.DeviceID]DeviceKey type ServerPrivateKeys struct { Status APIStatus `json:"status"` Version int `json:"version"` Mtime *int `json:"mtime"` PrivateKeys ServerPrivateKeyMap `json:"private_keys"` // note these are only PGP keys Devices DeviceKeyMap `json:"devices"` } type SecretSyncer struct { sync.Mutex Contextified dirty bool keys *ServerPrivateKeys } type DeviceTypeSet map[keybase1.DeviceTypeV2]bool var DefaultDeviceTypes = DeviceTypeSet{ keybase1.DeviceTypeV2_DESKTOP: true, keybase1.DeviceTypeV2_MOBILE: true, } var AllDeviceTypes = DeviceTypeSet{ keybase1.DeviceTypeV2_DESKTOP: true, keybase1.DeviceTypeV2_MOBILE: true, keybase1.DeviceTypeV2_PAPER: true, } func NewSecretSyncer(g *GlobalContext) *SecretSyncer { return &SecretSyncer{ Contextified: NewContextified(g), } } func (ss *SecretSyncer) Clear() error { ss.keys = nil return nil } func (ss *SecretSyncer) loadFromStorage(m MetaContext, uid keybase1.UID, useExpiration bool) (err error) { var tmp ServerPrivateKeys var found bool found, err = ss.G().LocalDb.GetInto(&tmp, ss.dbKey(uid)) m.Debug("| loadFromStorage -> found=%v, err=%s", found, ErrToOk(err)) if err != nil { return err } if !found { m.Debug("| Loaded empty record set") return nil } if ss.cachedSyncedSecretsOutOfDate(&tmp) { m.Debug("| Synced secrets out of date") return nil } // only set ss.keys to something if found. // // This is part of keybase-issues#1783: an (old) user with a synced // private key fell back to gpg instead of using a synced key. // m.Debug("| Loaded version %d", tmp.Version) ss.keys = &tmp return nil } func (ss *SecretSyncer) syncFromServer(m MetaContext, uid keybase1.UID, forceReload bool) (err error) { hargs := HTTPArgs{} if ss.keys != nil && !forceReload { m.Debug("| adding version %d to fetch_private call", ss.keys.Version) hargs.Add("version", I{ss.keys.Version}) } var res *APIRes apiArg := NewRetryAPIArg("key/fetch_private") apiArg.Args = hargs apiArg.SessionType = APISessionTypeREQUIRED res, err = ss.G().API.Get(m, apiArg) m.Debug("| syncFromServer -> %s", ErrToOk(err)) if err != nil { return } var obj ServerPrivateKeys if err = res.Body.UnmarshalAgain(&obj); err != nil { return } m.Debug("| Returned object: {Status: %v, Version: %d, #pgpkeys: %d, #devices: %d}", obj.Status, obj.Version, len(obj.PrivateKeys), len(obj.Devices)) if forceReload || ss.keys == nil || obj.Version > ss.keys.Version { m.Debug("| upgrade to version -> %d", obj.Version) ss.keys = &obj ss.dirty = true } else { m.Debug("| not changing synced keys: synced version %d not newer than existing version %d", obj.Version, ss.keys.Version) } return } func (ss *SecretSyncer) dbKey(uid keybase1.UID) DbKey { return DbKeyUID(DBUserSecretKeys, uid) } func (ss *SecretSyncer) store(m MetaContext, uid keybase1.UID) (err error) { if !ss.dirty { return } if err = m.G().LocalDb.PutObj(ss.dbKey(uid), nil, ss.keys); err != nil { return } ss.dirty = false return } // FindActiveKey examines the synced keys, looking for one that's currently // active. The key will be chosen at random due to non-deterministic order of // FindActiveKeys output. // Returns ret=nil if none was found. func (ss *SecretSyncer) FindActiveKey(ckf *ComputedKeyFamily) (ret *SKB, err error) { keys, err := ss.FindActiveKeys(ckf) if err != nil { return nil, err } if len(keys) == 0 { return nil, nil } ss.G().Log.Debug("NOTE: calling SecretSyncer.FindActiveKey: returning first secret key from randomly ordered map", err) return keys[0], nil } // FindActiveKey examines the synced keys, and returns keys that are currently // active. func (ss *SecretSyncer) FindActiveKeys(ckf *ComputedKeyFamily) (ret []*SKB, err error) { ss.Lock() defer ss.Unlock() if ss.keys == nil { return ret, nil } for _, key := range ss.keys.PrivateKeys { keyRet, err := key.FindActiveKey(ss.G(), ckf) if err != nil { ss.G().Log.Debug("SecretSyncer.FindActiveKeys: error from key.FindActiveKey, skipping key: %s", err) } else { ret = append(ret, keyRet) } } return ret, nil } // AllActiveKeys returns all the active synced PGP keys. func (ss *SecretSyncer) AllActiveKeys(ckf *ComputedKeyFamily) []*SKB { ss.Lock() defer ss.Unlock() var res []*SKB for _, key := range ss.keys.PrivateKeys { if ret, _ := key.FindActiveKey(ss.G(), ckf); ret != nil { res = append(res, ret) } } return res } func (ss *SecretSyncer) FindPrivateKey(kid string) (ServerPrivateKey, bool) { ss.Lock() defer ss.Unlock() k, ok := ss.keys.PrivateKeys[kid] return k, ok } func (k *ServerPrivateKey) FindActiveKey(g *GlobalContext, ckf *ComputedKeyFamily) (ret *SKB, err error) { kid := keybase1.KIDFromString(k.Kid) if ckf.GetKeyRole(kid) != DLGSibkey { return } if ret, err = DecodeArmoredSKBPacket(k.Bundle); err != nil { return } ret.SetGlobalContext(g) return ret, nil } func (ss *SecretSyncer) FindDevice(id keybase1.DeviceID) (DeviceKey, error) { ss.Lock() defer ss.Unlock() if ss.keys == nil { return DeviceKey{}, DeviceNotFoundError{"SecretSyncer", id, false} } dev, ok := ss.keys.Devices[id] if !ok { return DeviceKey{}, DeviceNotFoundError{"SecretSyncer", id, true} } return dev, nil } func (ss *SecretSyncer) AllDevices() DeviceKeyMap { ss.Lock() defer ss.Unlock() if ss.keys == nil { return nil } return ss.keys.Devices } func (ss *SecretSyncer) HasDevices() bool { if ss.keys == nil { return false } return len(ss.keys.Devices) > 0 } func (ss *SecretSyncer) Devices() (DeviceKeyMap, error) { ss.Lock() defer ss.Unlock() if ss.keys == nil { return nil, fmt.Errorf("no keys") } return ss.keys.Devices, nil } // IsDeviceNameTaken returns true if a desktop or mobile device is // using a name already. func (ss *SecretSyncer) IsDeviceNameTaken(name string, includeTypesSet DeviceTypeSet) bool { devs, err := ss.ActiveDevices(includeTypesSet) if err != nil { return false } for _, v := range devs { if NameCmp(v.Description, name) { return true } } return false } // HasActiveDevice returns true if there is an active desktop or // mobile device available. func (ss *SecretSyncer) HasActiveDevice(includeTypesSet DeviceTypeSet) (bool, error) { devs, err := ss.ActiveDevices(includeTypesSet) if err != nil { return false, err } return len(devs) > 0, nil } // ActiveDevices returns all the active desktop and mobile devices. func (ss *SecretSyncer) ActiveDevices(includeTypesSet DeviceTypeSet) (DeviceKeyMap, error) { ss.Lock() defer ss.Unlock() if ss.keys == nil { return nil, fmt.Errorf("no keys") } if includeTypesSet == nil { return nil, fmt.Errorf("need valid includeTypesSet") } res := make(DeviceKeyMap) for k, v := range ss.keys.Devices { if v.Status != DeviceStatusActive { continue } if includeTypesSet[v.Type] { res[k] = v } } return res, nil } func (ss *SecretSyncer) DumpPrivateKeys() { ss.Lock() defer ss.Unlock() for s, key := range ss.keys.PrivateKeys { ss.G().Log.Info("Private key: %s", s) ss.G().Log.Info(" -- kid: %s, keytype: %d, bits: %d, algo: %d", key.Kid, key.KeyType, key.KeyBits, key.KeyAlgo) } } // As we add more fields to the data we're caching here, we need to detect the // cases where our cached data is missing the new fields. We can extend this // function with more cases as we add more fields. func (ss *SecretSyncer) cachedSyncedSecretsOutOfDate(cached *ServerPrivateKeys) bool { for _, dev := range cached.Devices { if dev.LastUsedTime == 0 { ss.G().Log.Debug("cachedSyncedSecretsOutOfDate noticed a cached device with no last used time") return true } } return false } func (k ServerPrivateKey) ToSKB(gc *GlobalContext) (*SKB, error) { if k.KeyType != KeyTypeP3skbPrivate { return nil, fmt.Errorf("invalid key type for skb conversion: %d", k.KeyType) } skb, err := DecodeArmoredSKBPacket(k.Bundle) if err != nil { return nil, err } return skb, nil } func (ss *SecretSyncer) needsLogin(m MetaContext) bool { return true } func (d DeviceKey) ToLKSec() (LKSecServerHalf, error) { return NewLKSecServerHalfFromHex(d.LksServerHalf) }