session_test.go 16.3 KB
Newer Older
1
package sessiontest
Sietse Ringers's avatar
Sietse Ringers committed
2
3
4
5
6
7

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"testing"
8
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
9

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

17
18
19
20
func init() {
	irma.ForceHttps = false
}

Sietse Ringers's avatar
Sietse Ringers committed
21
22
23
func getDisclosureRequest(id irma.AttributeTypeIdentifier) *irma.DisclosureRequest {
	return &irma.DisclosureRequest{
		BaseRequest: irma.BaseRequest{Type: irma.ActionDisclosing},
24
		Content: irma.AttributeDisjunctionList([]*irma.AttributeDisjunction{{
Sietse Ringers's avatar
Sietse Ringers committed
25
			Label:      "foo",
26
			Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
27
		}}),
Sietse Ringers's avatar
Sietse Ringers committed
28
	}
Sietse Ringers's avatar
Sietse Ringers committed
29
30
}

Sietse Ringers's avatar
Sietse Ringers committed
31
32
func getSigningRequest(id irma.AttributeTypeIdentifier) *irma.SignatureRequest {
	return &irma.SignatureRequest{
33
		Message: "test",
34
		DisclosureRequest: irma.DisclosureRequest{
Sietse Ringers's avatar
Sietse Ringers committed
35
			BaseRequest: irma.BaseRequest{Type: irma.ActionSigning},
36
			Content: irma.AttributeDisjunctionList([]*irma.AttributeDisjunction{{
Sietse Ringers's avatar
Sietse Ringers committed
37
				Label:      "foo",
38
				Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
39
40
			}}),
		},
Sietse Ringers's avatar
Sietse Ringers committed
41
	}
Sietse Ringers's avatar
Sietse Ringers committed
42
43
}

44
func getIssuanceRequest(defaultValidity bool) *irma.IssuanceRequest {
45
	temp := irma.Timestamp(irma.FloorToEpochBoundary(time.Now().AddDate(1, 0, 0)))
46
	var expiry *irma.Timestamp
47

48
49
50
51
	if !defaultValidity {
		expiry = &temp
	}

52
	return &irma.IssuanceRequest{
Sietse Ringers's avatar
Sietse Ringers committed
53
		BaseRequest: irma.BaseRequest{Type: irma.ActionIssuing},
54
		Credentials: []*irma.CredentialRequest{
Sietse Ringers's avatar
Sietse Ringers committed
55
			{
56
				Validity:         expiry,
57
				CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
Sietse Ringers's avatar
Sietse Ringers committed
58
59
				Attributes: map[string]string{
					"university":        "Radboud",
60
					"studentCardNumber": "31415927",
Sietse Ringers's avatar
Sietse Ringers committed
61
62
63
64
					"studentID":         "s1234567",
					"level":             "42",
				},
			}, {
65
				Validity:         expiry,
66
				CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root"),
Sietse Ringers's avatar
Sietse Ringers committed
67
68
69
70
71
				Attributes: map[string]string{
					"BSN": "299792458",
				},
			},
		},
72
73
74
	}
}

75
76
func getNameIssuanceRequest() *irma.IssuanceRequest {
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
77
78

	req := &irma.IssuanceRequest{
Sietse Ringers's avatar
Sietse Ringers committed
79
		BaseRequest: irma.BaseRequest{Type: irma.ActionIssuing},
80
81
82
		Credentials: []*irma.CredentialRequest{
			{
				Validity:         &expiry,
83
				CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.fullName"),
84
85
86
87
88
89
90
91
92
93
94
95
				Attributes: map[string]string{
					"firstnames": "Johan Pieter",
					"firstname":  "Johan",
					"familyname": "Stuivezand",
				},
			},
		},
	}

	return req
}

Sietse Ringers's avatar
Sietse Ringers committed
96
97
98
99
func getSpecialIssuanceRequest(defaultValidity bool, attribute string) *irma.IssuanceRequest {
	request := getIssuanceRequest(defaultValidity)
	request.Credentials[0].Attributes["studentCardNumber"] = attribute
	return request
100
101
}

