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

import (
4
	"encoding/json"
5
	"errors"
Sietse Ringers's avatar
Sietse Ringers committed
6

Sietse Ringers's avatar
Sietse Ringers committed
7
8
9
	"os"
	"testing"

10
	"github.com/privacybydesign/gabi"
11
12
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
13
	"github.com/privacybydesign/irmago/internal/test"
14
	"github.com/stretchr/testify/assert"
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
			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 verifyKeyshareIsUnmarshaled(t *testing.T, client *Client) {
	require.NotNil(t, client.keyshareServers)
85
86
87
	testManager := irma.NewSchemeManagerIdentifier("test")
	require.Contains(t, client.keyshareServers, testManager)
	kss := client.keyshareServers[testManager]
Sietse Ringers's avatar
Sietse Ringers committed
88
89
90
	require.NotEmpty(t, kss.Nonce)
}

91
func TestStorageDeserialization(t *testing.T) {
Sietse Ringers's avatar
Sietse Ringers committed
92
	client := parseStorage(t)
93
94
	defer test.ClearTestStorage(t)

Sietse Ringers's avatar
Sietse Ringers committed
95
	verifyClientIsUnmarshaled(t, client)
Sietse Ringers's avatar
Sietse Ringers committed
96
97
98
99
	verifyCredentials(t, client)
	verifyKeyshareIsUnmarshaled(t, client)
}

100
101
102
// 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
103
104
func TestCandidates(t *testing.T) {
	client := parseStorage(t)
105
	defer test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
106

107
	// client contains one instance of the studentCard credential, whose studentID attribute is 456.
108
	attrtype := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
109
110

	// If the disjunction contains no required values at all, then our attribute is a candidate
111
112
	disjunction := irma.AttributeDisCon{
		irma.AttributeCon{irma.AttributeRequest{Type: attrtype}},
Sietse Ringers's avatar
Sietse Ringers committed
113
	}
114
115
	attrs, missing := client.Candidates(disjunction)
	require.Empty(t, missing)
Sietse Ringers's avatar
Sietse Ringers committed
116
117
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)
118
	require.NotNil(t, attrs[0])
119
	require.Equal(t, attrs[0][0].Type, attrtype)
Sietse Ringers's avatar
Sietse Ringers committed
120

121
122
123
	// If the disjunction requires our attribute to have 456 as value, which it does,
	// then our attribute is a candidate
	reqval := "456"
124
125
126
	disjunction[0][0].Value = &reqval
	attrs, missing = client.Candidates(disjunction)
	require.Empty(t, missing)
Sietse Ringers's avatar
Sietse Ringers committed
127
128
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)
129
	require.NotNil(t, attrs[0])
130
	require.Equal(t, attrs[0][0].Type, attrtype)
Sietse Ringers's avatar
Sietse Ringers committed
131

132
133
134
	// If the disjunction requires our attribute to have a different value than it does,
	// then it is NOT a match.
	reqval = "foobarbaz"
135
136
137
	disjunction[0][0].Value = &reqval
	attrs, missing = client.Candidates(disjunction)
	require.NotEmpty(t, missing)
138
139
140
141
	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
142
143
144
	disjunction[0][0].Value = nil
	attrs, missing = client.Candidates(disjunction)
	require.Empty(t, missing)
Sietse Ringers's avatar
Sietse Ringers committed
145
	require.NotNil(t, attrs)
146
147
	require.Len(t, attrs, 1)
	require.NotNil(t, attrs[0])
148
	require.Equal(t, attrs[0][0].Type, attrtype)
149

150
151
152
153
	// Require an attribute we do not have
	disjunction[0][0] = irma.NewAttributeRequest("irma-demo.MijnOverheid.ageLower.over12")
	attrs, missing = client.Candidates(disjunction)
	require.NotEmpty(t, missing)
Sietse Ringers's avatar
Sietse Ringers committed
154
155
156
	require.Empty(t, attrs)
}

157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
func TestCandidateConjunctionOrder(t *testing.T) {
	client := parseStorage(t)

	j := `[
	  [
	    [
	      "irma-demo.RU.studentCard.level",
	      "test.test.mijnirma.email"
	    ]
	  ]
	]`

	cdc := irma.AttributeConDisCon{}
	require.NoError(t, json.Unmarshal([]byte(j), &cdc))
	assert.Equal(t,
		"irma-demo.RU.studentCard.level",
		cdc[0][0][0].Type.String(),
	)

	for i := 1; i < 20; i++ {
		candidates, missing := client.CheckSatisfiability(cdc)
		require.Empty(t, missing)
		require.Equal(t, "irma-demo.RU.studentCard.level", candidates[0][0][0].Type.String())
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
183
184
func TestCredentialRemoval(t *testing.T) {
	client := parseStorage(t)
185
	defer test.ClearTestStorage(t)
186

187
188
	id := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	id2 := irma.NewCredentialTypeIdentifier("test.test.mijnirma")
Sietse Ringers's avatar
Sietse Ringers committed
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208

	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)
}

209
210
func TestWrongSchemeManager(t *testing.T) {
	client := parseStorage(t)
211
	defer test.ClearTestStorage(t)
212
213
214

	irmademo := irma.NewSchemeManagerIdentifier("irma-demo")
	require.Contains(t, client.Configuration.SchemeManagers, irmademo)
215
	require.NoError(t, os.Remove("../testdata/storage/test/irma_configuration/irma-demo/index"))
216
217
218
219
220

	err := client.Configuration.ParseFolder()
	_, ok := err.(*irma.SchemeManagerError)
	require.True(t, ok)
	require.Contains(t, client.Configuration.DisabledSchemeManagers, irmademo)
221
222
223
224
225
	require.Contains(t, client.Configuration.SchemeManagers, irmademo)
	require.NotEqual(t,
		client.Configuration.SchemeManagers[irmademo].Status,
		irma.SchemeManagerStatusValid,
	)
226
227
}

228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
func TestCredentialInfoListNewAttribute(t *testing.T) {
	client := parseStorage(t)
	defer test.ClearTestStorage(t)

	schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute")

	client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
	require.NoError(t, client.Configuration.UpdateSchemeManager(schemeid, nil))
	require.NoError(t, client.Configuration.ParseFolder())
	require.NotNil(t, client.Configuration.CredentialTypes[credid].AttributeType(attrid))

	// irma-demo.RU.studentCard.newAttribute now exists in the scheme but not in the instance in the teststorage
	for _, credinfo := range client.CredentialInfoList() {
		if credinfo.ID == "studentCard" {
			require.Nil(t, credinfo.Attributes[attrid])
			require.NotEmpty(t, credinfo.Attributes[irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level")])
			return
		}
	}
	require.Fail(t, "studentCard credential not found")
}

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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
// ------

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)
	}
}