github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/scanproofs.go (about)

     1  // Copyright 2016 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package engine
     5  
     6  import (
     7  	"bufio"
     8  	"encoding/csv"
     9  	"encoding/gob"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/keybase/client/go/libkb"
    18  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    19  )
    20  
    21  type ScanProofsCacheData struct {
    22  	// Map from sigid to whether the proof is ok.
    23  	Proofs map[string]bool
    24  }
    25  
    26  type ScanProofsCache struct {
    27  	data  ScanProofsCacheData
    28  	dirty bool
    29  }
    30  
    31  func NewScanProofsCache() *ScanProofsCache {
    32  	return &ScanProofsCache{
    33  		data: ScanProofsCacheData{
    34  			Proofs: make(map[string]bool),
    35  		},
    36  		dirty: false,
    37  	}
    38  }
    39  
    40  func LoadScanProofsCache(filepath string) (*ScanProofsCache, error) {
    41  	f, err := os.Open(filepath)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	dec := gob.NewDecoder(f)
    46  	var c ScanProofsCache
    47  	err = dec.Decode(&c.data)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	return &c, nil
    52  }
    53  
    54  func (c *ScanProofsCache) Get(sigID string) bool {
    55  	return c.data.Proofs[sigID]
    56  }
    57  
    58  func (c *ScanProofsCache) Set(sigID string) {
    59  	if !c.data.Proofs[sigID] {
    60  		c.dirty = true
    61  	}
    62  	c.data.Proofs[sigID] = true
    63  }
    64  
    65  func (c *ScanProofsCache) Save(filepath string) error {
    66  	if !c.dirty {
    67  		// Don't save if nothing has changed
    68  		return nil
    69  	}
    70  	temppath, f, err := libkb.OpenTempFile(filepath, "", 0644)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	enc := gob.NewEncoder(f)
    75  	err = enc.Encode(c.data)
    76  	if err != nil {
    77  		f.Close()
    78  		return err
    79  	}
    80  	f.Close()
    81  	err = os.Rename(temppath, filepath)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	c.dirty = false
    86  	return nil
    87  }
    88  
    89  type ScanProofsTickers map[keybase1.ProofType]*time.Ticker
    90  
    91  type ScanProofsEngine struct {
    92  	libkb.Contextified
    93  	infile     string
    94  	indices    string
    95  	sigid      string
    96  	ratelimit  int
    97  	cachefile  string
    98  	ignorefile string
    99  }
   100  
   101  var _ Engine2 = (*ScanProofsEngine)(nil)
   102  
   103  func NewScanProofsEngine(infile string, indices string, sigid string, ratelimit int, cachefile string, ignorefile string, g *libkb.GlobalContext) *ScanProofsEngine {
   104  	return &ScanProofsEngine{
   105  		infile:       infile,
   106  		indices:      indices,
   107  		sigid:        sigid,
   108  		ratelimit:    ratelimit,
   109  		cachefile:    cachefile,
   110  		ignorefile:   ignorefile,
   111  		Contextified: libkb.NewContextified(g),
   112  	}
   113  }
   114  
   115  func (e *ScanProofsEngine) Name() string {
   116  	return "ScanProofs"
   117  }
   118  
   119  func (e *ScanProofsEngine) Prereqs() Prereqs {
   120  	return Prereqs{}
   121  }
   122  
   123  func (e *ScanProofsEngine) RequiredUIs() []libkb.UIKind {
   124  	return []libkb.UIKind{
   125  		libkb.LogUIKind,
   126  	}
   127  }
   128  
   129  func (e *ScanProofsEngine) SubConsumers() []libkb.UIConsumer {
   130  	return []libkb.UIConsumer{}
   131  }
   132  
   133  func (e *ScanProofsEngine) Run(m libkb.MetaContext) (err error) {
   134  	defer m.Trace("ScanProofsEngine#Run", &err)()
   135  
   136  	var cache *ScanProofsCache
   137  	saveevery := 10
   138  	var ignored []string
   139  
   140  	if len(e.cachefile) > 0 {
   141  		lcache, err := LoadScanProofsCache(e.cachefile)
   142  		if err == nil {
   143  			m.Info("Using cache: %v (%v entries)", e.cachefile, len(lcache.data.Proofs))
   144  			cache = lcache
   145  		} else {
   146  			m.Warning("Could not load cache: %v", err)
   147  			cache = NewScanProofsCache()
   148  		}
   149  	}
   150  
   151  	if len(e.ignorefile) > 0 {
   152  		ignored, err = LoadScanProofsIgnore(e.ignorefile)
   153  		if err != nil {
   154  			return fmt.Errorf("Could not open ignore file: %v", err)
   155  		}
   156  		m.Info("Using ignore file: %v (%v entries)", e.ignorefile, len(ignored))
   157  	}
   158  
   159  	if len(e.sigid) > 0 && len(e.indices) > 0 {
   160  		return fmt.Errorf("Only one of sigid and indices allowed")
   161  	}
   162  
   163  	// One ticker for each proof type.
   164  	var tickers = make(map[keybase1.ProofType]*time.Ticker)
   165  	m.Info("Running with ratelimit: %v ms", e.ratelimit)
   166  	if e.ratelimit < 0 {
   167  		return fmt.Errorf("Ratelimit value can not be negative: %v", e.ratelimit)
   168  	}
   169  	if e.ratelimit > 0 {
   170  		for _, ptype := range keybase1.ProofTypeMap {
   171  			switch ptype {
   172  			case keybase1.ProofType_GENERIC_WEB_SITE, keybase1.ProofType_DNS:
   173  				// Web sites and DNS do not need a rate limit.
   174  			default:
   175  				tickers[ptype] = time.NewTicker(time.Millisecond * time.Duration(e.ratelimit))
   176  			}
   177  		}
   178  	}
   179  	defer func(tickers *map[keybase1.ProofType]*time.Ticker) {
   180  		for _, ticker := range *tickers {
   181  			if ticker != nil {
   182  				ticker.Stop()
   183  			}
   184  		}
   185  	}(&tickers)
   186  
   187  	f, err := os.Open(e.infile)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	r := csv.NewReader(f)
   192  
   193  	var records []map[string]string
   194  
   195  	header, err := r.Read()
   196  	if err != nil {
   197  		return fmt.Errorf("Could not read header: %v", err)
   198  	}
   199  
   200  	m.Debug("Reading csv... ")
   201  	for {
   202  		rec, err := r.Read()
   203  		if err == io.EOF {
   204  			break
   205  		}
   206  		if err != nil {
   207  			return err
   208  		}
   209  		record := make(map[string]string)
   210  		for i, val := range rec {
   211  			record[header[i]] = val
   212  		}
   213  		records = append(records, record)
   214  	}
   215  	m.Debug("done")
   216  
   217  	startindex := 0
   218  	endindex := len(records)
   219  	if len(e.indices) > 0 {
   220  		startindex, endindex, err = e.ParseIndices(e.indices)
   221  		if err != nil {
   222  			return err
   223  		}
   224  	}
   225  	if startindex < 0 {
   226  		return fmt.Errorf("Invalid start index: %v", startindex)
   227  	}
   228  	if endindex > len(records) {
   229  		return fmt.Errorf("Invalid end index: %v (%v records)", endindex, len(records))
   230  	}
   231  
   232  	nrun := 0
   233  	nok := 0
   234  
   235  	for i := startindex; i < endindex; i++ {
   236  		rec := records[i]
   237  
   238  		if len(e.sigid) > 0 && e.sigid != rec["sig_id"] {
   239  			continue
   240  		}
   241  
   242  		m.Info("i:%v user:%v type:%v sigid:%v", i, rec["username"], rec["proof_type"], rec["sig_id"])
   243  
   244  		err := e.ProcessOne(m, i, rec, cache, ignored, tickers)
   245  		nrun++
   246  		if err == nil {
   247  			m.Info("Ok\n")
   248  			nok++
   249  			if cache != nil {
   250  				cache.Set(rec["sig_id"])
   251  				if i%saveevery == 0 {
   252  					saveerr := cache.Save(e.cachefile)
   253  					if saveerr != nil {
   254  						m.Warning("Could not save cache: %v", saveerr)
   255  					}
   256  				}
   257  			}
   258  		} else {
   259  			m.Error("%v FAILED: %v\n", i, err)
   260  		}
   261  	}
   262  
   263  	m.Info("---")
   264  	m.Info("proofs checked  : %v", nrun)
   265  	m.Info("oks             : %v", nok)
   266  	m.Info("fails           : %v", nrun-nok)
   267  
   268  	if cache != nil {
   269  		saveerr := cache.Save(e.cachefile)
   270  		if saveerr != nil {
   271  			m.Warning("Could not save cache: %v", saveerr)
   272  		}
   273  	}
   274  
   275  	return nil
   276  }
   277  
   278  func (e *ScanProofsEngine) ProcessOne(m libkb.MetaContext, i int, rec map[string]string, cache *ScanProofsCache, ignored []string, tickers ScanProofsTickers) error {
   279  	serverstate, err := strconv.Atoi(rec["state"])
   280  	if err != nil {
   281  		return fmt.Errorf("Could not read server state: %v", err)
   282  	}
   283  
   284  	shouldsucceed := true
   285  	skip := false
   286  	var skipreason string
   287  	badstate := false
   288  
   289  	switch keybase1.ProofState(serverstate) {
   290  	case keybase1.ProofState_NONE:
   291  		badstate = true
   292  	case keybase1.ProofState_OK:
   293  	case keybase1.ProofState_TEMP_FAILURE:
   294  		shouldsucceed = false
   295  	case keybase1.ProofState_PERM_FAILURE:
   296  		shouldsucceed = false
   297  	case keybase1.ProofState_LOOKING:
   298  		skip = true
   299  		skipreason = "server LOOKING"
   300  	case keybase1.ProofState_SUPERSEDED:
   301  		skip = true
   302  		skipreason = "server SUPERSEDED"
   303  	case keybase1.ProofState_POSTED:
   304  		badstate = true
   305  	case keybase1.ProofState_REVOKED:
   306  		skip = true
   307  		skipreason = "server REVOKED"
   308  	case keybase1.ProofState_DELETED:
   309  		skip = true
   310  		skipreason = "server DELETED"
   311  	default:
   312  		badstate = true
   313  	}
   314  
   315  	if cache != nil && cache.Get(rec["sig_id"]) {
   316  		skip = true
   317  		skipreason = "cached success"
   318  	}
   319  
   320  	for _, x := range ignored {
   321  		if x == rec["sig_id"] {
   322  			skip = true
   323  			skipreason = "in ignored list"
   324  		}
   325  	}
   326  
   327  	if badstate {
   328  		return fmt.Errorf("Unsupported serverstate: %v", serverstate)
   329  	}
   330  
   331  	if skip {
   332  		m.Info("skipping: %v", skipreason)
   333  		return nil
   334  	}
   335  
   336  	deluserstr := "Error loading user: Deleted"
   337  	perr1, foundhint1, err := e.CheckOne(m, rec, tickers)
   338  	if err != nil {
   339  		if err.Error() == deluserstr {
   340  			m.Info("deleted user")
   341  			return nil
   342  		}
   343  		return err
   344  	}
   345  	// Skip the rate limit on the second check.
   346  	perr2, foundhint2, err := e.CheckOne(m, rec, nil)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	if foundhint1 != foundhint2 {
   352  		return fmt.Errorf("Local verifiers disagree: foundhint1:%v foundhint:%v (likely timing)",
   353  			foundhint1, foundhint2)
   354  	}
   355  
   356  	if (perr1 == nil) != (perr2 == nil) {
   357  		return fmt.Errorf("Local verifiers disagree:\n  %v\n  %v", perr1, perr2)
   358  	}
   359  
   360  	succeeded := foundhint1 && (perr1 == nil)
   361  
   362  	if succeeded != shouldsucceed {
   363  		return fmt.Errorf("Local verifiers disagree with server: server:%v client:%v", serverstate, perr1)
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  // CheckOne checks one proof using two checkers (default, pvl).
   370  // NOTE: This doesn't make sense anymore because pvl is the default.
   371  // Returns nil or an error, whether a hint was found, and any more serious error
   372  func (e *ScanProofsEngine) CheckOne(m libkb.MetaContext, rec map[string]string, tickers ScanProofsTickers) (libkb.ProofError, bool, error) {
   373  	uid := keybase1.UID(rec["uid"])
   374  	sigid := keybase1.SigID(rec["sig_id"])
   375  
   376  	foundhint := false
   377  	hint, err := e.GetSigHint(m, uid, sigid)
   378  	if err != nil {
   379  		return nil, foundhint, err
   380  	}
   381  	if hint == nil {
   382  		return nil, foundhint, nil
   383  	}
   384  	foundhint = true
   385  
   386  	link, err := e.GetRemoteProofChainLink(m, uid, sigid)
   387  	if err != nil {
   388  		return nil, foundhint, err
   389  	}
   390  
   391  	pc, err := libkb.MakeProofChecker(m, m.G().GetProofServices(), link)
   392  	if err != nil {
   393  		return nil, foundhint, err
   394  	}
   395  
   396  	// Beyond this point, external requests will occur, and rate limiting is used
   397  	ptype := link.GetProofType()
   398  	if tickers[ptype] != nil {
   399  		m.Info("Waiting for ticker: %v (%v)", keybase1.ProofTypeRevMap[ptype], ptype)
   400  		<-tickers[ptype].C
   401  	}
   402  
   403  	pvlSource := m.G().GetPvlSource()
   404  	if pvlSource == nil {
   405  		return nil, foundhint, fmt.Errorf("no pvl source for proof verification")
   406  	}
   407  	pvlU, err := pvlSource.GetLatestEntry(m)
   408  	if err != nil {
   409  		return nil, foundhint, fmt.Errorf("error getting pvl: %s", err)
   410  	}
   411  
   412  	if _, perr := pc.CheckStatus(m, *hint, libkb.ProofCheckerModeActive, pvlU); perr != nil {
   413  		return perr, foundhint, nil
   414  	}
   415  
   416  	return nil, foundhint, nil
   417  }
   418  
   419  // GetSigHint gets the SigHint. This can return (nil, nil) if nothing goes wrong but there is no hint.
   420  func (e *ScanProofsEngine) GetSigHint(m libkb.MetaContext, uid keybase1.UID, sigid keybase1.SigID) (*libkb.SigHint, error) {
   421  	sighints, err := libkb.LoadAndRefreshSigHints(m, uid)
   422  	if err != nil {
   423  		return nil, err
   424  	}
   425  
   426  	sighint := sighints.Lookup(sigid)
   427  	if sighint == nil {
   428  		return nil, nil
   429  	}
   430  	return sighint, nil
   431  }
   432  
   433  func (e *ScanProofsEngine) GetRemoteProofChainLink(m libkb.MetaContext, uid keybase1.UID, sigid keybase1.SigID) (libkb.RemoteProofChainLink, error) {
   434  	user, err := libkb.LoadUser(libkb.NewLoadUserArgWithMetaContext(m).WithUID(uid))
   435  	if err != nil {
   436  		return nil, fmt.Errorf("Error loading user: %v", err)
   437  	}
   438  
   439  	link := user.LinkFromSigID(sigid)
   440  	if link == nil {
   441  		return nil, fmt.Errorf("Could not find link from sigid")
   442  	}
   443  
   444  	tlink, w := libkb.NewTypedChainLink(link)
   445  	if w != nil {
   446  		return nil, fmt.Errorf("Could not get typed chain link: %v", w.Warning())
   447  	}
   448  
   449  	switch vlink := tlink.(type) {
   450  	case libkb.RemoteProofChainLink:
   451  		return vlink, nil
   452  	default:
   453  		return nil, fmt.Errorf("Link is not a RemoteProofChainLink: %v", tlink)
   454  	}
   455  }
   456  
   457  func (e *ScanProofsEngine) ParseIndices(indices string) (start int, end int, reterr error) {
   458  	wrap := func(format string, arg ...interface{}) error {
   459  		f2 := fmt.Sprintf("Invalid indices: %s", format)
   460  		return fmt.Errorf(f2, arg...)
   461  	}
   462  	ss := strings.Split(strings.TrimSpace(indices), ":")
   463  	if len(ss) != 2 {
   464  		return start, end, wrap("must be like start:end")
   465  	}
   466  	var err error
   467  	start, err = strconv.Atoi(ss[0])
   468  	if err != nil {
   469  		return start, end, wrap("could not convert start: %v", err)
   470  	}
   471  	end, err = strconv.Atoi(ss[1])
   472  	if err != nil {
   473  		return start, end, wrap("could not convert end: %v", err)
   474  	}
   475  	if end <= start {
   476  		return start, end, wrap("%v <= %v", end, start)
   477  	}
   478  	reterr = nil
   479  	return
   480  }
   481  
   482  // LoadScanProofsIgnore loads an ignore file and returns the list of proofids to ignore.
   483  func LoadScanProofsIgnore(filepath string) ([]string, error) {
   484  	f, err := os.Open(filepath)
   485  	if err != nil {
   486  		return nil, err
   487  	}
   488  	defer f.Close()
   489  	scanner := bufio.NewScanner(f)
   490  	scanner.Split(bufio.ScanLines)
   491  	var ignored []string
   492  	for scanner.Scan() {
   493  		x := strings.TrimSpace(scanner.Text())
   494  		if strings.HasPrefix(x, "//") {
   495  			continue
   496  		}
   497  		ignored = append(ignored, x)
   498  	}
   499  	return ignored, nil
   500  }