session_test.go 14.8 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
}

21
22
23
func getDisclosureJwt(name string, id irma.AttributeTypeIdentifier) interface{} {
	return irma.NewServiceProviderJwt(name, &irma.DisclosureRequest{
		Content: irma.AttributeDisjunctionList([]*irma.AttributeDisjunction{{
Sietse Ringers's avatar
Sietse Ringers committed
24
			Label:      "foo",
25
			Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
26
27
28
29
		}}),
	})
}

30
31
func getSigningJwt(name string, id irma.AttributeTypeIdentifier) interface{} {
	return irma.NewSignatureRequestorJwt(name, &irma.SignatureRequest{
32
		Message: "test",
33
34
		DisclosureRequest: irma.DisclosureRequest{
			Content: irma.AttributeDisjunctionList([]*irma.AttributeDisjunction{{
Sietse Ringers's avatar
Sietse Ringers committed
35
				Label:      "foo",
36
				Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
37
38
39
40
41
			}}),
		},
	})
}

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

46
47
48
49
	if !defaultValidity {
		expiry = &temp
	}

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

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

	req := &irma.IssuanceRequest{
		Credentials: []*irma.CredentialRequest{
			{
				Validity:         &expiry,
79
				CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.fullName"),
80
81
82
83
84
85
86
87
88
89
90
91
				Attributes: map[string]string{
					"firstnames": "Johan Pieter",
					"firstname":  "Johan",
					"familyname": "Stuivezand",
				},
			},
		},
	}

	return req
}

92
93
94
95
96
97
98
func getIssuanceJwt(name string, defaultValidity bool, attribute string) interface{} {
	jwt := irma.NewIdentityProviderJwt(name, getIssuanceRequest(defaultValidity))
	if attribute != "" {
		jwt.Request.Request.Credentials[0].Attributes["studentCardNumber"] = attribute
	}
	return jwt

99
100
101
}

func getCombinedJwt(name string, id irma.AttributeTypeIdentifier) interface{} {
102
	isreq := getIssuanceRequest(false)
103
104
105
106
	isreq.Disclose = irma.AttributeDisjunctionList{
		&irma.AttributeDisjunction{Label: "foo", Attributes: []irma.AttributeTypeIdentifier{id}},
	}
	return irma.NewIdentityProviderJwt(name, isreq)
Sietse Ringers's avatar
Sietse Ringers committed
107
108
}

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

121
func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *irmaclient.Client) {
122
	if client == nil {
123
		client = parseStorage(t)
124
		defer test.ClearTestStorage(t)
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
	}

	url = "http://localhost:8088/irma_api_server/api/v2/" + url
	//url = "https://demo.irmacard.org/tomcat/irma_api_server/api/v2/" + url

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

	jwt := base64.RawStdEncoding.EncodeToString(headerbytes) + "." + base64.RawStdEncoding.EncodeToString(bodybytes) + "."
	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}
145
146
147
	j, err := json.Marshal(qr)
	require.NoError(t, err)
	client.NewSession(string(j), h)
148
149
150
151
152
153

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

154
func keyshareSessions(t *testing.T, client *irmaclient.Client) {
155
156
157
158
159
160
161
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
	jwt := getCombinedJwt("testip", id)
	jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials = append(
		jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials,
		&irma.CredentialRequest{
			Validity:         &expiry,
162
			CredentialTypeID: irma.NewCredentialTypeIdentifier("test.test.mijnirma"),
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
			Attributes:       map[string]string{"email": "testusername"},
		},
	)
	sessionHelper(t, jwt, "issue", client)

	jwt = getDisclosureJwt("testsp", id)
	jwt.(*irma.ServiceProviderJwt).Request.Request.Content = append(
		jwt.(*irma.ServiceProviderJwt).Request.Request.Content,
		&irma.AttributeDisjunction{
			Label:      "foo",
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
		},
	)
	sessionHelper(t, jwt, "verification", client)

	jwt = getSigningJwt("testsigclient", id)
	jwt.(*irma.SignatureRequestorJwt).Request.Request.Content = append(
		jwt.(*irma.SignatureRequestorJwt).Request.Request.Content,
		&irma.AttributeDisjunction{
			Label:      "foo",
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
		},
	)
	sessionHelper(t, jwt, "signature", client)
}

Sietse Ringers's avatar
Sietse Ringers committed
189
func TestSigningSession(t *testing.T) {
190
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
191
192
193
194
195
196
197
	name := "testsigclient"

	jwtcontents := getSigningJwt(name, id)
	sessionHelper(t, jwtcontents, "signature", nil)
}

func TestDisclosureSession(t *testing.T) {
198
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
199
200
201
202
203
204
	name := "testsp"

	jwtcontents := getDisclosureJwt(name, id)
	sessionHelper(t, jwtcontents, "verification", nil)
}

205
206
207
208
209
210
211
212
func TestNoAttributeDisclosureSession(t *testing.T) {
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard")
	name := "testsp"

	jwtcontents := getDisclosureJwt(name, id)
	sessionHelper(t, jwtcontents, "verification", nil)
}

Sietse Ringers's avatar
Sietse Ringers committed
213
func TestIssuanceSession(t *testing.T) {
214
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
215
216
	name := "testip"

217
	jwtcontents := getCombinedJwt(name, id)
Sietse Ringers's avatar
Sietse Ringers committed
218
219
220
	sessionHelper(t, jwtcontents, "issue", nil)
}

221
222
func TestDefaultCredentialValidity(t *testing.T) {
	client := parseStorage(t)
223
	jwtcontents := getIssuanceJwt("testip", true, "")
224
225
226
	sessionHelper(t, jwtcontents, "issue", client)
}

227
func TestIssuanceOptionalEmptyAttributes(t *testing.T) {
228
229
230
231
232
233
234
235
236
	req := getNameIssuanceRequest()
	jwt := irma.NewIdentityProviderJwt("testip", req)
	sessionHelper(t, jwt, "issue", nil)
}

func TestIssuanceOptionalZeroLengthAttributes(t *testing.T) {
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = ""
	jwt := irma.NewIdentityProviderJwt("testip", req)
237
238
239
240
	sessionHelper(t, jwt, "issue", nil)
}

func TestIssuanceOptionalSetAttributes(t *testing.T) {
241
242
243
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = "van"
	jwt := irma.NewIdentityProviderJwt("testip", req)
244
245
246
	sessionHelper(t, jwt, "issue", nil)
}

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

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

253
	jwtcontents := getIssuanceJwt("testip", false, "1234567890123456789012345678901234567890") // 40 chars
254
255
256
257
258
259
	sessionHelper(t, jwtcontents, "issue", client)

	jwtcontents = getDisclosureJwt("testsp", irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.university"))
	sessionHelper(t, jwtcontents, "verification", client)
}

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

264
265
266
	jwtcontents := getIssuanceJwt("testip", true, "")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")

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

	sessionHelper(t, jwtcontents, "issue", client)
270
271
	require.NotNil(t, client.Attributes(credid, 0))
	require.Nil(t, client.Attributes(credid, 1))
272
273

	sessionHelper(t, jwtcontents, "issue", client)
274
275
	require.NotNil(t, client.Attributes(credid, 0))
	require.Nil(t, client.Attributes(credid, 1))
276
277
}

278
279
280
281
282
283
284
285
/* 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)
286
	defer test.ClearTestStorage(t)
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
	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. */
	jwtcontents := getIssuanceJwt("testip", false, "a23456789012345678901234567890")
	sessionHelper(t, jwtcontents, "issue", client)

	/* 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. */
	jwtcontents = getIssuanceJwt("testip", false, "é")
	sessionHelper(t, jwtcontents, "issue", client)
}

307
// Use the existing keyshare enrollment and credentials
Sietse Ringers's avatar
Sietse Ringers committed
308
309
310
311
// 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)
312
	defer test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
313

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

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

	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)
344
345
	defer test.ClearTestStorage(t)

346
347
348
349
350
351
352
353
354
355
356
357
358
359
	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)
360
361
	defer test.ClearTestStorage(t)

362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
	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())
}

// 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)
379
	defer test.ClearTestStorage(t)
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

	// 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
	jwt := getCombinedJwt("testip", irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
	sessionHelper(t, jwt, "issue", client)

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

408
	basepath := test.FindTestdataFolder(t) + "/storage/test/irma_configuration/irma-demo"
409
410
411
412
413
414
415
416
417
418
	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)
}