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

     1  package engine
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/keybase/client/go/libkb"
    10  	"github.com/keybase/client/go/logger"
    11  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    12  	"github.com/stretchr/testify/require"
    13  	"golang.org/x/net/context"
    14  )
    15  
    16  type auditLog struct {
    17  	l     logger.Logger
    18  	lines *[]string
    19  }
    20  
    21  func newAuditLog(l logger.Logger) *auditLog {
    22  	return &auditLog{l: l, lines: &[]string{}}
    23  }
    24  
    25  func (a *auditLog) GetLines() []string {
    26  	return *a.lines
    27  }
    28  
    29  func (a *auditLog) ClearLines() {
    30  	a.lines = &[]string{}
    31  }
    32  
    33  func (a *auditLog) Debug(format string, args ...interface{}) {
    34  	s := fmt.Sprintf(format, args...)
    35  	a.l.CloneWithAddedDepth(1).Debug(s)
    36  	*a.lines = append(*a.lines, s)
    37  }
    38  func (a *auditLog) CDebugf(ctx context.Context, format string, args ...interface{}) {
    39  	s := fmt.Sprintf(format, args...)
    40  	a.l.CloneWithAddedDepth(1).CDebugf(ctx, s)
    41  	*a.lines = append(*a.lines, s)
    42  }
    43  func (a *auditLog) Info(format string, args ...interface{}) {
    44  	a.l.CloneWithAddedDepth(1).Info(format, args...)
    45  }
    46  func (a *auditLog) CInfof(ctx context.Context, format string, args ...interface{}) {
    47  	a.l.CloneWithAddedDepth(1).CInfof(ctx, format, args...)
    48  }
    49  func (a *auditLog) Notice(format string, args ...interface{}) {
    50  	a.l.CloneWithAddedDepth(1).Notice(format, args...)
    51  }
    52  func (a *auditLog) CNoticef(ctx context.Context, format string, args ...interface{}) {
    53  	a.l.CloneWithAddedDepth(1).CNoticef(ctx, format, args...)
    54  }
    55  func (a *auditLog) Warning(format string, args ...interface{}) {
    56  	a.l.CloneWithAddedDepth(1).Warning(format, args...)
    57  }
    58  func (a *auditLog) CWarningf(ctx context.Context, format string, args ...interface{}) {
    59  	a.l.CloneWithAddedDepth(1).CWarningf(ctx, format, args...)
    60  }
    61  func (a *auditLog) Error(format string, args ...interface{}) {
    62  	a.l.CloneWithAddedDepth(1).Errorf(format, args...)
    63  }
    64  func (a *auditLog) Errorf(format string, args ...interface{}) {
    65  	a.l.CloneWithAddedDepth(1).Errorf(format, args...)
    66  }
    67  func (a *auditLog) CErrorf(ctx context.Context, format string, args ...interface{}) {
    68  	a.l.CloneWithAddedDepth(1).CErrorf(ctx, format, args...)
    69  }
    70  func (a *auditLog) Critical(format string, args ...interface{}) {
    71  	a.l.CloneWithAddedDepth(1).Critical(format, args...)
    72  }
    73  func (a *auditLog) CCriticalf(ctx context.Context, format string, args ...interface{}) {
    74  	a.l.CloneWithAddedDepth(1).CCriticalf(ctx, format, args...)
    75  }
    76  func (a *auditLog) Fatalf(format string, args ...interface{}) {
    77  	a.l.CloneWithAddedDepth(1).Fatalf(format, args...)
    78  }
    79  func (a *auditLog) CFatalf(ctx context.Context, format string, args ...interface{}) {
    80  	a.l.CloneWithAddedDepth(1).CFatalf(ctx, format, args...)
    81  }
    82  func (a *auditLog) Profile(fmts string, args ...interface{}) {
    83  	a.l.CloneWithAddedDepth(1).Profile(fmts, args...)
    84  }
    85  func (a *auditLog) Configure(style string, debug bool, filename string) {
    86  	a.l.CloneWithAddedDepth(1).Configure(style, debug, filename)
    87  }
    88  func (a *auditLog) CloneWithAddedDepth(depth int) logger.Logger {
    89  	// Keep the same list of strings. This is important, because the tests here
    90  	// read the list at the end, and expect all the log lines to be there, even
    91  	// though some of the loggin calls in the middle call CloneWithAddedDepth.
    92  	return &auditLog{
    93  		l:     a.l.CloneWithAddedDepth(depth),
    94  		lines: a.lines,
    95  	}
    96  }
    97  func (a *auditLog) SetExternalHandler(handler logger.ExternalHandler) {
    98  	a.l.SetExternalHandler(handler)
    99  }
   100  
   101  func corruptDevice2(dev1 libkb.TestContext, dev2 libkb.TestContext) (*libkb.DeviceKey, error) {
   102  	m2 := NewMetaContextForTest(dev2)
   103  	m1 := NewMetaContextForTest(dev1)
   104  
   105  	var ret libkb.DeviceKey
   106  
   107  	ss, err := m1.G().ActiveDevice.SyncSecrets(m1)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	ret, err = ss.FindDevice(m1.G().Env.GetDeviceID())
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	// Dev1 has a passphrase cached, but dev2 doesn't (since it was provisioned).
   117  	// For this test though it's fine to take the passphrase from dev1.
   118  	pps, err := libkb.GetPassphraseStreamStored(m1)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	if pps == nil {
   123  		return nil, errors.New("empty passphrase stream on m1, but expected one since we just signed up")
   124  	}
   125  	goodLksec := libkb.NewLKSec(pps, m2.CurrentUID())
   126  
   127  	if err = goodLksec.LoadServerHalf(m2); err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	dev1ServerHalf, err := ret.ToLKSec()
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	badLskec := libkb.NewLKSecWithFullSecret(
   137  		goodLksec.CorruptedFullSecretForBug3964Testing(dev1ServerHalf),
   138  		dev2.G.Env.GetUID(),
   139  	)
   140  	var krf *libkb.SKBKeyringFile
   141  	krf, err = libkb.LoadSKBKeyringFromMetaContext(m2)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	for _, b := range krf.Blocks {
   146  		raw, _, erroneousMask, err := goodLksec.Decrypt(m2, b.Priv.Data)
   147  		if err != nil {
   148  			return nil, err
   149  		}
   150  		if !erroneousMask.IsNil() {
   151  			return nil, errors.New("bad erroneousMask")
   152  		}
   153  		b.Priv.Data, err = badLskec.Encrypt(m2, raw)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  	}
   158  	krf.MarkDirty()
   159  	if err = krf.Save(); err != nil {
   160  		return nil, err
   161  	}
   162  	return &ret, nil
   163  }
   164  
   165  // Limit the log lines to a function "trace" given by which. Meaning log lines
   166  // that start with "+ which" and end with "- which"
   167  func limitToTrace(lines []string, which string) []string {
   168  	for i, l := range lines {
   169  		if l == "+ "+which {
   170  			rest := lines[(i + 1):]
   171  			for j, k := range rest {
   172  				if strings.HasPrefix(k, "- "+which) {
   173  					return rest[:j]
   174  				}
   175  			}
   176  			return nil
   177  		}
   178  	}
   179  	return nil
   180  }
   181  
   182  func checkAuditLogForBug3964Recovery(t *testing.T, log []string, deviceID keybase1.DeviceID, dev1Key *libkb.DeviceKey) {
   183  	log = limitToTrace(log, "LKSec#tryAllDevicesForBug3964Recovery()")
   184  	needle := fmt.Sprintf("| Trying Bug 3964 Recovery w/ device %q {id: %s, lks: %s...}",
   185  		dev1Key.Description, deviceID, dev1Key.LksServerHalf[0:8])
   186  	for i, line := range log {
   187  		if strings.HasPrefix(line, needle) {
   188  			if log[i+1] == "| Success" {
   189  				return
   190  			}
   191  			t.Fatalf("Found %q but it wasn't followed by '| Success'", needle)
   192  		}
   193  	}
   194  	t.Fatalf("Didn't find evidence of %q", needle)
   195  }
   196  
   197  func findLine(t *testing.T, haystack []string, needle string) []string {
   198  	for i, line := range haystack {
   199  		if strings.HasPrefix(line, needle) {
   200  			return haystack[(i + 1):]
   201  		}
   202  	}
   203  	t.Fatalf("Didn't find line %q", needle)
   204  	return nil
   205  }
   206  
   207  func checkAuditLogForBug3964Repair(t *testing.T, log []string, deviceID keybase1.DeviceID, dev1Key *libkb.DeviceKey) {
   208  	log = limitToTrace(log, "bug3964Repairman#Run")
   209  	require.NotZero(t, len(log))
   210  	log = findLine(t, log, "| Repairman wasn't short-circuited")
   211  	require.NotZero(t, len(log))
   212  	log = findLine(t, log, "+ bug3964Repairman#saveRepairmanVisit")
   213  	require.NotZero(t, len(log))
   214  }
   215  
   216  func logoutLogin(t *testing.T, user *FakeUser, dev libkb.TestContext) {
   217  	Logout(dev)
   218  
   219  	uis := libkb.UIs{
   220  		ProvisionUI: newTestProvisionUIPassphrase(),
   221  		LoginUI:     &libkb.TestLoginUI{},
   222  		LogUI:       dev.G.UI.GetLogUI(),
   223  		SecretUI:    user.NewSecretUI(),
   224  		GPGUI:       &gpgtestui{},
   225  	}
   226  	eng := NewLogin(dev.G, keybase1.DeviceTypeV2_DESKTOP, user.Username, keybase1.ClientType_CLI)
   227  	m := NewMetaContextForTest(dev).WithUIs(uis)
   228  	if err := RunEngine2(m, eng); err != nil {
   229  		t.Fatal(err)
   230  	}
   231  }
   232  
   233  func checkAuditLogCleanLogin(t *testing.T, log []string) {
   234  	if len(limitToTrace(log, "LKSec#Decrypt()")) == 0 {
   235  		t.Fatalf("at least expected a login call")
   236  	}
   237  	for _, line := range log {
   238  		if strings.HasPrefix(line, "+ LKSec#tryAllDevicesForBug3964Recovery()") {
   239  			t.Fatalf("found attempt to try bug 3964 recovery after a full repair")
   240  		}
   241  	}
   242  }
   243  
   244  func checkAuditLogForRepairmanShortCircuit(t *testing.T, log []string) {
   245  	for _, line := range log {
   246  		if strings.HasPrefix(line, "| Repairman wasn't short-circuited") {
   247  			t.Fatalf("short-circuit mechanism failed")
   248  		}
   249  	}
   250  	found := false
   251  	for _, line := range log {
   252  		if strings.HasPrefix(line, "| Repairman already visited after file update; bailing out") {
   253  			found = true
   254  			break
   255  		}
   256  	}
   257  	if !found {
   258  		t.Fatalf("Didn't find a mention of short-circuiting")
   259  	}
   260  }
   261  
   262  func checkLKSWorked(t *testing.T, tctx libkb.TestContext, u *FakeUser) {
   263  	uis := libkb.UIs{
   264  		SecretUI: u.NewSecretUI(),
   265  	}
   266  	me, err := libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(NewMetaContextForTest(tctx)))
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	// need unlocked signing key
   271  	ska := libkb.SecretKeyArg{
   272  		Me:      me,
   273  		KeyType: libkb.DeviceEncryptionKeyType,
   274  	}
   275  	m := NewMetaContextForTest(tctx).WithUIs(uis)
   276  	arg := m.SecretKeyPromptArg(ska, "tracking signature")
   277  	encKey, err := tctx.G.Keyrings.GetSecretKeyWithPrompt(NewMetaContextForTest(tctx), arg)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	if encKey == nil {
   282  		t.Fatal("got back a nil decryption key")
   283  	}
   284  	_, clientHalf, err := fetchLKS(m, encKey)
   285  	if err != nil {
   286  		t.Fatal(err)
   287  	}
   288  	pps, err := libkb.GetPassphraseStreamStored(m)
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  	if pps == nil {
   293  		t.Fatal("failed to get passphrase stream")
   294  	}
   295  	clientHalfExpected := pps.LksClientHalf()
   296  	if !clientHalf.Equal(clientHalfExpected) {
   297  		t.Fatal("got bad passphrase from LKS recovery")
   298  	}
   299  }
   300  
   301  func TestBug3964Repairman(t *testing.T) {
   302  	var log *auditLog
   303  
   304  	user, dev1, dev2, cleanup := SetupTwoDevicesWithHook(t, "bug", func(tc *libkb.TestContext) {
   305  		log = newAuditLog(tc.G.Log)
   306  		tc.G.Log = log
   307  	})
   308  	defer cleanup()
   309  
   310  	t.Logf("-------------- Checkpoint 1 -----------------------")
   311  	dev1Key, err := corruptDevice2(dev1, dev2)
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  
   316  	dev2.G.TestOptions.NoBug3964Repair = true
   317  	logoutLogin(t, user, dev2)
   318  	t.Logf("-------------- Checkpoint 2 -----------------------")
   319  	checkAuditLogForBug3964Recovery(t, log.GetLines(), dev1.G.Env.GetDeviceID(), dev1Key)
   320  	dev2.G.TestOptions.NoBug3964Repair = false
   321  
   322  	log.ClearLines()
   323  	logoutLogin(t, user, dev2)
   324  	t.Logf("-------------- Checkpoint 3 -----------------------")
   325  	checkAuditLogForBug3964Repair(t, log.GetLines(), dev1.G.Env.GetDeviceID(), dev1Key)
   326  
   327  	log.ClearLines()
   328  	logoutLogin(t, user, dev2)
   329  	t.Logf("-------------- Checkpoint 4 -----------------------")
   330  	checkAuditLogCleanLogin(t, log.GetLines())
   331  	checkAuditLogForRepairmanShortCircuit(t, log.GetLines())
   332  
   333  	t.Logf("-------------- Checkpoint 5 -----------------------")
   334  	checkLKSWorked(t, dev2, user)
   335  }