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

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"errors"
     8  	"time"
     9  )
    10  
    11  type bug3964Repairman struct {
    12  	Contextified
    13  }
    14  
    15  func newBug3964Repairman(g *GlobalContext) *bug3964Repairman {
    16  	return &bug3964Repairman{Contextified: NewContextified(g)}
    17  }
    18  
    19  func (b *bug3964Repairman) attemptRepair(m MetaContext, lksec *LKSec, dkm DeviceKeyMap) (ran bool, serverHalfSet *LKSecServerHalfSet, err error) {
    20  	defer m.Trace("bug3964Repairman#attemptRepair", &err)()
    21  	var oldKeyring, newKeyring *SKBKeyringFile
    22  	lctx := m.LoginContext()
    23  	oldKeyring, err = lctx.Keyring(m)
    24  	if err != nil {
    25  		return false, nil, err
    26  	}
    27  	newKeyring, serverHalfSet, err = oldKeyring.Bug3964Repair(m, lksec, dkm)
    28  	if err != nil {
    29  		return false, nil, err
    30  	}
    31  	if newKeyring == nil {
    32  		return false, nil, nil
    33  	}
    34  	if err = newKeyring.Save(); err != nil {
    35  		m.Debug("Error saving new keyring: %s", err)
    36  		return false, nil, err
    37  	}
    38  	lctx.ClearKeyring()
    39  	return true, serverHalfSet, err
    40  }
    41  
    42  func (b *bug3964Repairman) loadLKSecServerDetails(m MetaContext, lksec *LKSec) (ret DeviceKeyMap, err error) {
    43  	defer m.Trace("bug3964Repairman#loadLKSecServerDetails", &err)()
    44  	ret, err = lksec.LoadServerDetails(m)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	lksec.SetFullSecret(m)
    49  	return ret, err
    50  }
    51  
    52  func (b *bug3964Repairman) updateSecretStore(m MetaContext, nun NormalizedUsername, lksec *LKSec) error {
    53  	fs := lksec.FullSecret()
    54  	ss := b.G().SecretStore()
    55  	if fs.IsNil() {
    56  		m.Warning("Got unexpected nil full secret")
    57  		return ss.ClearSecret(m, nun)
    58  	}
    59  	return ss.StoreSecret(m, nun, fs)
    60  }
    61  
    62  func (b *bug3964Repairman) saveRepairmanVisit(nun NormalizedUsername) (err error) {
    63  	defer b.G().Trace("bug3964Repairman#saveRepairmanVisit", &err)()
    64  	return b.G().Env.GetConfigWriter().SetBug3964RepairTime(nun, time.Now())
    65  }
    66  
    67  func (b *bug3964Repairman) postToServer(m MetaContext, serverHalfSet *LKSecServerHalfSet, ppgen PassphraseGeneration, nun NormalizedUsername) (err error) {
    68  	defer m.G().CTrace(m.Ctx(), "bug3964Repairman#postToServer", &err)()
    69  	if serverHalfSet == nil {
    70  		return errors.New("internal error --- had nil server half set")
    71  	}
    72  	_, err = m.G().API.Post(m, APIArg{
    73  		Endpoint:    "user/bug_3964_repair",
    74  		SessionType: APISessionTypeREQUIRED,
    75  		Args: HTTPArgs{
    76  			"device_id":         S{Val: m.G().Env.GetDeviceIDForUsername(nun).String()},
    77  			"ppgen":             I{Val: int(ppgen)},
    78  			"lks_server_halves": S{Val: serverHalfSet.EncodeToHexList()},
    79  		},
    80  	})
    81  	return err
    82  }
    83  
    84  func (b *bug3964Repairman) computeShortCircuit(nun NormalizedUsername) (ss bool, err error) {
    85  	defer b.G().Trace("bug3964Repairman#computeShortCircuit", &err)()
    86  	repairTime, tmpErr := b.G().Env.GetConfig().GetBug3964RepairTime(nun)
    87  
    88  	// Ignore any decoding errors
    89  	if tmpErr != nil {
    90  		b.G().Log.Warning("Problem reading previous bug 3964 repair time: %s", tmpErr)
    91  	}
    92  
    93  	if repairTime.IsZero() {
    94  		b.G().Log.Debug("| repair time is zero or wasn't set")
    95  		return false, nil
    96  	}
    97  	var fileTime time.Time
    98  	fileTime, err = StatSKBKeyringMTime(nun, b.G())
    99  	if err != nil {
   100  		return false, err
   101  	}
   102  	ss = !repairTime.Before(fileTime)
   103  	b.G().Log.Debug("| Checking repair-time (%s) v file-write-time (%s): shortCircuit=%v", repairTime, fileTime, ss)
   104  	return ss, nil
   105  }
   106  
   107  func (b *bug3964Repairman) fixLKSClientHalf(m MetaContext, lksec *LKSec, ppgen PassphraseGeneration) (err error) {
   108  	defer m.Trace("bug3964Repairman#fixLKSClientHalf", &err)()
   109  	var me *User
   110  	var encKey GenericKey
   111  	var ctext string
   112  
   113  	me, err = LoadMe(NewLoadUserArgWithMetaContext(m))
   114  	if err != nil {
   115  		return err
   116  	}
   117  	encKey, err = me.GetDeviceSubkey()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	// make client half recovery
   122  	kid := encKey.GetKID()
   123  	ctext, err = lksec.EncryptClientHalfRecovery(encKey)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	_, err = b.G().API.Post(m, APIArg{
   129  		Endpoint:    "device/update_lks_client_half",
   130  		SessionType: APISessionTypeREQUIRED,
   131  		Args: HTTPArgs{
   132  			"ppgen":           I{Val: int(ppgen)},
   133  			"kid":             S{Val: kid.String()},
   134  			"lks_client_half": S{Val: ctext},
   135  		},
   136  	})
   137  
   138  	return err
   139  }
   140  
   141  // Run the engine
   142  func (b *bug3964Repairman) Run(m MetaContext) (err error) {
   143  	defer m.G().CTrace(m.Ctx(), "bug3964Repairman#Run", &err)()
   144  	lctx := m.LoginContext()
   145  	pps := lctx.PassphraseStreamCache().PassphraseStream()
   146  
   147  	var lksec *LKSec
   148  	var ran bool
   149  	var dkm DeviceKeyMap
   150  	var ss bool
   151  	var serverHalfSet *LKSecServerHalfSet
   152  	nun := m.G().Env.GetUsername()
   153  
   154  	if m.G().TestOptions.NoBug3964Repair {
   155  		m.G().Log.CDebugf(m.Ctx(), "| short circuit due to test options")
   156  		return nil
   157  	}
   158  
   159  	if pps == nil {
   160  		m.G().Log.CDebugf(m.Ctx(), "| Can't run repairman without a passphrase stream")
   161  		return nil
   162  	}
   163  
   164  	if ss, err = b.computeShortCircuit(nun); err != nil {
   165  		return err
   166  	}
   167  
   168  	if ss {
   169  		// This logline is asserted in testing in bug_3964_repairman_test
   170  		m.G().Log.CDebugf(m.Ctx(), "| Repairman already visited after file update; bailing out")
   171  		return nil
   172  	}
   173  
   174  	// This logline is asserted in testing in bug_3964_repairman_test
   175  	m.G().Log.CDebugf(m.Ctx(), "| Repairman wasn't short-circuited")
   176  
   177  	lksec, err = pps.ToLKSec(lctx.GetUID())
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	if dkm, err = b.loadLKSecServerDetails(m, lksec); err != nil {
   183  		return err
   184  	}
   185  
   186  	if ran, serverHalfSet, err = b.attemptRepair(m, lksec, dkm); err != nil {
   187  		return err
   188  	}
   189  
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	m.G().Log.CDebugf(m.Ctx(), "| SKB keyring repair completed; edits=%v", ran)
   195  
   196  	if !ran {
   197  		return b.saveRepairmanVisit(nun)
   198  	}
   199  
   200  	if err := b.fixLKSClientHalf(m, lksec, pps.Generation()); err != nil {
   201  		return err
   202  	}
   203  
   204  	if ussErr := b.updateSecretStore(m, nun, lksec); ussErr != nil {
   205  		m.G().Log.CWarningf(m.Ctx(), "Error in secret store manipulation: %s", ussErr)
   206  	} else {
   207  		err := b.saveRepairmanVisit(nun)
   208  		if err != nil {
   209  			return err
   210  		}
   211  	}
   212  
   213  	err = b.postToServer(m, serverHalfSet, pps.Generation(), nun)
   214  
   215  	return err
   216  }
   217  
   218  func RunBug3964Repairman(m MetaContext) error {
   219  	err := newBug3964Repairman(m.G()).Run(m)
   220  	if err != nil {
   221  		m.G().Log.CDebugf(m.Ctx(), "Error running Bug 3964 repairman: %s", err)
   222  	}
   223  	return err
   224  }