irmaclient_test.go 10.1 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
2
3
package irmaclient

import (
4
	"encoding/json"
5
	"errors"
6
	gobig "math/big"
Sietse Ringers's avatar
Sietse Ringers committed
7
8
9
	"os"
	"testing"

10
11
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
12
13
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
14
	"github.com/privacybydesign/irmago/internal/test"
Sietse Ringers's avatar
Sietse Ringers committed
15
16
17
18
	"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
19
	// Create HTTP server for scheme managers
20
	test.StartSchemeManagerHttpServer()
21
	defer test.StopSchemeManagerHttpServer()
22

23
	test.CreateTestStorage(nil)
24
	defer test.ClearTestStorage(nil)
25

26
	os.Exit(m.Run())
Sietse Ringers's avatar
Sietse Ringers committed
27
28
29
}

func parseStorage(t *testing.T) *Client {
30
	test.SetupTestStorage(t)
31
	require.NoError(t, fs.CopyDirectory("../testdata/teststorage", "../testdata/storage/test"))
32
	client, err := New(
33
34
		"../testdata/storage/test",
		"../testdata/irma_configuration",
35
		"",
36
		&TestClientHandler{t: t},
Sietse Ringers's avatar
Sietse Ringers committed
37
38
	)
	require.NoError(t, err)
39
	return client
Sietse Ringers's avatar
Sietse Ringers committed
40
41
}

Sietse Ringers's avatar
Sietse Ringers committed
42
func verifyClientIsUnmarshaled(t *testing.T, client *Client) {
43
	cred, err := client.credential(irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
Sietse Ringers's avatar
Sietse Ringers committed
44
45
46
47
	require.NoError(t, err, "could not fetch credential")
	require.NotNil(t, cred, "Credential should exist")
	require.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")

48
	cred, err = client.credential(irma.NewCredentialTypeIdentifier("test.test.mijnirma"), 0)
Sietse Ringers's avatar
Sietse Ringers committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
	require.NoError(t, err, "could not fetch credential")
	require.NotNil(t, cred, "Credential should exist")
	require.NotNil(t, cred.Signature.KeyshareP)

	require.NotEmpty(t, client.CredentialInfoList())

	pk, err := cred.PublicKey()
	require.NoError(t, err)
	require.True(t,
		cred.Signature.Verify(pk, cred.Attributes),
		"Credential should be valid",
	)
}

func verifyCredentials(t *testing.T, client *Client) {
	var pk *gabi.PublicKey
65
66
67
68
	for credtype, credsmap := range client.attributes {
		for index, attrs := range credsmap {
			cred, err := client.credential(attrs.CredentialType().Identifier(), index)
			require.NoError(t, err)
Sietse Ringers's avatar
Sietse Ringers committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
			pk, err = cred.PublicKey()
			require.NoError(t, err)
			require.True(t,
				cred.Credential.Signature.Verify(pk, cred.Attributes),
				"Credential %s-%d was invalid", credtype.String(), index,
			)
			require.Equal(t, cred.Attributes[0], client.secretkey.Key,
				"Secret key of credential %s-%d unequal to main secret key",
				cred.CredentialType().Identifier().String(), index,
			)
		}
	}
}

func verifyPaillierKey(t *testing.T, PrivateKey *paillierPrivateKey) {
	require.NotNil(t, PrivateKey)
	require.NotNil(t, PrivateKey.L)
	require.NotNil(t, PrivateKey.U)
	require.NotNil(t, PrivateKey.PublicKey.N)

89
90
	require.Equal(t, gobig.NewInt(1), new(gobig.Int).Exp(gobig.NewInt(2), PrivateKey.L, PrivateKey.N))
	require.Equal(t, PrivateKey.NSquared, new(gobig.Int).Exp(PrivateKey.N, gobig.NewInt(2), nil))
Sietse Ringers's avatar
Sietse Ringers committed
91
92
93
94
95
96
97
98
99
100
101
102

	plaintext := "Hello Paillier!"
	ciphertext, err := PrivateKey.Encrypt([]byte(plaintext))
	require.NoError(t, err)
	decrypted, err := PrivateKey.Decrypt(ciphertext)
	require.NoError(t, err)
	require.Equal(t, plaintext, string(decrypted))
}

func verifyKeyshareIsUnmarshaled(t *testing.T, client *Client) {
	require.NotNil(t, client.paillierKeyCache)
	require.NotNil(t, client.keyshareServers)
103
104
105
	testManager := irma.NewSchemeManagerIdentifier("test")
	require.Contains(t, client.keyshareServers, testManager)
	kss := client.keyshareServers[testManager]
Sietse Ringers's avatar
Sietse Ringers committed
106
107
108
109
110
111
	require.NotEmpty(t, kss.Nonce)

	verifyPaillierKey(t, kss.PrivateKey)
	verifyPaillierKey(t, client.paillierKeyCache)
}

112
func TestStorageDeserialization(t *testing.T) {
Sietse Ringers's avatar
Sietse Ringers committed
113
	client := parseStorage(t)
114
115
	defer test.ClearTestStorage(t)

Sietse Ringers's avatar
Sietse Ringers committed
116
	verifyClientIsUnmarshaled(t, client)
Sietse Ringers's avatar
Sietse Ringers committed
117
118
119
120
	verifyCredentials(t, client)
	verifyKeyshareIsUnmarshaled(t, client)
}

121
122
123
// TestCandidates tests the correctness of the function of the client that, given a disjunction of attributes
// requested by the verifier, calculates a list of candidate attributes contained by the client that would
// satisfy the attribute disjunction.
Sietse Ringers's avatar
Sietse Ringers committed
124
125
func TestCandidates(t *testing.T) {
	client := parseStorage(t)
126
	defer test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
127

128
	// client contains one instance of the studentCard credential, whose studentID attribute is 456.
129
	attrtype := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
130
131

	// If the disjunction contains no required values at all, then our attribute is a candidate
132
133
	disjunction := &irma.AttributeDisjunction{
		Attributes: []irma.AttributeTypeIdentifier{attrtype},
Sietse Ringers's avatar
Sietse Ringers committed
134
135
136
137
	}
	attrs := client.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)
138
139
	require.NotNil(t, attrs[0])
	require.Equal(t, attrs[0].Type, attrtype)
Sietse Ringers's avatar
Sietse Ringers committed
140

141
142
143
	// If the disjunction requires our attribute to have 456 as value, which it does,
	// then our attribute is a candidate
	reqval := "456"
144
145
	disjunction = &irma.AttributeDisjunction{
		Attributes: []irma.AttributeTypeIdentifier{attrtype},
146
		Values:     map[irma.AttributeTypeIdentifier]*string{attrtype: &reqval},
Sietse Ringers's avatar
Sietse Ringers committed
147
148
149
150
	}
	attrs = client.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)
151
152
	require.NotNil(t, attrs[0])
	require.Equal(t, attrs[0].Type, attrtype)
Sietse Ringers's avatar
Sietse Ringers committed
153

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
	// If the disjunction requires our attribute to have a different value than it does,
	// then it is NOT a match.
	reqval = "foobarbaz"
	disjunction.Values[attrtype] = &reqval
	attrs = client.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Empty(t, attrs)

	// A required value of nil counts as no requirement on the value, so our attribute is a candidate
	disjunction.Values[attrtype] = nil
	attrs = client.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)
	require.NotNil(t, attrs[0])
	require.Equal(t, attrs[0].Type, attrtype)

	// This test should be equivalent to the one above
	disjunction = &irma.AttributeDisjunction{}
	json.Unmarshal([]byte(`{"attributes":{"irma-demo.RU.studentCard.studentID":null}}`), &disjunction)
Sietse Ringers's avatar
Sietse Ringers committed
173
174
	attrs = client.Candidates(disjunction)
	require.NotNil(t, attrs)
175
176
177
178
179
180
181
182
183
	require.Len(t, attrs, 1)
	require.NotNil(t, attrs[0])
	require.Equal(t, attrs[0].Type, attrtype)

	// A required value of null counts as no requirement on the value, but we must still satisfy the disjunction
	// We do not have an instance of this attribute so we have no candidate
	disjunction = &irma.AttributeDisjunction{}
	json.Unmarshal([]byte(`{"attributes":{"irma-demo.MijnOverheid.ageLower.over12":null}}`), &disjunction)
	attrs = client.Candidates(disjunction)
Sietse Ringers's avatar
Sietse Ringers committed
184
185
186
187
188
	require.Empty(t, attrs)
}

