github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }