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

     1  package teams
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"encoding/base64"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/keybase/client/go/kbtest"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  // TestSeitanEncryption does an offline test run of seitan crypto
    18  // functions.
    19  func TestSeitanEncryption(t *testing.T) {
    20  	tc := SetupTest(t, "team", 1)
    21  	defer tc.Cleanup()
    22  
    23  	user, err := kbtest.CreateAndSignupFakeUser("team", tc.G)
    24  	require.NoError(t, err)
    25  
    26  	name := createTeam(tc)
    27  
    28  	team, err := Load(context.TODO(), tc.G, keybase1.LoadTeamArg{
    29  		Name:        name,
    30  		NeedAdmin:   true,
    31  		ForceRepoll: true,
    32  	})
    33  	require.NoError(t, err)
    34  
    35  	ikey, err := GenerateIKey()
    36  	require.NoError(t, err)
    37  	t.Logf("ikey is: %q (%d)\n", ikey, len(ikey))
    38  
    39  	sikey, err := ikey.GenerateSIKey()
    40  	require.NoError(t, err)
    41  	t.Logf("sikey is: %v (%d)\n", sikey, len(sikey))
    42  
    43  	inviteID, err := sikey.GenerateTeamInviteID()
    44  	require.NoError(t, err)
    45  	t.Logf("Invite id is: %s\n", inviteID)
    46  	require.Equal(t, len(string(inviteID)), 32)
    47  
    48  	var labelSms keybase1.SeitanKeyLabelSms
    49  	labelSms.F = "Edwin Powell Hubble"
    50  	labelSms.N = "+48123ZZ3045"
    51  
    52  	label := keybase1.NewSeitanKeyLabelWithSms(labelSms)
    53  
    54  	pkey, encoded, err := ikey.GeneratePackedEncryptedKey(context.TODO(), team, label)
    55  	require.NoError(t, err)
    56  	require.EqualValues(t, pkey.Version, 1)
    57  	require.EqualValues(t, pkey.TeamKeyGeneration, 1)
    58  	require.NotZero(tc.T, pkey.RandomNonce)
    59  
    60  	t.Logf("Encrypted ikey with gen: %d\n", pkey.TeamKeyGeneration)
    61  	t.Logf("Armored output: %s\n", encoded)
    62  
    63  	pkey2, err := SeitanDecodePKey(encoded)
    64  	require.NoError(t, err)
    65  	require.Equal(t, pkey.Version, pkey2.Version)
    66  	require.Equal(t, pkey.TeamKeyGeneration, pkey2.TeamKeyGeneration)
    67  	require.Equal(t, pkey.RandomNonce, pkey2.RandomNonce)
    68  	require.Equal(t, pkey.EncryptedKeyAndLabel, pkey2.EncryptedKeyAndLabel)
    69  
    70  	keyAndLabel, err := pkey.DecryptKeyAndLabel(context.TODO(), team)
    71  	require.NoError(t, err)
    72  	keyAndLabelType, err := keyAndLabel.V()
    73  	require.NoError(t, err)
    74  	require.Equal(t, keybase1.SeitanKeyAndLabelVersion_V1, keyAndLabelType)
    75  	keyAndLabelV1 := keyAndLabel.V1()
    76  	require.EqualValues(t, ikey, keyAndLabelV1.I)
    77  
    78  	label2 := keyAndLabelV1.L
    79  	label2Type, err := label2.T()
    80  	require.NoError(t, err)
    81  	require.Equal(t, keybase1.SeitanKeyLabelType_SMS, label2Type)
    82  
    83  	labelSms2 := label2.Sms()
    84  	require.Equal(t, labelSms.F, labelSms2.F)
    85  	require.Equal(t, labelSms.N, labelSms2.N)
    86  
    87  	t.Logf("Decrypted ikey is %q\n", keyAndLabelV1.I)
    88  
    89  	_, _, err = sikey.GenerateAcceptanceKey(user.User.GetUID(), user.EldestSeqno, time.Now().Unix())
    90  	require.NoError(t, err)
    91  }
    92  
    93  // TestSeitanKnownSamples runs offline seitan crypto chain using known
    94  // inputs and compares results with known samples generated using
    95  // server test library.
    96  func TestSeitanKnownSamples(t *testing.T) {
    97  	fromB64 := func(b string) (ret []byte) {
    98  		ret, err := base64.StdEncoding.DecodeString(b)
    99  		require.NoError(t, err)
   100  		return ret
   101  	}
   102  
   103  	// Secret key is dKzxu7uoeL4gOpS9a+xPKJ0wM/8SQs8DAsvzqfSu6FU=
   104  	// IKey is raw2ewqp249dyod4
   105  	// SIKey is Yqbj8NgHkIG03wfZX/dxpBpqFoXPXNXyQr+MnvCMbS4=
   106  	// invite_id is 24189cc0ad5851ac52404ee99c7c9c27
   107  	// pkey is lAHAxBi8R7edkN/i0W+z1xbgsCqdFAdOFJXOaLvEIKAWDcvayhW+cel6YdZdpuVXj+Iyv434w30z3+PkascC
   108  	// Label is sms (type 1): { full_name : "Edwin Powell Hubble", number : "+48123ZZ3045" }
   109  
   110  	expectedIKey := SeitanIKey("raw2ewqp249dyod4")
   111  	var expectedSIKey SeitanSIKey
   112  	copy(expectedSIKey[:], fromB64("Yqbj8NgHkIG03wfZX/dxpBpqFoXPXNXyQr+MnvCMbS4="))
   113  	expectedInviteID := SCTeamInviteID("24189cc0ad5851ac52404ee99c7c9c27")
   114  
   115  	var secretKey keybase1.Bytes32
   116  	copy(secretKey[:], fromB64("dKzxu7uoeL4gOpS9a+xPKJ0wM/8SQs8DAsvzqfSu6FU="))
   117  
   118  	pkeyBase64 := "lAEBxBgfSKQYaD+wEBhdRga+OUuEyTlT1lg6sGbEW6uPYbSC94eoWQopzkyVVoaZYYx6sAH3EXewxYkrCoIyncd4hayOFeGZI5XraS/vS5YvqThWj19EZAzxRVBV/W6JrZuiCFuw5Rkx0TJqGg1n+Y65cXSCP5zbPP8="
   119  
   120  	pkey, err := SeitanDecodePKey(pkeyBase64)
   121  	require.NoError(t, err)
   122  	require.EqualValues(t, 1, pkey.Version)
   123  	require.EqualValues(t, 1, pkey.TeamKeyGeneration)
   124  
   125  	keyAndLabel, err := pkey.decryptKeyAndLabelWithSecretKey(secretKey)
   126  	require.NoError(t, err) // only encoded map or array can be decoded into a struct
   127  
   128  	keyAndLabelType, err := keyAndLabel.V()
   129  	require.NoError(t, err)
   130  	require.Equal(t, keybase1.SeitanKeyAndLabelVersion_V1, keyAndLabelType)
   131  	keyAndLabelV1 := keyAndLabel.V1()
   132  	ikey := SeitanIKey(keyAndLabelV1.I)
   133  
   134  	require.Equal(t, expectedIKey, ikey)
   135  
   136  	sikey, err := ikey.GenerateSIKey()
   137  	require.NoError(t, err)
   138  	require.Equal(t, expectedSIKey, sikey)
   139  
   140  	inviteID, err := sikey.GenerateTeamInviteID()
   141  	require.NoError(t, err)
   142  	require.Equal(t, expectedInviteID, inviteID)
   143  
   144  	label := keyAndLabelV1.L
   145  	labelType, err := label.T()
   146  	require.NoError(t, err)
   147  	require.Equal(t, keybase1.SeitanKeyLabelType_SMS, labelType)
   148  
   149  	labelSms := label.Sms()
   150  	require.Equal(t, "Edwin Powell Hubble", labelSms.F)
   151  	require.Equal(t, "+48123ZZ3045", labelSms.N)
   152  
   153  	pkey2, _, err := ikey.generatePackedEncryptedKeyWithSecretKey(secretKey, keybase1.PerTeamKeyGeneration(1), pkey.RandomNonce, keyAndLabelV1.L)
   154  	require.NoError(t, err)
   155  	require.Equal(t, pkey.Version, pkey2.Version)
   156  	require.Equal(t, pkey.TeamKeyGeneration, pkey2.TeamKeyGeneration)
   157  	require.Equal(t, pkey.RandomNonce, pkey2.RandomNonce)
   158  	require.Equal(t, pkey.EncryptedKeyAndLabel, pkey2.EncryptedKeyAndLabel)
   159  }
   160  
   161  // TestSeitanParams tests the note at the top of seitan.go.
   162  func TestSeitanParams(t *testing.T) {
   163  	require.True(t, (len(KBase30EncodeStd) <= int(base30BitMask)), "the right bitmask at log2(len(alphabet))")
   164  }
   165  
   166  func TestIsSeitanyNoMatches(t *testing.T) {
   167  	var noMatches = []string{
   168  		"team.aaa.bb.cc",
   169  		"aanbbjejjeff",
   170  		"a+b",
   171  		"aaa+b",
   172  		"+",
   173  		"+++",
   174  		"chia_public",
   175  	}
   176  	for _, s := range noMatches {
   177  		require.False(t, IsSeitany(s), "not seitany")
   178  	}
   179  }
   180  
   181  func TestParseSeitanTokenFromPaste(t *testing.T) {
   182  	units := []struct {
   183  		token     string
   184  		expectedS string
   185  		expectedB bool
   186  	}{
   187  		{
   188  			`aazaaa0a+aaaaaaaaa`,
   189  			`aazaaa0a+aaaaaaaaa`,
   190  			true,
   191  		}, {
   192  
   193  			`aazaaa0aaaaaaaaaa`,
   194  			`aazaaa0aaaaaaaaaa`,
   195  			false,
   196  		}, {
   197  
   198  			`team1`,
   199  			`team1`,
   200  			false,
   201  		}, {
   202  			`team1.subteam2`,
   203  			`team1.subteam2`,
   204  			false,
   205  		}, {
   206  			`team1.subteam222`,
   207  			`team1.subteam222`,
   208  			false,
   209  		}, {
   210  			`team1.subteam2222`,
   211  			`team1.subteam2222`,
   212  			false,
   213  		}, {
   214  			`team1.subteam22222`,
   215  			`team1.subteam22222`,
   216  			false,
   217  		}, {
   218  			`HELLO AND WELCOME TO THIS TEAM. token: aazaaa0a+aaaaaaaaa`,
   219  			`aazaaa0a+aaaaaaaaa`,
   220  			true,
   221  		}, {
   222  			`HELLO AND WELCOME TO THIS TEAM. token: aazaaa0aaaaaaaaa`,
   223  			`aazaaa0aaaaaaaaa`,
   224  			true,
   225  		}, {
   226  			`HELLO AND WELCOME TO THIS TEAM. token: aazaaa0aaaaaaaaaa`,
   227  			`aazaaa0aaaaaaaaaa`,
   228  			true,
   229  		}, {
   230  			`aazaaa0aaaaaaaaaa`,
   231  			`aazaaa0aaaaaaaaaa`,
   232  			false,
   233  		}, {
   234  			`aazaaa0aaaaaaaaaa aazaaa0aaaaaaaaaa`,
   235  			`aazaaa0aaaaaaaaaa aazaaa0aaaaaaaaaa`,
   236  			false,
   237  		}, {
   238  			`invited to team 0123456789012345 with token: 87zaaa0aaa1zyaaz`,
   239  			`87zaaa0aaa1zyaaz`,
   240  			true,
   241  		}, {
   242  			`Please join the agot team on Keybase. Install and paste this in the "Teams" tab:  token: m947873cdbwdvtku  quick install: keybase.io/_/go`,
   243  			`m947873cdbwdvtku`,
   244  			true,
   245  		},
   246  	}
   247  
   248  	for i, unit := range units {
   249  		t.Logf("[%v] %v", i, unit.token)
   250  		maybeSeitan, keepSecret := ParseSeitanTokenFromPaste(unit.token)
   251  		require.Equal(t, unit.expectedS, maybeSeitan)
   252  		require.Equal(t, unit.expectedB, keepSecret)
   253  	}
   254  }
   255  
   256  func TestTeamInviteSeitanFailures(t *testing.T) {
   257  	tc := SetupTest(t, "team", 1)
   258  	defer tc.Cleanup()
   259  
   260  	tc.Tp.SkipSendingSystemChatMessages = true
   261  
   262  	user2, err := kbtest.CreateAndSignupFakeUser("team", tc.G)
   263  	require.NoError(t, err)
   264  	kbtest.Logout(tc)
   265  
   266  	admin, err := kbtest.CreateAndSignupFakeUser("team", tc.G)
   267  	require.NoError(t, err)
   268  
   269  	teamName, teamID := createTeam2(tc)
   270  	t.Logf("Created team %q", teamName.String())
   271  
   272  	token, err := CreateSeitanToken(context.Background(), tc.G,
   273  		teamName.String(), keybase1.TeamRole_WRITER, keybase1.SeitanKeyLabel{})
   274  	require.NoError(t, err)
   275  
   276  	t.Logf("Created token %q", token)
   277  
   278  	kbtest.LogoutAndLoginAs(tc, user2)
   279  
   280  	// Generate invitation id, but make AKey with different IKey.
   281  	// Simulate "replay attack" or similar.
   282  	ikey, err := ParseIKeyFromString(string(token))
   283  	require.NoError(t, err)
   284  	sikey, err := ikey.GenerateSIKey()
   285  	require.NoError(t, err)
   286  	inviteID, err := sikey.GenerateTeamInviteID()
   287  	require.NoError(t, err)
   288  
   289  	ikey2, err := GenerateIKey() // ikey2 is not the ikey from token.
   290  	require.NoError(t, err)
   291  	sikey2, err := ikey2.GenerateSIKey()
   292  	require.NoError(t, err)
   293  	unixNow := time.Now().Unix()
   294  	badAkey, badEncoded, err := sikey2.GenerateAcceptanceKey(user2.GetUID(), user2.EldestSeqno, unixNow)
   295  	require.NoError(t, err)
   296  
   297  	err = postSeitanV1(tc.MetaContext(), acceptedSeitanV1{
   298  		akey:     badAkey,
   299  		encoded:  badEncoded,
   300  		unixNow:  unixNow,
   301  		inviteID: inviteID,
   302  	})
   303  	require.NoError(t, err)
   304  
   305  	teamInviteID, err := inviteID.TeamInviteID()
   306  	require.NoError(t, err)
   307  
   308  	t.Logf("handle synthesized rekeyd command")
   309  	kbtest.LogoutAndLoginAs(tc, admin)
   310  
   311  	msg := keybase1.TeamSeitanMsg{
   312  		TeamID: teamID,
   313  		Seitans: []keybase1.TeamSeitanRequest{{
   314  			InviteID:    teamInviteID,
   315  			Uid:         user2.GetUID(),
   316  			EldestSeqno: user2.EldestSeqno,
   317  			Akey:        keybase1.SeitanAKey(badEncoded),
   318  			Role:        keybase1.TeamRole_WRITER,
   319  			UnixCTime:   unixNow,
   320  		}},
   321  	}
   322  
   323  	API := libkb.NewAPIArgRecorder(tc.G.API)
   324  	tc.G.API = API
   325  	err = HandleTeamSeitan(context.TODO(), tc.G, msg)
   326  	// Seitan handler does not fail, but ignores the request.
   327  	require.NoError(t, err)
   328  	records := API.GetFilteredRecordsAndReset(func(rec *libkb.APIRecord) bool {
   329  		return rec.Arg.Endpoint == "team/reject_invite_acceptance"
   330  	})
   331  	// not an InviteLink, nothing to reject
   332  	require.Len(t, records, 0, "no invite link acceptances were rejected")
   333  
   334  	t.Logf("invite should still be there")
   335  	t0, err := GetTeamByNameForTest(context.Background(), tc.G, teamName.String(), false /* public */, true /* needAdmin */)
   336  	require.NoError(t, err)
   337  	require.Equal(t, 1, t0.NumActiveInvites(), "invite should still be active")
   338  	require.EqualValues(t, t0.CurrentSeqno(), 2)
   339  
   340  	t.Logf("user should not be in team")
   341  	role, err := t0.MemberRole(context.Background(), user2.GetUserVersion())
   342  	require.NoError(t, err)
   343  	require.Equal(t, keybase1.TeamRole_NONE, role, "user role")
   344  }