/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/engine/passphrase_change.go
440 строк
12 KB
zoom-ua
modernize error handling with errors.As/Is (#29350)
25 июн 2026, 17:41
Не верифицирован
25 июн 2026, 17:41
e09e254
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. package engine import ( "errors" "fmt" "github.com/keybase/client/go/libkb" keybase1 "github.com/keybase/client/go/protocol/keybase1" ) // PassphraseChange engine is used for changing the user's passphrase, either // by replacement or by force. type PassphraseChange struct { arg *keybase1.PassphraseChangeArg me *libkb.User usingPaper bool libkb.Contextified } // NewPassphraseChange creates a new engine for changing user passphrases, // either if the current passphrase is known, or in "force" mode func NewPassphraseChange(g *libkb.GlobalContext, a *keybase1.PassphraseChangeArg) *PassphraseChange { return &PassphraseChange{ arg: a, Contextified: libkb.NewContextified(g), } } // Name provides the name of the engine for the engine interface func (c *PassphraseChange) Name() string { return "PassphraseChange" } // Prereqs returns engine prereqs func (c *PassphraseChange) Prereqs() Prereqs { if c.arg.Force { return Prereqs{} } return Prereqs{Device: true} } // RequiredUIs returns the required UIs. func (c *PassphraseChange) RequiredUIs() []libkb.UIKind { return []libkb.UIKind{ libkb.SecretUIKind, } } // SubConsumers requires the other UI consumers of this engine func (c *PassphraseChange) SubConsumers() []libkb.UIConsumer { return []libkb.UIConsumer{ &PaperKeyGen{}, } } // Run the engine func (c *PassphraseChange) Run(m libkb.MetaContext) (err error) { m = m.WithLogTag("PPCHNG") defer m.Trace("PassphraseChange#Run", &err)() defer func() { m.G().SKBKeyringMu.Unlock() }() m.G().SKBKeyringMu.Lock() m.Debug("| Acquired SKBKeyringMu mutex") if len(c.arg.Passphrase) < libkb.MinPassphraseLength { return libkb.PassphraseError{Msg: "too short"} } if err = c.loadMe(); err != nil { return } if _, w := m.ActiveDevice().SyncSecrets(m); w != nil { m.Debug("| failed to run secret syncer: %s", w) } m = m.WithNewProvisionalLoginContextForUser(c.me) if c.arg.Force { err = c.runForcedUpdate(m) } else { err = c.runStandardUpdate(m) } if err != nil { return err } // If the passphrase changes, it's known. Do this in case we aren't getting // gregors for some reason (standalone or test) - it will at least update // this device. libkb.MaybeSavePassphraseState(m, keybase1.PassphraseState_KNOWN) // We used to sync secrets here, but sync secrets in runForceUpdate // or runStandardUpdate, since the temporary login information won't // persist past the scope of these functions. return nil } // findPaperKeys checks if the user has paper keys. If he/she // does, it prompts for a paper key phrase. This is used to // regenerate paper keys, which are then matched against the // paper keys found in the keyfamily. func (c *PassphraseChange) findPaperKeys(m libkb.MetaContext) (*libkb.DeviceWithKeys, error) { kp, err := findPaperKeys(m, c.me) if err != nil { m.Debug("findPaperKeys error: %s", err) return nil, err } m.Debug("findPaperKeys success") c.usingPaper = true return kp, nil } // findUpdateKeys looks for keys to perform the passphrase update. // The first choice is device keys. If that fails, it will look // for backup keys. If backup keys are necessary, then it will // also log the user in with the backup keys. func (c *PassphraseChange) findUpdateDevice(m libkb.MetaContext) (ad *libkb.ActiveDevice, err error) { defer m.Trace("PassphraseChange#findUpdateDevice", &err)() if ad = m.G().ActiveDevice; ad.Valid() { m.Debug("| returning globally active device key") return ad, nil } kp, err := c.findPaperKeys(m) if err != nil { m.Debug("| error fetching paper keys") return nil, err } ad = libkb.NewActiveDeviceWithDeviceWithKeys(m, m.CurrentUserVersion(), kp) m.Debug("| installing paper key as thread-local active device") return ad, nil } func (c *PassphraseChange) forceUpdatePassphrase(m libkb.MetaContext, sigKey libkb.GenericKey, ppGen libkb.PassphraseGeneration, oldClientHalf libkb.LKSecClientHalf) (err error) { defer m.Trace("PassphraseChange#forceUpdatePassphrase", &err)() // Don't update server-synced pgp keys when recovering. // This will render any server-synced pgp keys unrecoverable from the server. // TODO would it responsible to ask the server to delete them? pgpKeys, nPgpKeysLost, err := c.findAndDecryptPrivatePGPKeysLossy(m) if err != nil { return err } if nPgpKeysLost > 0 { m.Debug("PassphraseChange.runForcedUpdate: Losing %v synced keys", nPgpKeysLost) } // Ready the update argument; almost done, but we need some more stuff. payload, err := c.commonArgs(m, oldClientHalf, pgpKeys, ppGen) if err != nil { return err } // get the new passphrase hash out of the args pwh, ok := payload["pwh"].(string) if !ok || len(pwh) == 0 { return errors.New("no pwh found in common args") } // get the new PDPKA5 KID out of the args pdpka5kid, ok := payload["pdpka5_kid"].(string) if !ok || len(pdpka5kid) == 0 { return errors.New("no pdpka5kid found in common args") } // Generate a signature with our unlocked sibling key from device. proof, err := c.me.UpdatePassphraseProof(m, sigKey, pwh, ppGen+1, pdpka5kid) if err != nil { return err } sig, _, _, err := libkb.SignJSON(proof, sigKey) if err != nil { return err } payload["sig"] = sig payload["signing_kid"] = sigKey.GetKID() postArg := libkb.APIArg{ Endpoint: "passphrase/sign", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } // Important to pass a MetaContext here to pick up the provisional login context // or an ActiveDevice that is thread-local. _, err = m.G().API.PostJSON(m, postArg) if err != nil { return fmt.Errorf("api post to passphrase/sign error: %s", err) } return nil } // 1. Get keys for decryption and signing // 2. If necessary, log in with backup keys // 3. Get lks client half from server // 4. Post an update passphrase proof func (c *PassphraseChange) runForcedUpdate(m libkb.MetaContext) (err error) { defer m.Trace("PassphraseChange#runForcedUpdate", &err)() ad, err := c.findUpdateDevice(m) if err != nil { return } if ad == nil { return libkb.NoSecretKeyError{} } enc, err := ad.EncryptionKey() if err != nil { return err } sig, err := ad.SigningKey() if err != nil { return err } m = m.WithActiveDevice(ad) ppGen, oldClientHalf, err := fetchLKS(m, enc) if err != nil { return } err = c.forceUpdatePassphrase(m, sig, ppGen, oldClientHalf) if err != nil { return err } _, err = m.SyncSecrets() if err != nil { return err } m = m.WithGlobalActiveDevice() // Reset passphrase stream cache so that subsequent updates go through // without a problem (see CORE-3933) m.ActiveDevice().ClearCaches() return nil } // runStandardUpdate is for when the user knows the current password. func (c *PassphraseChange) runStandardUpdate(m libkb.MetaContext) (err error) { defer m.Trace("PassphraseChange.runStandardUpdate", &err)() var ppStream *libkb.PassphraseStream if len(c.arg.OldPassphrase) == 0 { ppStream, err = libkb.GetPassphraseStreamViaPromptInLoginContext(m) } else { ppStream, err = libkb.VerifyPassphraseGetStreamInLoginContext(m, c.arg.OldPassphrase) } if err != nil { return err } pgpKeys, err := c.findAndDecryptPrivatePGPKeys(m) if err != nil { return err } gen := m.PassphraseStream().Generation() oldClientHalf := m.PassphraseStream().LksClientHalf() payload, err := c.commonArgs(m, oldClientHalf, pgpKeys, gen) if err != nil { return err } lp, err := libkb.ComputeLoginPackage2(m, ppStream) if err != nil { return err } payload["ppgen"] = gen payload["old_pdpka4"] = lp.PDPKA4() payload["old_pdpka5"] = lp.PDPKA5() postArg := libkb.APIArg{ Endpoint: "passphrase/replace", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } _, err = c.G().API.PostJSON(m, postArg) if err != nil { return err } _, err = m.SyncSecrets() if err != nil { return err } // Reset the passphrase stream cache on the global Active Device, since if it exists, // it was for a previous version of the passphrase. m = m.WithGlobalActiveDevice() m.ActiveDevice().ClearCaches() return nil } func (c *PassphraseChange) commonArgs(_ libkb.MetaContext, oldClientHalf libkb.LKSecClientHalf, pgpKeys []libkb.GenericKey, existingGen libkb.PassphraseGeneration) (libkb.JSONPayload, error) { salt, err := c.me.GetSalt() if err != nil { return nil, err } tsec, newPPStream, err := libkb.StretchPassphrase(c.G(), c.arg.Passphrase, salt) if err != nil { return nil, err } newPWH := newPPStream.PWHash() newClientHalf := newPPStream.LksClientHalf() pdpka5kid, err := newPPStream.PDPKA5KID() if err != nil { return nil, err } mask := oldClientHalf.ComputeMask(newClientHalf) lksch := make(map[keybase1.KID]string) devices := c.me.GetComputedKeyFamily().GetAllDevices() for _, dev := range devices { if !dev.IsActive() { continue } key, err := c.me.GetComputedKeyFamily().GetEncryptionSubkeyForDevice(dev.ID) if err != nil { return nil, err } ctext, err := key.EncryptToString(newClientHalf.Bytes(), nil) if err != nil { return nil, err } lksch[key.GetKID()] = ctext } payload := make(libkb.JSONPayload) payload["pwh"] = libkb.HexArg(newPWH).String() payload["pwh_version"] = libkb.ClientTriplesecVersion payload["lks_mask"] = mask.EncodeToHex() payload["lks_client_halves"] = lksch payload["pdpka5_kid"] = pdpka5kid.String() var encodedKeys []string for _, key := range pgpKeys { encoded, err := c.encodePrivatePGPKey(key, tsec, existingGen+1) if err != nil { return nil, err } encodedKeys = append(encodedKeys, encoded) } payload["private_keys"] = encodedKeys return payload, nil } func (c *PassphraseChange) loadMe() (err error) { c.me, err = libkb.LoadMe(libkb.NewLoadUserForceArg(c.G())) return } // findAndDecryptPrivatePGPKeys gets the user's private pgp keys if any exist and decrypts them. func (c *PassphraseChange) findAndDecryptPrivatePGPKeys(m libkb.MetaContext) ([]libkb.GenericKey, error) { var keyList []libkb.GenericKey // Using a paper key makes TripleSec-synced keys unrecoverable if c.usingPaper { m.Debug("using a paper key, thus TripleSec-synced keys are unrecoverable") return keyList, nil } // Only use the synced secret keys: blocks, err := c.me.AllSyncedSecretKeys(m) if err != nil { return nil, err } secretRetriever := libkb.NewSecretStore(m, c.me.GetNormalizedName()) for _, block := range blocks { parg := m.SecretKeyPromptArg(libkb.SecretKeyArg{}, "passphrase change") key, err := block.PromptAndUnlock(m, parg, secretRetriever, c.me) if err != nil { return nil, err } keyList = append(keyList, key) } return keyList, nil } // findAndDecryptPrivatePGPKeysLossy gets the user's private pgp keys if any exist and attempts // to decrypt them without prompting the user. If any fail to decrypt, they are silently not returned. // The second return value is the number of keys which were not decrypted. func (c *PassphraseChange) findAndDecryptPrivatePGPKeysLossy(m libkb.MetaContext) ([]libkb.GenericKey, int, error) { var keyList []libkb.GenericKey nLost := 0 // Only use the synced secret keys: blocks, err := c.me.AllSyncedSecretKeys(m) if err != nil { return nil, 0, err } secretRetriever := libkb.NewSecretStore(m, c.me.GetNormalizedName()) for _, block := range blocks { key, err := block.UnlockNoPrompt(m, secretRetriever) if err == nil { keyList = append(keyList, key) } else { if !errors.Is(err, libkb.ErrUnlockNotPossible) { return nil, 0, err } nLost++ m.Debug("findAndDecryptPrivatePGPKeysLossy: ignoring failure to decrypt key without prompt") } } return keyList, nLost, nil } // encodePrivatePGPKey encrypts key with tsec and armor-encodes it. // It includes the passphrase generation in the data. func (c *PassphraseChange) encodePrivatePGPKey(key libkb.GenericKey, tsec libkb.Triplesec, gen libkb.PassphraseGeneration) (string, error) { skb, err := libkb.ToServerSKB(c.G(), key, tsec, gen) if err != nil { return "", err } return skb.ArmoredEncode() }