func TestPaillier(t *testing.T) {
	client := parseStorage(t)
189
	defer test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204

	challenge, _ := gabi.RandomBigInt(256)
	comm, _ := gabi.RandomBigInt(1000)
	resp, _ := gabi.RandomBigInt(1000)

	sk := client.paillierKey(true)
	bytes, err := sk.Encrypt(challenge.Bytes())
	require.NoError(t, err)
	cipher := new(big.Int).SetBytes(bytes)

	bytes, err = sk.Encrypt(comm.Bytes())
	require.NoError(t, err)
	commcipher := new(big.Int).SetBytes(bytes)

	// [[ c ]]^resp * [[ comm ]]
205
206
	nsquared := big.Convert(sk.NSquared)
	cipher.Exp(cipher, resp, nsquared).Mul(cipher, commcipher).Mod(cipher, nsquared)
Sietse Ringers's avatar
Sietse Ringers committed
207
208
209
210
211
212
213
214
215
216
217
218

	bytes, err = sk.Decrypt(cipher.Bytes())
	require.NoError(t, err)
	plaintext := new(big.Int).SetBytes(bytes)
	expected := new(big.Int).Set(challenge)
	expected.Mul(expected, resp).Add(expected, comm)

	require.Equal(t, plaintext, expected)
}

func TestCredentialRemoval(t *testing.T) {
	client := parseStorage(t)
219
	defer test.ClearTestStorage(t)
220

221
222
	id := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	id2 := irma.NewCredentialTypeIdentifier("test.test.mijnirma")
Sietse Ringers's avatar
Sietse Ringers committed
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242

	cred, err := client.credential(id, 0)
	require.NoError(t, err)
	require.NotNil(t, cred)
	err = client.RemoveCredentialByHash(cred.AttributeList().Hash())
	require.NoError(t, err)
	cred, err = client.credential(id, 0)
	require.NoError(t, err)
	require.Nil(t, cred)

	cred, err = client.credential(id2, 0)
	require.NoError(t, err)
	require.NotNil(t, cred)
	err = client.RemoveCredential(id2, 0)
	require.NoError(t, err)
	cred, err = client.credential(id2, 0)
	require.NoError(t, err)
	require.Nil(t, cred)
}

243
244
func TestWrongSchemeManager(t *testing.T) {
	client := parseStorage(t)
245
	defer test.ClearTestStorage(t)
246
247
248

	irmademo := irma.NewSchemeManagerIdentifier("irma-demo")
	require.Contains(t, client.Configuration.SchemeManagers, irmademo)
249
	require.NoError(t, os.Remove("../testdata/storage/test/irma_configuration/irma-demo/index"))
250
251
252
253
254

	err := client.Configuration.ParseFolder()
	_, ok := err.(*irma.SchemeManagerError)
	require.True(t, ok)
	require.Contains(t, client.Configuration.DisabledSchemeManagers, irmademo)
255
256
257
258
259
	require.Contains(t, client.Configuration.SchemeManagers, irmademo)
	require.NotEqual(t,
		client.Configuration.SchemeManagers[irmademo].Status,
		irma.SchemeManagerStatusValid,
	)
260
261
}

262
263
// Test pinchange interaction
func TestKeyshareChangePin(t *testing.T) {
264
	client := parseStorage(t)
265
	defer test.ClearTestStorage(t)
266

267
268
	require.NoError(t, client.keyshareChangePinWorker(irma.NewSchemeManagerIdentifier("test"), "12345", "54321"))
	require.NoError(t, client.keyshareChangePinWorker(irma.NewSchemeManagerIdentifier("test"), "54321", "12345"))
Sietse Ringers's avatar
Sietse Ringers committed
269
}
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321

// ------

type TestClientHandler struct {
	t *testing.T
	c chan error
}

func (i *TestClientHandler) UpdateConfiguration(new *irma.IrmaIdentifierSet) {}
func (i *TestClientHandler) UpdateAttributes()                               {}
func (i *TestClientHandler) EnrollmentSuccess(manager irma.SchemeManagerIdentifier) {
	select {
	case i.c <- nil: // nop
	default: // nop
	}
}
func (i *TestClientHandler) EnrollmentFailure(manager irma.SchemeManagerIdentifier, err error) {
	select {
	case i.c <- err: // nop
	default:
		i.t.Fatal(err)
	}
}
func (i *TestClientHandler) ChangePinSuccess(manager irma.SchemeManagerIdentifier) {
	select {
	case i.c <- nil: // nop
	default: // nop
	}
}
func (i *TestClientHandler) ChangePinFailure(manager irma.SchemeManagerIdentifier, err error) {
	select {
	case i.c <- err: //nop
	default:
		i.t.Fatal(err)
	}
}
func (i *TestClientHandler) ChangePinIncorrect(manager irma.SchemeManagerIdentifier, attempts int) {
	err := errors.New("incorrect pin")
	select {
	case i.c <- err: //nop
	default:
		i.t.Fatal(err)
	}
}
func (i *TestClientHandler) ChangePinBlocked(manager irma.SchemeManagerIdentifier, timeout int) {
	err := errors.New("blocked account")
	select {
	case i.c <- err: //nop
	default:
		i.t.Fatal(err)
	}
}