session_test.go 17 KB
Newer Older
1
package sessiontest
Sietse Ringers's avatar
Sietse Ringers committed
2
3

import (
4
	"context"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"encoding/json"
6
7
	"net/http"
	"path/filepath"
8
	"reflect"
Sietse Ringers's avatar
Sietse Ringers committed
9
	"testing"
10
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
11

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

func TestSigningSession(t *testing.T) {
20
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
21
22
	request := getSigningRequest(id)
	sessionHelper(t, request, "signature", nil)
Sietse Ringers's avatar
Sietse Ringers committed
23
24
25
}

func TestDisclosureSession(t *testing.T) {
26
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
27
28
	request := getDisclosureRequest(id)
	sessionHelper(t, request, "verification", nil)
Sietse Ringers's avatar
Sietse Ringers committed
29
30
}

31
32
func TestNoAttributeDisclosureSession(t *testing.T) {
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard")
Sietse Ringers's avatar
Sietse Ringers committed
33
34
	request := getDisclosureRequest(id)
	sessionHelper(t, request, "verification", nil)
35
36
}

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
func TestEmptyDisclosure(t *testing.T) {
	// Disclosure request asking for an attribute value that the client doesn't have,
	// and an empty conjunction as first option, which is always chosen by the test session handler
	val := "client doesn't have this attr"
	request := irma.NewDisclosureRequest()
	request.Disclose = irma.AttributeConDisCon{
		irma.AttributeDisCon{
			irma.AttributeCon{},
			irma.AttributeCon{{Type: irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level"), Value: &val}},
		},
	}

	res := requestorSessionHelper(t, request, nil)
	require.Nil(t, res.Err)
	require.NotNil(t, res.SessionResult)
	require.NotEmpty(t, res.SessionResult.Disclosed) // The outer conjunction was satisfied
	require.Empty(t, res.SessionResult.Disclosed[0]) // by the empty set, so we get no attributes
}

Sietse Ringers's avatar
Sietse Ringers committed
56
func TestIssuanceSession(t *testing.T) {
57
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
58
59
	request := getCombinedIssuanceRequest(id)
	sessionHelper(t, request, "issue", nil)
Sietse Ringers's avatar
Sietse Ringers committed
60
61
}

62
63
64
65
66
func TestMultipleIssuanceSession(t *testing.T) {
	request := getMultipleIssuanceRequest()
	sessionHelper(t, request, "issue", nil)
}

67
func TestDefaultCredentialValidity(t *testing.T) {
68
	client, _ := parseStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
69
70
	request := getIssuanceRequest(true)
	sessionHelper(t, request, "issue", client)
71
72
}

73
74
75
76
func TestIssuanceDisclosureEmptyAttributes(t *testing.T) {
	client, _ := parseStorage(t)
	defer test.ClearTestStorage(t)

77
	req := getNameIssuanceRequest()
78
79
80
81
82
83
84
	sessionHelper(t, req, "issue", client)

	// Test disclosing our null attribute
	req2 := getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.MijnOverheid.fullName.prefix"))
	res := requestorSessionHelper(t, req2, client)
	require.Nil(t, res.Err)
	require.Nil(t, res.Disclosed[0][0].RawValue)
85
86
87
88
89
}

func TestIssuanceOptionalZeroLengthAttributes(t *testing.T) {
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = ""
Sietse Ringers's avatar
Sietse Ringers committed
90
	sessionHelper(t, req, "issue", nil)
91
92
93
}

func TestIssuanceOptionalSetAttributes(t *testing.T) {
94
95
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = "van"
Sietse Ringers's avatar
Sietse Ringers committed
96
	sessionHelper(t, req, "issue", nil)
97
98
}

99
func TestLargeAttribute(t *testing.T) {
100
	client, _ := parseStorage(t)
101
102
	defer test.ClearTestStorage(t)

103
104
	require.NoError(t, client.RemoveAllCredentials())

Sietse Ringers's avatar
Sietse Ringers committed
105
106
	issuanceRequest := getSpecialIssuanceRequest(false, "1234567890123456789012345678901234567890") // 40 chars
	sessionHelper(t, issuanceRequest, "issue", client)
107

Sietse Ringers's avatar
Sietse Ringers committed
108
109
	disclosureRequest := getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.university"))
	sessionHelper(t, disclosureRequest, "verification", client)
110
111
}

112
func TestIssuanceSingletonCredential(t *testing.T) {
113
	client, _ := parseStorage(t)
114
115
	defer test.ClearTestStorage(t)

116
	request := getMultipleIssuanceRequest()
117
118
	credid := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")

119
	require.Nil(t, client.Attributes(credid, 0))
120

Sietse Ringers's avatar
Sietse Ringers committed
121
	sessionHelper(t, request, "issue", client)
122
123
	require.NotNil(t, client.Attributes(credid, 0))
	require.Nil(t, client.Attributes(credid, 1))
124

Sietse Ringers's avatar
Sietse Ringers committed
125
	sessionHelper(t, request, "issue", client)
126
127
	require.NotNil(t, client.Attributes(credid, 0))
	require.Nil(t, client.Attributes(credid, 1))
128
129
}

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
func TestUnsatisfiableDisclosureSession(t *testing.T) {
	client, _ := parseStorage(t)
	defer test.ClearTestStorage(t)

	request := irma.NewDisclosureRequest()
	request.Disclose = irma.AttributeConDisCon{
		irma.AttributeDisCon{
			irma.AttributeCon{
				irma.NewAttributeRequest("irma-demo.MijnOverheid.root.BSN"),
				irma.NewAttributeRequest("irma-demo.RU.studentCard.level"),
			},
			irma.AttributeCon{
				irma.NewAttributeRequest("test.test.mijnirma.email"),
				irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.firstname"),
				irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.familyname"),
			},
		},
		irma.AttributeDisCon{
			irma.AttributeCon{
				irma.NewAttributeRequest("irma-demo.RU.studentCard.level"),
			},
		},
	}

	missing := irmaclient.MissingAttributes{}
	require.NoError(t, json.Unmarshal([]byte(`{
156
157
		"0": {
			"0": {
158
159
				"0": {"type": "irma-demo.MijnOverheid.root.BSN"}
			},
160
			"1": {
161
162
163
				"1": {"type": "irma-demo.MijnOverheid.fullName.firstname"},
				"2": {"type": "irma-demo.MijnOverheid.fullName.familyname"}
			}
164
		}
165
166
167
168
169
170
171
	}`), &missing))
	require.True(t, reflect.DeepEqual(
		missing,
		requestorSessionHelper(t, request, client, sessionOptionUnsatisfiableRequest).Missing),
	)
}

172
173
174
175
176
177
178
/* There is an annoying difference between how Java and Go convert big integers to and from
byte arrays: in Java the sign of the integer is taken into account, but not in Go. This means
that in Java, when converting a bigint to or from a byte array, the most significant bit
indicates the sign of the integer. In Go this is not the case. This resulted in invalid
signatures being issued in the issuance protocol in two distinct ways, of which we test here
that they have been fixed. */
func TestAttributeByteEncoding(t *testing.T) {
179
	client, _ := parseStorage(t)
180
	defer test.ClearTestStorage(t)
181
182
183
184
185
186
187
	require.NoError(t, client.RemoveAllCredentials())

	/* After bitshifting the presence bit into the large attribute below, the most significant
	bit is 1. In the bigint->[]byte conversion that happens before hashing this attribute, in
	Java this results in an extra 0 byte being prepended in order to have a 0 instead as most
	significant (sign) bit. We test that the Java implementation correctly removes the extraneous
	0 byte. */
Sietse Ringers's avatar
Sietse Ringers committed
188
189
	request := getSpecialIssuanceRequest(false, "a23456789012345678901234567890")
	sessionHelper(t, request, "issue", client)
190
191
192
193
194
195
196

	/* After converting the attribute below to bytes (using UTF8, on which Java and Go do agree),
	the most significant bit of the byte version of this attribute is 1. In the []byte->bigint
	conversion that happens at that point in the Java implementation (bitshifting is done
	afterwards), this results in a negative number in Java and a positive number in Go. We test
	here that the Java correctly prepends a 0 byte just before this conversion in order to get
	the same positive bigint. */
Sietse Ringers's avatar
Sietse Ringers committed
197
198
	request = getSpecialIssuanceRequest(false, "é")
	sessionHelper(t, request, "issue", client)
199
200
}

201
202
203
204
205
206
207
208
209
210
211
212
func TestOutdatedClientIrmaConfiguration(t *testing.T) {
	client, _ := parseStorage(t)
	defer test.ClearTestStorage(t)

	// Remove old studentCard credential from before support for optional attributes, and issue a new one
	require.NoError(t, client.RemoveAllCredentials())
	require.Nil(t, requestorSessionHelper(t, getIssuanceRequest(true), client).Err)

	// client does not have updated irma_configuration with new attribute irma-demo.RU.studentCard.newAttribute,
	// and the server does. Disclose an attribute from this credential. The client implicitly discloses value 0
	// for the new attribute, and the server accepts.
	req := getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level"))
213
	require.Nil(t, requestorSessionHelper(t, req, client, sessionOptionUpdatedIrmaConfiguration).Err)
214
215
}

216
func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) {
217
	client, _ := parseStorage(t)
218
	defer test.ClearTestStorage(t)
219
220
221
222
223
224

	schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute")
	require.False(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))

225
226
227
	// Remove old studentCard credential from before support for optional attributes, and issue a new one
	require.NoError(t, client.RemoveAllCredentials())
	require.Nil(t, requestorSessionHelper(t, getIssuanceRequest(true), client).Err)
228

229
230
231
232
233
	// Trigger downloading the updated irma_configuration using a disclosure request containing the
	// new attribute, and inform the client
	client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
	newAttrRequest := irma.NewDisclosureRequest(attrid)
	downloaded, err := client.Configuration.Download(newAttrRequest)
234
	require.NoError(t, err)
235
236
237
	require.NoError(t, client.ConfigurationUpdated(downloaded))

	// Our new attribute now exists in the configuration
238
	require.True(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
239
240
241
242
243
244
245
246
247

	// Disclose an old attribute (i.e. not newAttribute) to a server with an old configuration
	// Since our client has a new configuration it hides the new attribute that is not yet in the
	// server's configuration. All proofs are however valid as they should be and the server accepts.
	levelRequest := getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level"))
	require.Nil(t, requestorSessionHelper(t, levelRequest, client).Err)

	// Disclose newAttribute to a server with a new configuration. This attribute was added
	// after we received a credential without it, so its value in this credential is 0.
248
	res := requestorSessionHelper(t, newAttrRequest, client, sessionOptionUpdatedIrmaConfiguration)
249
250
	require.Nil(t, res.Err)
	require.Nil(t, res.Disclosed[0][0].RawValue)
251
252
253
}

func TestIssueNewAttributeUpdateSchemeManager(t *testing.T) {
254
	client, _ := parseStorage(t)
255
256
	defer test.ClearTestStorage(t)

257
258
259
260
261
262
263
264
	schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute")
	require.False(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))

	client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
	issuanceRequest := getIssuanceRequest(true)
	issuanceRequest.Credentials[0].Attributes["newAttribute"] = "foobar"
265
266
	_, err := client.Configuration.Download(issuanceRequest)
	require.NoError(t, err)
267
268
269
270
	require.True(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
}

func TestIssueOptionalAttributeUpdateSchemeManager(t *testing.T) {
271
	client, _ := parseStorage(t)
272
273
	defer test.ClearTestStorage(t)

274
275
276
277
278
279
280
281
	schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level")
	require.False(t, client.Configuration.CredentialTypes[credid].AttributeType(attrid).IsOptional())

	client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
	issuanceRequest := getIssuanceRequest(true)
	delete(issuanceRequest.Credentials[0].Attributes, "level")
282
283
	_, err := client.Configuration.Download(issuanceRequest)
	require.NoError(t, err)
284
285
286
	require.True(t, client.Configuration.CredentialTypes[credid].AttributeType(attrid).IsOptional())
}

Sietse Ringers's avatar
Sietse Ringers committed
287
func TestIssueNewCredTypeUpdateSchemeManager(t *testing.T) {
288
	client, _ := parseStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
289
290
291
292
293
294
295
296
	schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")

	delete(client.Configuration.CredentialTypes, credid)
	require.NotContains(t, client.Configuration.CredentialTypes, credid)

	client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
	request := getIssuanceRequest(true)
297
298
	_, err := client.Configuration.Download(request)
	require.NoError(t, err)
Sietse Ringers's avatar
Sietse Ringers committed
299
300
301
302
303
304
305

	require.Contains(t, client.Configuration.CredentialTypes, credid)

	test.ClearTestStorage(t)
}

func TestDisclosureNewCredTypeUpdateSchemeManager(t *testing.T) {
306
	client, _ := parseStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
307
308
309
310
311
312
313
314
	schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level")

	delete(client.Configuration.CredentialTypes, credid)
	require.NotContains(t, client.Configuration.CredentialTypes, credid)

	client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
315
316
317
	request := irma.NewDisclosureRequest(attrid)
	_, err := client.Configuration.Download(request)
	require.NoError(t, err)
Sietse Ringers's avatar
Sietse Ringers committed
318
319
320
	require.Contains(t, client.Configuration.CredentialTypes, credid)

	test.ClearTestStorage(t)
321
322
323
}

func TestDisclosureNonexistingCredTypeUpdateSchemeManager(t *testing.T) {
324
	client, _ := parseStorage(t)
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
	request := irma.NewDisclosureRequest(
		irma.NewAttributeTypeIdentifier("irma-demo.RU.foo.bar"),
		irma.NewAttributeTypeIdentifier("irma-demo.baz.qux.abc"),
	)
	_, err := client.Configuration.Download(request)
	require.Error(t, err)

	expectedErr := &irma.UnknownIdentifierError{
		ErrorType: irma.ErrorUnknownIdentifier,
		Missing: &irma.IrmaIdentifierSet{
			SchemeManagers: map[irma.SchemeManagerIdentifier]struct{}{},
			PublicKeys:     map[irma.IssuerIdentifier][]int{},
			Issuers: map[irma.IssuerIdentifier]struct{}{
				irma.NewIssuerIdentifier("irma-demo.baz"): struct{}{},
			},
			CredentialTypes: map[irma.CredentialTypeIdentifier]struct{}{
				irma.NewCredentialTypeIdentifier("irma-demo.RU.foo"):  struct{}{},
				irma.NewCredentialTypeIdentifier("irma-demo.baz.qux"): struct{}{},
			},
		},
	}
	require.True(t, reflect.DeepEqual(expectedErr, err), "Download() returned incorrect missing identifier set")
Sietse Ringers's avatar
Sietse Ringers committed
347
348
}

349
350
351
352
// Test installing a new scheme manager from a qr, and do a(n issuance) session
// within this manager to test the autmatic downloading of credential definitions,
// issuers, and public keys.
func TestDownloadSchemeManager(t *testing.T) {
353
	client, _ := parseStorage(t)
354
	defer test.ClearTestStorage(t)
355
356
357
358
359
360
361
362
363
364
365
366
367
368

	// Remove irma-demo scheme manager as we need to test adding it
	irmademo := irma.NewSchemeManagerIdentifier("irma-demo")
	require.Contains(t, client.Configuration.SchemeManagers, irmademo)
	require.NoError(t, client.Configuration.RemoveSchemeManager(irmademo, true))
	require.NotContains(t, client.Configuration.SchemeManagers, irmademo)

	// Do an add-scheme-manager-session
	c := make(chan *SessionResult)
	qr, err := json.Marshal(&irma.SchemeManagerRequest{
		Type: irma.ActionSchemeManager,
		URL:  "http://localhost:48681/irma_configuration/irma-demo",
	})
	require.NoError(t, err)
369
	client.NewSession(string(qr), TestHandler{t, c, client, nil})
370
371
372
373
374
375
	if result := <-c; result != nil {
		require.NoError(t, result.Err)
	}
	require.Contains(t, client.Configuration.SchemeManagers, irmademo)

	// Do a session to test downloading of cred types, issuers and keys
Sietse Ringers's avatar
Sietse Ringers committed
376
377
	request := getCombinedIssuanceRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
	sessionHelper(t, request, "issue", client)
378
379
380
381
382

	require.Contains(t, client.Configuration.SchemeManagers, irmademo)
	require.Contains(t, client.Configuration.Issuers, irma.NewIssuerIdentifier("irma-demo.RU"))
	require.Contains(t, client.Configuration.CredentialTypes, irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"))

383
384
	basepath := filepath.Join(test.FindTestdataFolder(t), "storage", "test", "irma_configuration", "irma-demo")
	exists, err := fs.PathExists(filepath.Join(basepath, "description.xml"))
385
386
	require.NoError(t, err)
	require.True(t, exists)
387
	exists, err = fs.PathExists(filepath.Join(basepath, "RU", "description.xml"))
388
389
	require.NoError(t, err)
	require.True(t, exists)
390
	exists, err = fs.PathExists(filepath.Join(basepath, "RU", "Issues", "studentCard", "description.xml"))
391
392
393
	require.NoError(t, err)
	require.True(t, exists)
}
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431

func TestStaticQRSession(t *testing.T) {
	client, _ := parseStorage(t)
	defer test.ClearTestStorage(t)
	StartRequestorServer(JwtServerConfiguration)
	defer StopRequestorServer()

	// start server to receive session result callback after the session
	var received bool
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		received = true
	})
	s := &http.Server{Addr: ":48685", Handler: mux}
	go func() { _ = s.ListenAndServe() }()

	// setup static QR and other variables
	qr := &irma.Qr{
		Type: irma.ActionRedirect,
		URL:  "http://localhost:48682/session/-/static/staticsession",
	}
	bts, err := json.Marshal(qr)
	require.NoError(t, err)
	localhost := "localhost"
	host := irma.NewTranslatedString(&localhost)
	c := make(chan *SessionResult)

	// Perform session
	client.NewSession(string(bts), TestHandler{t, c, client, host})
	if result := <-c; result != nil {
		require.NoError(t, result.Err)
	}

	// give irma server time to post session result to the server started above, and check the call was received
	time.Sleep(200 * time.Millisecond)
	require.NoError(t, s.Shutdown(context.Background()))
	require.True(t, received)
}