1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"crypto/rand"
8	"fmt"
9	"sync"
10	"testing"
11
12	"golang.org/x/net/context"
13
14	"github.com/keybase/client/go/kex2"
15	"github.com/keybase/client/go/libkb"
16	keybase1 "github.com/keybase/client/go/protocol/keybase1"
17	"github.com/stretchr/testify/require"
18)
19
20func TestDeviceAdd(t *testing.T) {
21	testDeviceAdd(t, false)
22}
23
24func TestDeviceAddPUK(t *testing.T) {
25	testDeviceAdd(t, true)
26}
27
28func runDeviceAddTest(t *testing.T, wg *sync.WaitGroup, tcY *libkb.TestContext, secretY kex2.Secret,
29	uid keybase1.UID) {
30	defer wg.Done()
31	err := (func() error {
32		uis := libkb.UIs{
33			ProvisionUI: &testProvisionUI{secretCh: make(chan kex2.Secret, 1)},
34		}
35		m := NewMetaContextForTest(*tcY).WithUIs(uis).WithNewProvisionalLoginContext()
36		deviceID, err := libkb.NewDeviceID()
37		if err != nil {
38			return err
39		}
40		suffix, err := libkb.RandBytes(5)
41		if err != nil {
42			return err
43		}
44		dname := fmt.Sprintf("device_%x", suffix)
45		device := &libkb.Device{
46			ID:          deviceID,
47			Description: &dname,
48			Type:        keybase1.DeviceTypeV2_DESKTOP,
49		}
50		provisionee := NewKex2Provisionee(tcY.G, device, secretY, uid, fakeSalt())
51		return RunEngine2(m, provisionee)
52	})()
53	require.NoError(t, err, "kex2 provisionee")
54}
55
56func testDeviceAdd(t *testing.T, upgradePerUserKey bool) {
57	// device X (provisioner) context:
58	tcX := SetupEngineTest(t, "kex2provision")
59	defer tcX.Cleanup()
60	tcX.Tp.DisableUpgradePerUserKey = !upgradePerUserKey
61
62	// device Y (provisionee) context:
63	tcY := SetupEngineTest(t, "template")
64	defer tcY.Cleanup()
65	tcY.Tp.DisableUpgradePerUserKey = !upgradePerUserKey
66
67	// provisioner needs to be logged in
68	userX := CreateAndSignupFakeUser(tcX, "login")
69
70	var secretY kex2.Secret
71	if _, err := rand.Read(secretY[:]); err != nil {
72		t.Fatal(err)
73	}
74
75	var wg sync.WaitGroup
76
77	// start provisionee
78	wg.Add(1)
79	go runDeviceAddTest(t, &wg, &tcY, secretY, userX.UID())
80
81	// run DeviceAdd engine on device X
82	uis := libkb.UIs{
83		SecretUI:    userX.NewSecretUI(),
84		ProvisionUI: &testXProvisionUI{secret: secretY},
85	}
86	eng := NewDeviceAdd(tcX.G)
87	m := NewMetaContextForTest(tcX).WithUIs(uis)
88	if err := RunEngine2(m, eng); err != nil {
89		t.Errorf("device add error: %s", err)
90	}
91
92	wg.Wait()
93}
94
95func TestDeviceAddPhraseLegacyDesktop(t *testing.T) {
96	testDeviceAddPhrase(t, libkb.Kex2SecretTypeV1Desktop)
97}
98
99func TestDeviceAddPhraseLegacyMobile(t *testing.T) {
100	testDeviceAddPhrase(t, libkb.Kex2SecretTypeV1Mobile)
101}
102
103func TestDeviceAddPhraseV2(t *testing.T) {
104	testDeviceAddPhrase(t, libkb.Kex2SecretTypeV2)
105}
106
107func testDeviceAddPhrase(t *testing.T, typ libkb.Kex2SecretType) {
108
109	// device X (provisioner) context:
110	tcX := SetupEngineTest(t, "kex2provision")
111	defer tcX.Cleanup()
112
113	// device Y (provisionee) context:
114	tcY := SetupEngineTest(t, "template")
115	defer tcY.Cleanup()
116
117	// provisioner needs to be logged in
118	userX := CreateAndSignupFakeUser(tcX, "login")
119
120	secretY, err := libkb.NewKex2SecretFromTypeAndUID(typ, userX.UID())
121	if err != nil {
122		t.Fatal(err)
123	}
124
125	var wg sync.WaitGroup
126
127	// start provisionee
128	wg.Add(1)
129	go runDeviceAddTest(t, &wg, &tcY, secretY.Secret(), userX.UID())
130
131	// run DeviceAdd engine on device X
132	uis := libkb.UIs{
133		SecretUI:    userX.NewSecretUI(),
134		ProvisionUI: &testPhraseProvisionUI{phrase: secretY.Phrase()},
135	}
136	eng := NewDeviceAdd(tcX.G)
137	m := NewMetaContextForTest(tcX).WithUIs(uis)
138	if err := RunEngine2(m, eng); err != nil {
139		t.Errorf("device add error: %s", err)
140	}
141
142	wg.Wait()
143}
144
145func TestDeviceAddStoredSecret(t *testing.T) {
146	// device X (provisioner) context:
147	tcX := SetupEngineTest(t, "kex2provision")
148	defer tcX.Cleanup()
149
150	// device Y (provisionee) context:
151	tcY := SetupEngineTest(t, "template")
152	defer tcY.Cleanup()
153
154	// provisioner needs to be logged in
155	userX := SignupFakeUserStoreSecret(tcX, "login")
156
157	var secretY kex2.Secret
158	if _, err := rand.Read(secretY[:]); err != nil {
159		t.Fatal(err)
160	}
161
162	var wg sync.WaitGroup
163
164	// start provisionee
165	wg.Add(1)
166	go runDeviceAddTest(t, &wg, &tcY, secretY, userX.UID())
167
168	testSecretUI := userX.NewSecretUI()
169
170	// run DeviceAdd engine on device X
171	uis := libkb.UIs{
172		SecretUI:    testSecretUI,
173		ProvisionUI: &testXProvisionUI{secret: secretY},
174	}
175	eng := NewDeviceAdd(tcX.G)
176	m := NewMetaContextForTest(tcX).WithUIs(uis)
177	if err := RunEngine2(m, eng); err != nil {
178		t.Errorf("device add error: %s", err)
179	}
180
181	wg.Wait()
182
183	if testSecretUI.CalledGetPassphrase {
184		t.Fatal("GetPassphrase() unexpectedly called")
185	}
186}
187
188type testXProvisionUI struct {
189	secret kex2.Secret
190	testProvisionUI
191}
192
193func (u *testXProvisionUI) DisplayAndPromptSecret(_ context.Context, arg keybase1.DisplayAndPromptSecretArg) (keybase1.SecretResponse, error) {
194	return keybase1.SecretResponse{Secret: u.secret[:]}, nil
195}
196
197type testPhraseProvisionUI struct {
198	phrase string
199	testProvisionUI
200}
201
202func (u *testPhraseProvisionUI) DisplayAndPromptSecret(_ context.Context, arg keybase1.DisplayAndPromptSecretArg) (keybase1.SecretResponse, error) {
203	return keybase1.SecretResponse{Phrase: u.phrase}, nil
204}
205