Sietse Ringers's avatar
Sietse Ringers committed
102
103
104
func getCombinedIssuanceRequest(id irma.AttributeTypeIdentifier) *irma.IssuanceRequest {
	request := getIssuanceRequest(false)
	request.Disclose = irma.AttributeDisjunctionList{
105
106
		&irma.AttributeDisjunction{Label: "foo", Attributes: []irma.AttributeTypeIdentifier{id}},
	}
Sietse Ringers's avatar
Sietse Ringers committed
107
	return request
Sietse Ringers's avatar
Sietse Ringers committed
108
109
}

110
// startSession starts an IRMA session by posting the request,
Sietse Ringers's avatar
Sietse Ringers committed
111
// and retrieving the QR contents from the specified url.
112
func startSession(request interface{}, url string) (*irma.Qr, error) {
113
114
	server := irma.NewHTTPTransport(url)
	var response irma.Qr
Sietse Ringers's avatar
Sietse Ringers committed
115
116
117
118
119
120
121
	err := server.Post("", &response, request)
	if err != nil {
		return nil, err
	}
	return &response, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
122
123
124
125
126
127
128
129
130
func getJwt(t *testing.T, request irma.SessionRequest, url string) string {
	var jwtcontents interface{}
	switch url {
	case "issue":
		jwtcontents = irma.NewIdentityProviderJwt("testip", request.(*irma.IssuanceRequest))
	case "verification":
		jwtcontents = irma.NewServiceProviderJwt("testsp", request.(*irma.DisclosureRequest))
	case "signature":
		jwtcontents = irma.NewSignatureRequestorJwt("testsigclient", request.(*irma.SignatureRequest))
131
132
133
134
135
136
137
	}

	headerbytes, err := json.Marshal(&map[string]string{"alg": "none", "typ": "JWT"})
	require.NoError(t, err)
	bodybytes, err := json.Marshal(jwtcontents)
	require.NoError(t, err)

Sietse Ringers's avatar
Sietse Ringers committed
138
139
140
141
142
143
144
145
146
147
148
	return base64.RawStdEncoding.EncodeToString(headerbytes) + "." + base64.RawStdEncoding.EncodeToString(bodybytes) + "."
}

func sessionHelper(t *testing.T, request irma.SessionRequest, url string, client *irmaclient.Client) {
	if client == nil {
		client = parseStorage(t)
		defer test.ClearTestStorage(t)
	}

	jwt := getJwt(t, request, url)
	url = "http://localhost:8088/irma_api_server/api/v2/" + url
149
150
151
152
153
154
155
156
157
	qr, transportErr := startSession(jwt, url)
	if transportErr != nil {
		fmt.Printf("+%v\n", transportErr)
	}
	require.NoError(t, transportErr)
	qr.URL = url + "/" + qr.URL

	c := make(chan *SessionResult)
	h := TestHandler{t, c, client}
158
159
160
	j, err := json.Marshal(qr)
	require.NoError(t, err)
	client.NewSession(string(j), h)
161
162
163
164
165
166

	if result := <-c; result != nil {
		require.NoError(t, result.Err)
	}
}

167
func keyshareSessions(t *testing.T, client *irmaclient.Client) {
168
169
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
Sietse Ringers's avatar
Sietse Ringers committed
170
171
	issuanceRequest := getCombinedIssuanceRequest(id)
	issuanceRequest.Credentials = append(issuanceRequest.Credentials,
172
173
		&irma.CredentialRequest{
			Validity:         &expiry,
174
			CredentialTypeID: irma.NewCredentialTypeIdentifier("test.test.mijnirma"),
175
176
177
			Attributes:       map[string]string{"email": "testusername"},
		},
	)
Sietse Ringers's avatar
Sietse Ringers committed
178
	sessionHelper(t, issuanceRequest, "issue", client)
179

Sietse Ringers's avatar
Sietse Ringers committed
180
181
	disclosureRequest := getDisclosureRequest(id)
	disclosureRequest.Content = append(disclosureRequest.Content,
182
183
184
185
186
		&irma.AttributeDisjunction{
			Label:      "foo",
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
		},
	)
Sietse Ringers's avatar
Sietse Ringers committed
187
	sessionHelper(t, disclosureRequest, "verification", client)
188

Sietse Ringers's avatar
Sietse Ringers committed
189
190
	sigRequest := getSigningRequest(id)
	sigRequest.Content = append(sigRequest.Content,
191
192
193
194
195
		&irma.AttributeDisjunction{
			Label:      "foo",
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
		},
	)
Sietse Ringers's avatar
Sietse Ringers committed
196
	sessionHelper(t, sigRequest, "signature", client)
197
198
}

Sietse Ringers's avatar
Sietse Ringers committed
199
func TestSigningSession(t *testing.T) {
200
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
201
202
	request := getSigningRequest(id)
	sessionHelper(t, request, "signature", nil)
Sietse Ringers's avatar
Sietse Ringers committed
203
204
205
}

func TestDisclosureSession(t *testing.T) {
206
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
207
208
	request := getDisclosureRequest(id)
	sessionHelper(t, request, "verification", nil)
Sietse Ringers's avatar
Sietse Ringers committed
209
210
}

211
212
func TestNoAttributeDisclosureSession(t *testing.T) {
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard")
Sietse Ringers's avatar
Sietse Ringers committed
213
214
	request := getDisclosureRequest(id)
	sessionHelper(t, request, "verification", nil)
215
216
}

Sietse Ringers's avatar
Sietse Ringers committed
217
func TestIssuanceSession(t *testing.T) {
218
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
219
220
	request := getCombinedIssuanceRequest(id)
	sessionHelper(t, request, "issue", nil)
Sietse Ringers's avatar
Sietse Ringers committed
221
222
}

223
224
func TestDefaultCredentialValidity(t *testing.T) {
	client := parseStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
225
226
	request := getIssuanceRequest(true)
	sessionHelper(t, request, "issue", client)
227
228
}

229
func TestIssuanceOptionalEmptyAttributes(t *testing.T) {
230
	req := getNameIssuanceRequest()
Sietse Ringers's avatar
Sietse Ringers committed
231
	sessionHelper(t, req, "issue", nil)
232
233
234
235
236
}

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

func TestIssuanceOptionalSetAttributes(t *testing.T) {
241
242
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = "van"
Sietse Ringers's avatar
Sietse Ringers committed
243
	sessionHelper(t, req, "issue", nil)
244
245
}

246
247
func TestLargeAttribute(t *testing.T) {
	client := parseStorage(t)
248
249
	defer test.ClearTestStorage(t)

250
251
	require.NoError(t, client.RemoveAllCredentials())

Sietse Ringers's avatar
Sietse Ringers committed
252
253
	issuanceRequest := getSpecialIssuanceRequest(false, "1234567890123456789012345678901234567890") // 40 chars
	sessionHelper(t, issuanceRequest, "issue", client)
254

Sietse Ringers's avatar
Sietse Ringers committed
255
256
	disclosureRequest := getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.university"))
	sessionHelper(t, disclosureRequest, "verification", client)
257
258
}

259
260
func TestIssuanceSingletonCredential(t *testing.T) {
	client := parseStorage(t)
261
262
	defer test.ClearTestStorage(t)

Sietse Ringers's avatar
Sietse Ringers committed
263
	request := getIssuanceRequest(true)
264
265
	credid := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")

266
	require.Nil(t, client.Attributes(credid, 0))
267

Sietse Ringers's avatar
Sietse Ringers committed
268
	sessionHelper(t, request, "issue", client)
269
270
	require.NotNil(t, client.Attributes(credid, 0))
	require.Nil(t, client.Attributes(credid, 1))
271

Sietse Ringers's avatar
Sietse Ringers committed
272
	sessionHelper(t, request, "issue", client)
273
274
	require.NotNil(t, client.Attributes(credid, 0))
	require.Nil(t, client.Attributes(credid, 1))
275
276
}

277
278
279
280
281
282
283
284
/* 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) {
	client := parseStorage(t)
285
	defer test.ClearTestStorage(t)
286
287
288
289
290
291
292
	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
293
294
	request := getSpecialIssuanceRequest(false, "a23456789012345678901234567890")
	sessionHelper(t, request, "issue", client)
295
296
297
298
299
300
301

	/* 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
302
303
	request = getSpecialIssuanceRequest(false, "é")
	sessionHelper(t, request, "issue", client)
304
305
}

306
// Use the existing keyshare enrollment and credentials
Sietse Ringers's avatar
Sietse Ringers committed
307
308
309
310
// in a keyshare session of each session type.
// Use keyshareuser.sql to enroll the user at the keyshare server.
func TestKeyshareSessions(t *testing.T) {
	client := parseStorage(t)
311
	defer test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
312

313
	keyshareSessions(t, client)
Sietse Ringers's avatar
Sietse Ringers committed
314
}
315
316
317

func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) {
	client := parseStorage(t)
318
	defer test.ClearTestStorage(t)
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342

	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"
	disclosureRequest := irma.DisclosureRequest{
		Content: irma.AttributeDisjunctionList{
			&irma.AttributeDisjunction{
				Label: "foo",
				Attributes: []irma.AttributeTypeIdentifier{
					attrid,
				},
			},
		},
	}

	client.Configuration.Download(&disclosureRequest)
	require.True(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
}

func TestIssueNewAttributeUpdateSchemeManager(t *testing.T) {
	client := parseStorage(t)
343
344
	defer test.ClearTestStorage(t)

345
346
347
348
349
350
351
352
353
354
355
356
357
358
	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"
	client.Configuration.Download(issuanceRequest)
	require.True(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
}

func TestIssueOptionalAttributeUpdateSchemeManager(t *testing.T) {
	client := parseStorage(t)
359
360
	defer test.ClearTestStorage(t)

361
362
363
364
365
366
367
368
369
370
371
372
	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")
	client.Configuration.Download(issuanceRequest)
	require.True(t, client.Configuration.CredentialTypes[credid].AttributeType(attrid).IsOptional())
}

Sietse Ringers's avatar
Sietse Ringers committed
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
func TestIssueNewCredTypeUpdateSchemeManager(t *testing.T) {
	client := parseStorage(t)
	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)
	client.Configuration.Download(request)

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

	test.ClearTestStorage(t)
}

func TestDisclosureNewCredTypeUpdateSchemeManager(t *testing.T) {
	client := parseStorage(t)
	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"
	request := &irma.DisclosureRequest{
		Content: irma.AttributeDisjunctionList([]*irma.AttributeDisjunction{{
			Label:      "foo",
			Attributes: []irma.AttributeTypeIdentifier{attrid},
		}}),
	}
	client.Configuration.Download(request)

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

	test.ClearTestStorage(t)
}

413
414
415
416
417
// 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) {
	client := parseStorage(t)
418
	defer test.ClearTestStorage(t)
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439

	// 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)
	client.NewSession(string(qr), TestHandler{t, c, client})
	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
440
441
	request := getCombinedIssuanceRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
	sessionHelper(t, request, "issue", client)
442
443
444
445
446

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

447
	basepath := test.FindTestdataFolder(t) + "/storage/test/irma_configuration/irma-demo"
448
449
450
451
452
453
454
455
456
457
	exists, err := fs.PathExists(basepath + "/description.xml")
	require.NoError(t, err)
	require.True(t, exists)
	exists, err = fs.PathExists(basepath + "/RU/description.xml")
	require.NoError(t, err)
	require.True(t, exists)
	exists, err = fs.PathExists(basepath + "/RU/Issues/studentCard/description.xml")
	require.NoError(t, err)
	require.True(t, exists)
}