/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/engine/prove.go
472 строки
13 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 ( "context" "errors" "fmt" "strings" "time" "github.com/keybase/client/go/externals" libkb "github.com/keybase/client/go/libkb" keybase1 "github.com/keybase/client/go/protocol/keybase1" ) // Prove is an engine used for proving ownership of remote accounts, // like Twitter, GitHub, etc. type Prove struct { arg *keybase1.StartProofArg me *libkb.User serviceType libkb.ServiceType serviceParameters *keybase1.ProveParameters supersede bool proof *libkb.ProofMetadataRes sig string sigID keybase1.SigID linkID libkb.LinkID postRes *libkb.PostProofRes signingKey libkb.GenericKey sigInner []byte remoteNameNormalized string libkb.Contextified } // NewProve makes a new Prove Engine given an RPC-friendly ProveArg. func NewProve(g *libkb.GlobalContext, arg *keybase1.StartProofArg) *Prove { if arg.SigVersion == nil || libkb.SigVersion(*arg.SigVersion) == libkb.KeybaseNullSigVersion { tmp := keybase1.SigVersion(libkb.GetDefaultSigVersion(g)) arg.SigVersion = &tmp } return &Prove{ arg: arg, Contextified: libkb.NewContextified(g), } } // Name provides the name of this engine for the engine interface contract func (p *Prove) Name() string { return "Prove" } // GetPrereqs returns the engine prereqs. func (p *Prove) Prereqs() Prereqs { return Prereqs{Device: true} } // RequiredUIs returns the required UIs. func (p *Prove) RequiredUIs() []libkb.UIKind { return []libkb.UIKind{ libkb.LogUIKind, libkb.ProveUIKind, libkb.SecretUIKind, } } // SubConsumers returns the other UI consumers for this engine. func (p *Prove) SubConsumers() []libkb.UIConsumer { return nil } func (p *Prove) loadMe(m libkb.MetaContext) (err error) { p.me, err = libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(m).WithForceReload()) return err } func (p *Prove) checkExists1(m libkb.MetaContext) (err error) { proofs := p.me.IDTable().GetActiveProofsFor(p.serviceType) if len(proofs) != 0 && !p.arg.Force && p.serviceType.LastWriterWins() { lst := proofs[len(proofs)-1] var redo bool redo, err = m.UIs().ProveUI.PromptOverwrite(m.Ctx(), keybase1.PromptOverwriteArg{ Account: lst.ToDisplayString(), Typ: keybase1.PromptOverwriteType_SOCIAL, }) if err != nil { return err } if !redo { return libkb.NotConfirmedError{} } p.supersede = true } return nil } func (p *Prove) promptRemoteName(m libkb.MetaContext) error { // If the name is already supplied, there's no need to prompt. if len(p.arg.Username) > 0 { remoteNameNormalized, err := p.serviceType.NormalizeRemoteName(m, p.arg.Username) if err == nil { p.remoteNameNormalized = remoteNameNormalized } return err } // Prompt for the name, retrying if it's invalid. var normalizationError error for { un, err := m.UIs().ProveUI.PromptUsername(m.Ctx(), keybase1.PromptUsernameArg{ Prompt: p.serviceType.GetPrompt(), PrevError: libkb.ExportErrorAsStatus(m.G(), normalizationError), Parameters: p.serviceParameters, }) if err != nil { // Errors here are conditions like EOF. Return them rather than retrying. return err } var remoteNameNormalized string remoteNameNormalized, normalizationError = p.serviceType.NormalizeRemoteName(m, un) if normalizationError == nil { p.remoteNameNormalized = remoteNameNormalized return nil } } } func (p *Prove) checkExists2(m libkb.MetaContext) (err error) { defer m.Trace("Prove#CheckExists2", &err)() if !p.serviceType.LastWriterWins() { var found libkb.RemoteProofChainLink for _, proof := range p.me.IDTable().GetActiveProofsFor(p.serviceType) { _, name := proof.ToKeyValuePair() if libkb.Cicmp(name, p.remoteNameNormalized) { found = proof break } } if found != nil { var redo bool redo, err = m.UIs().ProveUI.PromptOverwrite(m.Ctx(), keybase1.PromptOverwriteArg{ Account: found.ToDisplayString(), Typ: keybase1.PromptOverwriteType_SITE, }) if err != nil { return err } if !redo { err = libkb.NotConfirmedError{} return err } p.supersede = true } } return nil } func (p *Prove) doPrechecks(m libkb.MetaContext) (err error) { var w *libkb.Markup w, err = p.serviceType.PreProofCheck(m, p.remoteNameNormalized) if w != nil { if uierr := m.UIs().ProveUI.OutputPrechecks(m.Ctx(), keybase1.OutputPrechecksArg{Text: w.Export()}); uierr != nil { m.Warning("prove ui OutputPrechecks call error: %s", uierr) } } return err } func (p *Prove) doWarnings(m libkb.MetaContext) (err error) { if mu := p.serviceType.PreProofWarning(p.remoteNameNormalized); mu != nil { var ok bool arg := keybase1.PreProofWarningArg{Text: mu.Export()} if ok, err = m.UIs().ProveUI.PreProofWarning(m.Ctx(), arg); err == nil && !ok { err = libkb.NotConfirmedError{} } if err != nil { return err } } return nil } func (p *Prove) generateProof(m libkb.MetaContext) (err error) { ska := libkb.SecretKeyArg{ Me: p.me, KeyType: libkb.DeviceSigningKeyType, } p.signingKey, err = m.G().Keyrings.GetSecretKeyWithPrompt(m, m.SecretKeyPromptArg(ska, "tracking signature")) if err != nil { return err } sigVersion := libkb.SigVersion(*p.arg.SigVersion) if p.proof, err = p.me.ServiceProof(m, p.signingKey, p.serviceType, p.remoteNameNormalized, sigVersion); err != nil { return err } if p.sigInner, err = p.proof.J.Marshal(); err != nil { return err } p.sig, p.sigID, p.linkID, err = libkb.MakeSig( m, p.signingKey, libkb.LinkTypeWebServiceBinding, p.sigInner, libkb.SigHasRevokes(false), keybase1.SeqType_PUBLIC, libkb.SigIgnoreIfUnsupported(false), p.me, sigVersion, ) return err } func (p *Prove) postProofToServer(m libkb.MetaContext) (err error) { arg := libkb.PostProofArg{ UID: p.me.GetUID(), Seqno: p.proof.Seqno, Sig: p.sig, ProofType: p.serviceType.GetProofType(), RemoteServiceType: p.serviceType.GetTypeName(), SigID: p.sigID, LinkID: p.linkID, Supersede: p.supersede, RemoteUsername: p.remoteNameNormalized, RemoteKey: p.serviceType.GetAPIArgKey(), SigningKey: p.signingKey, } if libkb.SigVersion(*p.arg.SigVersion) == libkb.KeybaseSignatureV2 { arg.SigInner = p.sigInner } p.postRes, err = libkb.PostProof(m, arg) return err } func (p *Prove) instructAction(m libkb.MetaContext) (err error) { mkp := p.serviceType.PostInstructions(p.remoteNameNormalized) var txt string if txt, err = p.serviceType.FormatProofText(m, p.postRes, p.me.GetNormalizedName().String(), p.remoteNameNormalized, p.sigID); err != nil { return err } err = m.UIs().ProveUI.OutputInstructions(m.Ctx(), keybase1.OutputInstructionsArg{ Instructions: mkp.Export(), // If we don't trim newlines here, we'll run into an issue where e.g. // Facebook links get corrupted on iOS. See: // - https://keybase.atlassian.net/browse/DESKTOP-3335 // - https://keybase.atlassian.net/browse/CORE-4941 // All of our proof verifying code (PVL) should already be flexible // with surrounding whitespace, because users are pasting proofs by // hand anyway. Proof: strings.TrimSpace(txt), Parameters: p.serviceParameters, }) if err != nil { return err } return p.checkAutoPost(m, txt) } func (p *Prove) checkAutoPost(m libkb.MetaContext, txt string) error { if !p.arg.Auto { return nil } if libkb.RemoteServiceTypes[p.arg.Service] != keybase1.ProofType_ROOTER { return nil } m.Debug("making automatic post of proof to rooter") apiArg := libkb.APIArg{ Endpoint: "rooter", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "post": libkb.S{Val: txt}, "username": libkb.S{Val: p.arg.Username}, }, } if _, err := m.G().API.Post(m, apiArg); err != nil { m.Debug("error posting to rooter: %s", err) return err } return nil } // Keep asking the user whether they posted the proof // until it works or they give up. func (p *Prove) promptPostedLoop(m libkb.MetaContext) (err error) { found := false for i := 0; ; i++ { var retry bool var status keybase1.ProofStatus var warn *libkb.Markup retry, err = m.UIs().ProveUI.OkToCheck(m.Ctx(), keybase1.OkToCheckArg{ Name: p.serviceType.DisplayName(), Attempt: i, }) if !retry || err != nil { break } found, status, _, err = libkb.CheckPosted(m, p.sigID) if found || err != nil { break } warn, err = p.serviceType.RecheckProofPosting(i, status, p.remoteNameNormalized) if warn != nil { uierr := m.UIs().ProveUI.DisplayRecheckWarning(m.Ctx(), keybase1.DisplayRecheckWarningArg{ Text: warn.Export(), }) if uierr != nil { m.Warning("prove ui DisplayRecheckWarning call error: %s", uierr) } } if err != nil { break } } if !found && err == nil { err = libkb.ProofNotYetAvailableError{} } return err } // Poll until the proof succeeds, limited to an hour. func (p *Prove) verifyLoop(m libkb.MetaContext) (err error) { timeout := time.Hour m, cancel := m.WithTimeout(timeout) defer cancel() defer func() { if err != nil && errors.Is(m.Ctx().Err(), context.DeadlineExceeded) { m.Debug("Prove.verifyLoop rewriting error after timeout: %v", err) err = fmt.Errorf("Timed out after looking for proof for %v", timeout) } }() uierr := m.UIs().ProveUI.Checking(m.Ctx(), keybase1.CheckingArg{ Name: p.serviceType.DisplayName(), }) if uierr != nil { m.Warning("prove ui Checking call error: %s", uierr) } for i := 0; ; i++ { if shouldContinue, uierr := m.UIs().ProveUI.ContinueChecking(m.Ctx(), 0); !shouldContinue || uierr != nil { if uierr != nil { m.Warning("prove ui ContinueChecking call error: %s", uierr) } return libkb.CanceledError{} } found, status, _, err := libkb.CheckPosted(m, p.sigID) if err != nil { return err } m.Debug("Prove.verifyLoop round:%v found:%v status:%v", i, found, status) if found { return nil } wakeAt := m.G().Clock().Now().Add(2 * time.Second) err = libkb.SleepUntilWithContext(m.Ctx(), m.G().Clock(), wakeAt) if err != nil { return err } } } func (p *Prove) checkProofText(m libkb.MetaContext) error { m.Debug("p.postRes.Text: %q", p.postRes.Text) m.Debug("p.sigID: %q", p.sigID) return p.serviceType.CheckProofText(p.postRes.Text, p.sigID, p.sig) } func (p *Prove) getServiceType(m libkb.MetaContext) (err error) { p.serviceType = m.G().GetProofServices().GetServiceType(m.Ctx(), p.arg.Service) if p.serviceType == nil { return libkb.BadServiceError{Service: p.arg.Service} } if !p.serviceType.CanMakeNewProofs(m) { return libkb.ServiceDoesNotSupportNewProofsError{Service: p.arg.Service} } if serviceType, ok := p.serviceType.(*externals.GenericSocialProofServiceType); ok { tmp := serviceType.ProveParameters(m) p.serviceParameters = &tmp } return nil } // SigID returns the signature id of the proof posted to the // server. func (p *Prove) SigID() keybase1.SigID { return p.sigID } // Run runs the Prove engine, performing all steps of the proof process. func (p *Prove) Run(m libkb.MetaContext) (err error) { defer m.Trace("ProofEngine.Run", &err)() stage := func(s string) { m.Debug("| ProofEngine.Run() %s", s) } stage("GetServiceType") if err = p.getServiceType(m); err != nil { return err } stage("LoadMe") if err = p.loadMe(m); err != nil { return err } stage("CheckExists1") if err = p.checkExists1(m); err != nil { return err } stage("PromptRemoteName") if err = p.promptRemoteName(m); err != nil { return err } stage("CheckExists2") if err = p.checkExists2(m); err != nil { return err } stage("DoPrechecks") if err = p.doPrechecks(m); err != nil { return err } stage("DoWarnings") if err = p.doWarnings(m); err != nil { return err } m.G().LocalSigchainGuard().Set(m.Ctx(), "Prove") defer m.G().LocalSigchainGuard().Clear(m.Ctx(), "Prove") stage("GenerateProof") if err = p.generateProof(m); err != nil { return err } stage("PostProofToServer") if err = p.postProofToServer(m); err != nil { return err } m.G().LocalSigchainGuard().Clear(m.Ctx(), "Prove") stage("CheckProofText") if err = p.checkProofText(m); err != nil { return err } stage("InstructAction") if err = p.instructAction(m); err != nil { return err } if !p.arg.PromptPosted { m.Debug("PromptPosted not set, prove run finished") return nil } stage("CheckStart") if p.serviceParameters == nil { stage("PromptPostedLoop") if err = p.promptPostedLoop(m); err != nil { return err } } else { stage("VerifyLoop") if err = p.verifyLoop(m); err != nil { return err } } m.UIs().LogUI.Notice("Success!") return nil }