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

     1  package systests
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/keybase/client/go/saltpackkeys"
    10  
    11  	"github.com/keybase/client/go/engine"
    12  	"github.com/keybase/client/go/externalstest"
    13  	"github.com/keybase/client/go/kbtest"
    14  
    15  	"golang.org/x/net/context"
    16  
    17  	"github.com/keybase/client/go/libkb"
    18  	"github.com/keybase/client/go/protocol/keybase1"
    19  	"github.com/keybase/client/go/teams"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func SetupTestWithInsecureTriplesec(tb libkb.TestingTB, name string) (tc libkb.TestContext) {
    24  	// SetupTest ignores the depth argument, so we can safely pass 0.
    25  	tc = externalstest.SetupTest(tb, name, 0)
    26  
    27  	// use an insecure triplesec in tests
    28  	installInsecureTriplesec(tc.G)
    29  
    30  	return tc
    31  }
    32  
    33  func createTeam(tc libkb.TestContext) (keybase1.TeamID, string) {
    34  	teams.ServiceInit(tc.G)
    35  
    36  	b, err := libkb.RandBytes(4)
    37  	require.NoError(tc.T, err)
    38  	name := "t_" + hex.EncodeToString(b)
    39  	teamID, err := teams.CreateRootTeam(context.TODO(), tc.G, name, keybase1.TeamSettings{})
    40  	require.NoError(tc.T, err)
    41  	require.NotNil(tc.T, teamID)
    42  
    43  	return *teamID, name
    44  }
    45  
    46  type fakeSaltpackUI struct{}
    47  
    48  var _ libkb.SaltpackUI = fakeSaltpackUI{}
    49  
    50  func (s fakeSaltpackUI) SaltpackPromptForDecrypt(_ context.Context, arg keybase1.SaltpackPromptForDecryptArg, usedDelegateUI bool) (err error) {
    51  	return nil
    52  }
    53  
    54  func (s fakeSaltpackUI) SaltpackVerifySuccess(_ context.Context, arg keybase1.SaltpackVerifySuccessArg) error {
    55  	return nil
    56  }
    57  
    58  type FakeBadSenderError struct {
    59  	senderType keybase1.SaltpackSenderType
    60  }
    61  
    62  func (e *FakeBadSenderError) Error() string {
    63  	return fmt.Sprintf("fakeSaltpackUI bad sender error: %s", e.senderType.String())
    64  }
    65  
    66  func (s fakeSaltpackUI) SaltpackVerifyBadSender(_ context.Context, arg keybase1.SaltpackVerifyBadSenderArg) error {
    67  	return &FakeBadSenderError{arg.Sender.SenderType}
    68  }
    69  
    70  func TestSaltpackEncryptDecryptForTeams(t *testing.T) {
    71  	tc := SetupTestWithInsecureTriplesec(t, "SysSpckEncDecTeam")
    72  	defer tc.Cleanup()
    73  
    74  	u1, err := kbtest.CreateAndSignupFakeUser("spkfe", tc.G)
    75  	require.NoError(t, err)
    76  	u2, err := kbtest.CreateAndSignupFakeUser("spkfe", tc.G)
    77  	require.NoError(t, err)
    78  
    79  	msg := "this message will be encrypted for a team"
    80  
    81  	_, teamName := createTeam(tc)
    82  	_, err = teams.AddMember(context.TODO(), tc.G, teamName, u1.Username, keybase1.TeamRole_WRITER, nil)
    83  	require.NoError(t, err)
    84  
    85  	trackUI := &kbtest.FakeIdentifyUI{
    86  		Proofs: make(map[string]string),
    87  	}
    88  	uis2 := libkb.UIs{IdentifyUI: trackUI, SecretUI: u2.NewSecretUI()}
    89  
    90  	sink := libkb.NewBufferCloser()
    91  	arg := &engine.SaltpackEncryptArg{
    92  		Opts: keybase1.SaltpackEncryptOptions{
    93  			TeamRecipients: []string{teamName},
    94  			UseEntityKeys:  true,
    95  			NoSelfEncrypt:  true,
    96  		},
    97  		Source: strings.NewReader(msg),
    98  		Sink:   sink,
    99  	}
   100  
   101  	eng := engine.NewSaltpackEncrypt(arg, saltpackkeys.NewSaltpackRecipientKeyfinderEngineAsInterface)
   102  	m2 := libkb.NewMetaContextForTest(tc).WithUIs(uis2)
   103  	if err := engine.RunEngine2(m2, eng); err != nil {
   104  		t.Fatal(err)
   105  	}
   106  
   107  	out := sink.String()
   108  	if len(out) == 0 {
   109  		t.Fatal("no output")
   110  	}
   111  
   112  	t.Logf("encrypted data: %s", out)
   113  
   114  	// switch to another team member and decrypt
   115  	kbtest.Logout(tc)
   116  	err = u1.Login(tc.G)
   117  	require.NoError(t, err)
   118  	uis1 := libkb.UIs{IdentifyUI: trackUI, SecretUI: u1.NewSecretUI(), SaltpackUI: fakeSaltpackUI{}}
   119  	m1 := libkb.NewMetaContextForTest(tc).WithUIs(uis1)
   120  
   121  	decoded := libkb.NewBufferCloser()
   122  	decarg := &engine.SaltpackDecryptArg{
   123  		Source: strings.NewReader(out),
   124  		Sink:   decoded,
   125  	}
   126  	dec := engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m1))
   127  	if err := engine.RunEngine2(m1, dec); err != nil {
   128  		t.Fatal(err)
   129  	}
   130  	decmsg := decoded.String()
   131  	require.Equal(t, msg, decmsg)
   132  }
   133  
   134  func TestSaltpackEncryptDecryptWithEntityKeysForTeams(t *testing.T) {
   135  	tt := newTeamTester(t)
   136  	defer tt.cleanup()
   137  
   138  	u1 := tt.addUser("u1sp")
   139  	u2 := tt.addUser("u2sp")
   140  	u3 := tt.addUser("u3sp")
   141  
   142  	// u2 creates the team, and adds u1
   143  	team := u2.createTeam()
   144  	u2.addTeamMember(team, u1.username, keybase1.TeamRole_WRITER)
   145  
   146  	msg := "this message will be encrypted for a team"
   147  
   148  	sink := libkb.NewBufferCloser()
   149  	arg := &engine.SaltpackEncryptArg{
   150  		Opts: keybase1.SaltpackEncryptOptions{
   151  			TeamRecipients: []string{team},
   152  			NoSelfEncrypt:  true,
   153  			UseEntityKeys:  true,
   154  		},
   155  		Source: strings.NewReader(msg),
   156  		Sink:   sink,
   157  	}
   158  
   159  	trackUI := &kbtest.FakeIdentifyUI{
   160  		Proofs: make(map[string]string),
   161  	}
   162  	uis1 := libkb.UIs{IdentifyUI: trackUI, SecretUI: u1.newSecretUI(), SaltpackUI: fakeSaltpackUI{}}
   163  	eng := engine.NewSaltpackEncrypt(arg, saltpackkeys.NewSaltpackRecipientKeyfinderEngineAsInterface)
   164  	m1 := libkb.NewMetaContextForTest(*u1.tc).WithUIs(uis1)
   165  	if err := engine.RunEngine2(m1, eng); err != nil {
   166  		t.Fatal(err)
   167  	}
   168  
   169  	out := sink.String()
   170  	if len(out) == 0 {
   171  		t.Fatal("no output")
   172  	}
   173  
   174  	t.Logf("encrypted data: %s", out)
   175  
   176  	// u1 can decrypt
   177  	decoded := libkb.NewBufferCloser()
   178  	decarg := &engine.SaltpackDecryptArg{
   179  		Source: strings.NewReader(out),
   180  		Sink:   decoded,
   181  	}
   182  	dec1 := engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m1))
   183  	if err := engine.RunEngine2(m1, dec1); err != nil {
   184  		t.Fatal(err)
   185  	}
   186  	decmsg := decoded.String()
   187  	require.Equal(t, msg, decmsg)
   188  
   189  	// u2 can decrypt
   190  	uis2 := libkb.UIs{IdentifyUI: trackUI, SecretUI: u2.newSecretUI(), SaltpackUI: fakeSaltpackUI{}}
   191  	m2 := libkb.NewMetaContextForTest(*u2.tc).WithUIs(uis2)
   192  
   193  	decoded2 := libkb.NewBufferCloser()
   194  	decarg = &engine.SaltpackDecryptArg{
   195  		Source: strings.NewReader(out),
   196  		Sink:   decoded2,
   197  	}
   198  	dec2 := engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m2))
   199  	if err := engine.RunEngine2(m2, dec2); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	decmsg = decoded2.String()
   203  	require.Equal(t, msg, decmsg)
   204  
   205  	// u1 leaves team, can't decrypt
   206  	u1.leave(team)
   207  	teams := u1.teamList("", false, false)
   208  	require.Len(t, teams.Teams, 0)
   209  	decoded3 := libkb.NewBufferCloser()
   210  	decarg = &engine.SaltpackDecryptArg{
   211  		Source: strings.NewReader(out),
   212  		Sink:   decoded3,
   213  	}
   214  	dec1 = engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m1))
   215  	err := engine.RunEngine2(m1, dec1)
   216  	require.IsType(t, libkb.DecryptionError{}, err)
   217  	x, _ := err.(libkb.DecryptionError)
   218  	require.IsType(t, libkb.NoDecryptionKeyError{}, x.Cause.Err)
   219  
   220  	// u2 can still decrypt
   221  	teams = u2.teamList("", false, false)
   222  	require.Len(t, teams.Teams, 1)
   223  	decoded4 := libkb.NewBufferCloser()
   224  	decarg = &engine.SaltpackDecryptArg{
   225  		Source: strings.NewReader(out),
   226  		Sink:   decoded4,
   227  	}
   228  	dec2 = engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m2))
   229  	if err := engine.RunEngine2(m2, dec2); err != nil {
   230  		t.Fatal(err)
   231  	}
   232  	decmsg = decoded4.String()
   233  	require.Equal(t, msg, decmsg)
   234  
   235  	// u2 adds u3, can decrypt
   236  	u2.addTeamMember(team, u3.username, keybase1.TeamRole_WRITER)
   237  	uis3 := libkb.UIs{IdentifyUI: trackUI, SecretUI: u3.newSecretUI(), SaltpackUI: fakeSaltpackUI{}}
   238  	m3 := libkb.NewMetaContextForTest(*u3.tc).WithUIs(uis3)
   239  
   240  	decoded5 := libkb.NewBufferCloser()
   241  	decarg = &engine.SaltpackDecryptArg{
   242  		Source: strings.NewReader(out),
   243  		Sink:   decoded5,
   244  	}
   245  	dec3 := engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m3))
   246  	if err := engine.RunEngine2(m3, dec3); err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	decmsg = decoded5.String()
   250  	require.Equal(t, msg, decmsg)
   251  }
   252  
   253  func TestSaltpackEncryptDecryptForImplicitTeams(t *testing.T) {
   254  	tt := newTeamTester(t)
   255  	defer tt.cleanup()
   256  
   257  	// u1 will send a message to u2@rooter (before u2 proves rooter)
   258  	u1 := tt.addUser("u1sp")
   259  	u2 := tt.addUser("u2sp")
   260  
   261  	msg := "this message will be encrypted for an implicit team"
   262  
   263  	trackUI := &kbtest.FakeIdentifyUI{
   264  		Proofs: make(map[string]string),
   265  	}
   266  	uis1 := libkb.UIs{IdentifyUI: trackUI, SecretUI: u1.newSecretUI()}
   267  
   268  	sink := libkb.NewBufferCloser()
   269  	arg := &engine.SaltpackEncryptArg{
   270  		Opts: keybase1.SaltpackEncryptOptions{
   271  			Recipients:    []string{(u2.username + "@rooter")},
   272  			UseEntityKeys: true,
   273  		},
   274  		Source: strings.NewReader(msg),
   275  		Sink:   sink,
   276  	}
   277  
   278  	eng := engine.NewSaltpackEncrypt(arg, saltpackkeys.NewSaltpackRecipientKeyfinderEngineAsInterface)
   279  	m1 := libkb.NewMetaContextForTest(*u1.tc).WithUIs(uis1)
   280  	if err := engine.RunEngine2(m1, eng); err != nil {
   281  		t.Fatal(err)
   282  	}
   283  
   284  	out := sink.String()
   285  	if len(out) == 0 {
   286  		t.Fatal("no output")
   287  	}
   288  
   289  	t.Logf("encrypted data: %s", out)
   290  
   291  	// u2 has not proven rooter yet, they should not be able to decrypt
   292  	uis2 := libkb.UIs{IdentifyUI: trackUI, SecretUI: u2.newSecretUI(), SaltpackUI: fakeSaltpackUI{}}
   293  	m2 := libkb.NewMetaContextForTest(*u2.tc).WithUIs(uis2)
   294  
   295  	decoded := libkb.NewBufferCloser()
   296  	decarg := &engine.SaltpackDecryptArg{
   297  		Source: strings.NewReader(out),
   298  		Sink:   decoded,
   299  	}
   300  	dec := engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m2))
   301  	err := engine.RunEngine2(m2, dec)
   302  	require.IsType(t, libkb.DecryptionError{}, err)
   303  	x, _ := err.(libkb.DecryptionError)
   304  	require.IsType(t, libkb.NoDecryptionKeyError{}, x.Cause.Err)
   305  
   306  	// Get current implicit team seqno so we can wait for it to be updated later
   307  	team, _, _, err := teams.LookupImplicitTeam(context.Background(), u1.tc.G, u1.username+","+u2.username+"@rooter", false, teams.ImplicitTeamOptions{})
   308  	require.NoError(t, err)
   309  	nextSeqno := team.NextSeqno()
   310  
   311  	u2.proveRooter()
   312  
   313  	// Wait for u1 to add u2 to the implicit team
   314  	u2.kickTeamRekeyd()
   315  	u2.waitForTeamChangedGregor(team.ID, nextSeqno)
   316  
   317  	// Now decryption should succeed
   318  	decoded = libkb.NewBufferCloser()
   319  	decarg = &engine.SaltpackDecryptArg{
   320  		Source: strings.NewReader(out),
   321  		Sink:   decoded,
   322  	}
   323  	dec = engine.NewSaltpackDecrypt(decarg, saltpackkeys.NewKeyPseudonymResolver(m2))
   324  	if err := engine.RunEngine2(m2, dec); err != nil {
   325  		t.Fatal(err)
   326  	}
   327  	decmsg := decoded.String()
   328  	require.Equal(t, msg, decmsg)
   329  }