github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/auth/credential_authority_test.go (about)

     1  package auth
     2  
     3  import (
     4  	"crypto/rand"
     5  	"encoding/binary"
     6  	"fmt"
     7  	libkb "github.com/keybase/client/go/libkb"
     8  	"github.com/keybase/client/go/logger"
     9  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    10  	context "golang.org/x/net/context"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  )
    15  
    16  type testUser struct {
    17  	uid      keybase1.UID
    18  	username libkb.NormalizedUsername
    19  	sibkeys  []keybase1.KID
    20  	subkeys  []keybase1.KID
    21  }
    22  
    23  type testState struct {
    24  	sync.Mutex
    25  
    26  	users   map[keybase1.UID](*testUser)
    27  	changes []keybase1.UID
    28  	now     time.Time
    29  	evictCh chan keybase1.UID
    30  	pokeCh  chan struct{}
    31  	numGets int
    32  }
    33  
    34  var seq uint32
    35  
    36  func genKID() keybase1.KID {
    37  	var kid [35]byte
    38  	kid[0] = 0x1
    39  	kid[1] = 0x20
    40  	binary.BigEndian.PutUint32(kid[30:34], seq)
    41  	seq++
    42  	kid[34] = 0xa0
    43  	return keybase1.KIDFromSlice(kid[:])
    44  }
    45  
    46  func genUsername() string {
    47  	w, _ := libkb.SecWordList(1)
    48  	var buf [4]byte
    49  	_, _ = rand.Read(buf[:])
    50  	return fmt.Sprintf("%s%x", w[0], buf)
    51  }
    52  
    53  func newTestUser(nKeys int) *testUser {
    54  	un := genUsername()
    55  	ret := testUser{
    56  		username: libkb.NewNormalizedUsername(un),
    57  		uid:      libkb.UsernameToUID(un),
    58  		sibkeys:  make([]keybase1.KID, nKeys),
    59  		subkeys:  make([]keybase1.KID, nKeys),
    60  	}
    61  	for i := 0; i < nKeys; i++ {
    62  		ret.sibkeys[i] = genKID()
    63  		ret.subkeys[i] = genKID()
    64  	}
    65  	return &ret
    66  }
    67  
    68  func (ts *testState) newTestUser(nKeys int) *testUser {
    69  	ts.Lock()
    70  	defer ts.Unlock()
    71  	ret := newTestUser(nKeys)
    72  	ts.users[ret.uid] = ret
    73  	return ret
    74  }
    75  
    76  func (ts *testState) mutateUser(uid keybase1.UID, mutator func(u *testUser)) bool {
    77  	ts.Lock()
    78  	defer ts.Unlock()
    79  	u := ts.users[uid]
    80  	if u == nil {
    81  		return false
    82  	}
    83  	mutator(u)
    84  	ts.changes = append(ts.changes, uid)
    85  	return true
    86  }
    87  
    88  func newTestState() *testState {
    89  	return &testState{
    90  		users:   make(map[keybase1.UID](*testUser)),
    91  		now:     time.Unix(100, 0),
    92  		evictCh: make(chan keybase1.UID, 1),
    93  		pokeCh:  make(chan struct{}),
    94  	}
    95  }
    96  
    97  type userNotFoundError struct {
    98  }
    99  
   100  func (e userNotFoundError) Error() string {
   101  	return "user not found"
   102  }
   103  
   104  func (ts *testState) GetUser(_ context.Context, uid keybase1.UID) (
   105  	un libkb.NormalizedUsername, sibkeys, subkeys []keybase1.KID, isDeleted bool, err error) {
   106  	ts.Lock()
   107  	defer ts.Unlock()
   108  	u := ts.users[uid]
   109  	if u == nil {
   110  		return libkb.NormalizedUsername(""), nil, nil, false, userNotFoundError{}
   111  	}
   112  	ts.numGets++
   113  	return u.username, u.sibkeys, u.subkeys, false, nil
   114  }
   115  
   116  func (ts *testState) PollForChanges(_ context.Context) ([]keybase1.UID, error) {
   117  	ts.Lock()
   118  	defer ts.Unlock()
   119  	ret := ts.changes
   120  	ts.changes = nil
   121  	return ret, nil
   122  }
   123  
   124  var _ UserKeyAPIer = (*testState)(nil)
   125  var _ engine = (*testState)(nil)
   126  
   127  func (ts *testState) tick(d time.Duration) {
   128  	ts.pokeCh <- struct{}{}
   129  	ts.Lock()
   130  	ts.now = ts.now.Add(d)
   131  	ts.Unlock()
   132  	ts.pokeCh <- struct{}{}
   133  }
   134  
   135  func (ts *testState) Now() time.Time {
   136  	ts.Lock()
   137  	ret := ts.now
   138  	ts.Unlock()
   139  	return ret
   140  }
   141  
   142  func (ts *testState) GetPokeCh() <-chan struct{} { return ts.pokeCh }
   143  
   144  func (ts *testState) Evicted(uid keybase1.UID) {
   145  	ts.evictCh <- uid
   146  }
   147  
   148  func newTestSetup() (*testState, *CredentialAuthority) {
   149  	s := newTestState()
   150  	c := newCredentialAuthorityWithEngine(logger.New("test"), s, s)
   151  	return s, c
   152  }
   153  
   154  func TestSimple(t *testing.T) {
   155  	state, credentialAuthority := newTestSetup()
   156  	u0 := state.newTestUser(4)
   157  
   158  	key0 := u0.sibkeys[0]
   159  	key1 := u0.sibkeys[1]
   160  
   161  	if state.numGets != 0 {
   162  		t.Fatal("expected 0 gets")
   163  	}
   164  
   165  	err := credentialAuthority.CheckUserKey(context.TODO(), u0.uid, &u0.username, &key0, false)
   166  	if err != nil {
   167  		t.Fatal(err)
   168  	}
   169  	if state.numGets != 1 {
   170  		t.Fatal("expected 1 get")
   171  	}
   172  	err = credentialAuthority.CheckUserKey(context.TODO(), u0.uid, &u0.username, &key0, false)
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	if state.numGets != 1 {
   177  		t.Fatal("expected 1 get")
   178  	}
   179  
   180  	state.mutateUser(u0.uid, func(u *testUser) {
   181  		u.sibkeys = u.sibkeys[1:]
   182  	})
   183  
   184  	// Advance just an iota, so that our polling of the server
   185  	// has a chance to complete.
   186  	state.tick(pollWait)
   187  
   188  	// wait for the first eviction
   189  	uid := <-state.evictCh
   190  	if uid != u0.uid {
   191  		t.Fatalf("Wrong UID on eviction: %s != %s\n", uid, u0.uid)
   192  	}
   193  
   194  	err = credentialAuthority.CheckUserKey(context.TODO(), u0.uid, &u0.username, &key0, false)
   195  	if err == nil {
   196  		t.Fatal("Expected an error")
   197  	}
   198  	bke, ok := err.(BadKeyError)
   199  	switch {
   200  	case !ok:
   201  		t.Fatal("Expected a bad key error")
   202  	case bke.uid != u0.uid:
   203  		t.Fatalf("Expected a bad key error on %s (not %s)", u0.uid, bke.uid)
   204  	case bke.kid != key0:
   205  		t.Fatalf("Expected a bad key error on key %s (not %s)", key0, bke.kid)
   206  	}
   207  
   208  	err = credentialAuthority.CheckUserKey(context.TODO(), u0.uid, &u0.username, &key1, false)
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	if state.numGets != 2 {
   213  		t.Fatal("expected 2 gets")
   214  	}
   215  	state.tick(userTimeout + time.Millisecond)
   216  	err = credentialAuthority.CheckUserKey(context.TODO(), u0.uid, &u0.username, &key1, false)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  	if state.numGets != 3 {
   221  		t.Fatal("expected 3 gets")
   222  	}
   223  	state.tick(cacheTimeout + time.Millisecond)
   224  
   225  	// u0 should now be gone since we haven't touched him in over cacheTimeout
   226  	// duration.
   227  	uid = <-state.evictCh
   228  	if uid != u0.uid {
   229  		t.Fatalf("Wrong UID on eviction: %s != %s\n", uid, u0.uid)
   230  	}
   231  
   232  	// Make a new user -- u1!
   233  	u1 := state.newTestUser(4)
   234  
   235  	ng := 3
   236  	for i := 0; i < 10; i++ {
   237  		err = credentialAuthority.CheckUserKey(context.TODO(), u1.uid, &u1.username, &u1.sibkeys[0], false)
   238  		if err != nil {
   239  			t.Fatal(err)
   240  		}
   241  		ng++
   242  		if state.numGets != ng {
   243  			t.Fatalf("expected %d gets, got %d", ng, state.numGets)
   244  		}
   245  		state.tick(userTimeout + time.Millisecond)
   246  
   247  		select {
   248  		case uid = <-state.evictCh:
   249  			t.Fatalf("Got unwanted eviction for %s", uid)
   250  		default:
   251  		}
   252  	}
   253  
   254  	state.tick(cacheTimeout - userTimeout + 3*time.Millisecond)
   255  	uid = <-state.evictCh
   256  	if uid != u1.uid {
   257  		t.Fatalf("Got wrong eviction: wanted %s but got %s\n", u1.uid, uid)
   258  	}
   259  
   260  	// Make a new user -- u2!
   261  	u2 := state.newTestUser(4)
   262  	err = credentialAuthority.CheckUserKey(context.TODO(), u2.uid, &u2.username, &u2.sibkeys[0], false)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	ng++
   267  	if state.numGets != ng {
   268  		t.Fatalf("expected %d gets, got %d", ng, state.numGets)
   269  	}
   270  
   271  	// Check that u2 is evicted properly after we shutdown the CA.
   272  	credentialAuthority.Shutdown()
   273  	uid = <-state.evictCh
   274  	if uid != u2.uid {
   275  		t.Fatalf("Got wrong eviction: wanted %s but got %s\n", u2.uid, uid)
   276  	}
   277  
   278  }
   279  
   280  func TestCheckUsers(t *testing.T) {
   281  	state, credentialAuthority := newTestSetup()
   282  
   283  	var users, usersWithDud []keybase1.UID
   284  	for i := 0; i < 10; i++ {
   285  		u := state.newTestUser(2)
   286  		users = append(users, u.uid)
   287  		usersWithDud = append(usersWithDud, u.uid)
   288  	}
   289  	usersWithDud = append(usersWithDud, libkb.UsernameToUID(genUsername()))
   290  
   291  	if state.numGets != 0 {
   292  		t.Fatal("expected 0 gets")
   293  	}
   294  
   295  	err := credentialAuthority.CheckUsers(context.TODO(), users)
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	if state.numGets != 10 {
   300  		t.Fatal("expected 10 gets")
   301  	}
   302  	err = credentialAuthority.CheckUsers(context.TODO(), users)
   303  	if err != nil {
   304  		t.Fatal(err)
   305  	}
   306  	if state.numGets != 10 {
   307  		t.Fatal("expected 10 gets")
   308  	}
   309  
   310  	err = credentialAuthority.CheckUsers(context.TODO(), usersWithDud)
   311  	if err == nil {
   312  		t.Fatal("Expected an error")
   313  	} else if _, ok := err.(userNotFoundError); !ok {
   314  		t.Fatal("Expected a user not found error")
   315  	}
   316  	credentialAuthority.Shutdown()
   317  }
   318  
   319  func TestCompareKeys(t *testing.T) {
   320  	state, credentialAuthority := newTestSetup()
   321  	u := state.newTestUser(10)
   322  
   323  	err := credentialAuthority.CompareUserKeys(context.TODO(), u.uid, u.sibkeys, u.subkeys)
   324  	if err != nil {
   325  		t.Fatal(err)
   326  	}
   327  
   328  	err = credentialAuthority.CompareUserKeys(context.TODO(), u.uid, nil, u.subkeys)
   329  	if err != nil {
   330  		t.Fatal(err)
   331  	}
   332  
   333  	err = credentialAuthority.CompareUserKeys(context.TODO(), u.uid, u.sibkeys, nil)
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  
   338  	missingSibkey := u.sibkeys[1:]
   339  	err = credentialAuthority.CompareUserKeys(context.TODO(), u.uid, missingSibkey, u.subkeys)
   340  	if err != ErrKeysNotEqual {
   341  		t.Fatal("Expected an ErrKeysNotEqual")
   342  	}
   343  
   344  	missingSubkey := u.subkeys[1:]
   345  	err = credentialAuthority.CompareUserKeys(context.TODO(), u.uid, u.sibkeys, missingSubkey)
   346  	if err != ErrKeysNotEqual {
   347  		t.Fatal("Expected an ErrKeysNotEqual")
   348  	}
   349  	credentialAuthority.Shutdown()
   350  }