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

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

Sietse Ringers's avatar
Sietse Ringers committed
11
12
	"math/big"

Sietse Ringers's avatar
Sietse Ringers committed
13
	"github.com/go-errors/errors"
14
	"github.com/privacybydesign/irmago"
15
	"github.com/privacybydesign/irmago/internal/test"
Sietse Ringers's avatar
Sietse Ringers committed
16
17
18
19
20
	"github.com/stretchr/testify/require"
)

type TestHandler struct {
	t      *testing.T
21
	c      chan *irma.SessionError
Sietse Ringers's avatar
Sietse Ringers committed
22
23
24
	client *Client
}

25
26
func (th TestHandler) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
	th.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("KeyshareEnrollmentIncomplete")})
27
28
}

29
func (th TestHandler) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
30
	th.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("KeyshareBlocked")})
31
32
}

33
func (th TestHandler) KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier) {
34
	th.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.Errorf("Missing keyshare server %s", manager.String())})
Sietse Ringers's avatar
Sietse Ringers committed
35
36
}

37
38
39
40
func (th TestHandler) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
	th.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.Errorf("Keyshare enrollment deleted for %s", manager.String())})
}

41
func (th TestHandler) StatusUpdate(action irma.Action, status irma.Status) {}
42
func (th TestHandler) Success(action irma.Action, result string) {
Sietse Ringers's avatar
Sietse Ringers committed
43
44
	th.c <- nil
}
45
46
func (th TestHandler) Cancelled(action irma.Action) {
	th.c <- &irma.SessionError{}
Sietse Ringers's avatar
Sietse Ringers committed
47
}
48
func (th TestHandler) Failure(action irma.Action, err *irma.SessionError) {
Sietse Ringers's avatar
Sietse Ringers committed
49
50
51
52
53
54
	select {
	case th.c <- err:
	default:
		th.t.Fatal(err)
	}
}
55
func (th TestHandler) UnsatisfiableRequest(action irma.Action, serverName string, missing irma.AttributeDisjunctionList) {
56
57
	th.c <- &irma.SessionError{
		ErrorType: irma.ErrorType("UnsatisfiableRequest"),
Sietse Ringers's avatar
Sietse Ringers committed
58
59
	}
}
60
61
62
func (th TestHandler) RequestVerificationPermission(request irma.DisclosureRequest, ServerName string, callback PermissionHandler) {
	choice := &irma.DisclosureChoice{
		Attributes: []*irma.AttributeIdentifier{},
Sietse Ringers's avatar
Sietse Ringers committed
63
	}
64
	var candidates []*irma.AttributeIdentifier
Sietse Ringers's avatar
Sietse Ringers committed
65
66
	for _, disjunction := range request.Content {
		candidates = th.client.Candidates(disjunction)
Sietse Ringers's avatar
Sietse Ringers committed
67
68
69
		if len(candidates) == 0 {
			th.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("No disclosure candidates found")})
		}
Sietse Ringers's avatar
Sietse Ringers committed
70
71
72
73
		choice.Attributes = append(choice.Attributes, candidates[0])
	}
	callback(true, choice)
}
74
75
func (th TestHandler) RequestIssuancePermission(request irma.IssuanceRequest, ServerName string, callback PermissionHandler) {
	dreq := irma.DisclosureRequest{
Sietse Ringers's avatar
Sietse Ringers committed
76
77
78
79
80
		SessionRequest: request.SessionRequest,
		Content:        request.Disclose,
	}
	th.RequestVerificationPermission(dreq, ServerName, callback)
}
81
func (th TestHandler) RequestSignaturePermission(request irma.SignatureRequest, ServerName string, callback PermissionHandler) {
Sietse Ringers's avatar
Sietse Ringers committed
82
83
	th.RequestVerificationPermission(request.DisclosureRequest, ServerName, callback)
}
84
func (th TestHandler) RequestSchemeManagerPermission(manager *irma.SchemeManager, callback func(proceed bool)) {
Sietse Ringers's avatar
Sietse Ringers committed
85
86
87
88
89
90
	callback(true)
}
func (th TestHandler) RequestPin(remainingAttempts int, callback PinHandler) {
	callback(true, "12345")
}

91
92
93
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
94
			Label:      "foo",
95
			Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
96
97
98
99
		}}),
	})
}

100
101
func getSigningJwt(name string, id irma.AttributeTypeIdentifier) interface{} {
	return irma.NewSignatureRequestorJwt(name, &irma.SignatureRequest{
102
		Message: "test",
103
		DisclosureRequest: irma.DisclosureRequest{
104
105
106
107
			SessionRequest: irma.SessionRequest{
				Nonce:   big.NewInt(1),
				Context: big.NewInt(1),
			},
108
			Content: irma.AttributeDisjunctionList([]*irma.AttributeDisjunction{{
Sietse Ringers's avatar
Sietse Ringers committed
109
				Label:      "foo",
110
				Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
111
112
113
114
115
			}}),
		},
	})
}

116
func getIssuanceRequest(defaultValidity bool) *irma.IssuanceRequest {
117
	temp := irma.Timestamp(irma.FloorToEpochBoundary(time.Now().AddDate(1, 0, 0)))
118
	var expiry *irma.Timestamp
119
120
	credid1 := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	credid2 := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
121

122
123
124
125
	if !defaultValidity {
		expiry = &temp
	}

126
	return &irma.IssuanceRequest{
127
		Credentials: []*irma.CredentialRequest{
Sietse Ringers's avatar
Sietse Ringers committed
128
			{
129
				Validity:         expiry,
Sietse Ringers's avatar
Sietse Ringers committed
130
131
132
				CredentialTypeID: &credid1,
				Attributes: map[string]string{
					"university":        "Radboud",
133
					"studentCardNumber": "31415927",
Sietse Ringers's avatar
Sietse Ringers committed
134
135
136
137
					"studentID":         "s1234567",
					"level":             "42",
				},
			}, {
138
				Validity:         expiry,
Sietse Ringers's avatar
Sietse Ringers committed
139
140
141
142
143
144
				CredentialTypeID: &credid2,
				Attributes: map[string]string{
					"BSN": "299792458",
				},
			},
		},
145
146
147
	}
}

148
149
func getNameIssuanceRequest() *irma.IssuanceRequest {
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
	credid := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.fullName")

	req := &irma.IssuanceRequest{
		Credentials: []*irma.CredentialRequest{
			{
				Validity:         &expiry,
				CredentialTypeID: &credid,
				Attributes: map[string]string{
					"firstnames": "Johan Pieter",
					"firstname":  "Johan",
					"familyname": "Stuivezand",
				},
			},
		},
	}

	return req
}

169
170
171
172
173
174
175
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

176
177
178
}

func getCombinedJwt(name string, id irma.AttributeTypeIdentifier) interface{} {
179
	isreq := getIssuanceRequest(false)
180
181
182
183
	isreq.Disclose = irma.AttributeDisjunctionList{
		&irma.AttributeDisjunction{Label: "foo", Attributes: []irma.AttributeTypeIdentifier{id}},
	}
	return irma.NewIdentityProviderJwt(name, isreq)
Sietse Ringers's avatar
Sietse Ringers committed
184
185
186
187
}

// StartSession starts an IRMA session by posting the request,
// and retrieving the QR contents from the specified url.
188
189
190
func StartSession(request interface{}, url string) (*irma.Qr, error) {
	server := irma.NewHTTPTransport(url)
	var response irma.Qr
Sietse Ringers's avatar
Sietse Ringers committed
191
192
193
194
195
196
197
198
	err := server.Post("", &response, request)
	if err != nil {
		return nil, err
	}
	return &response, nil
}

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

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

func TestDisclosureSession(t *testing.T) {
207
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
211
212
213
	name := "testsp"

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

214
215
216
217
218
219
220
221
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
222
func TestIssuanceSession(t *testing.T) {
223
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
224
225
	name := "testip"

226
	jwtcontents := getCombinedJwt(name, id)
Sietse Ringers's avatar
Sietse Ringers committed
227
228
229
	sessionHelper(t, jwtcontents, "issue", nil)
}

230
231
func TestDefaultCredentialValidity(t *testing.T) {
	client := parseStorage(t)
232
	jwtcontents := getIssuanceJwt("testip", true, "")
233
234
235
	sessionHelper(t, jwtcontents, "issue", client)
}

236
func TestIssuanceOptionalEmptyAttributes(t *testing.T) {
237
238
239
240
241
242
243
244
245
	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)
246
247
248
249
	sessionHelper(t, jwt, "issue", nil)
}

func TestIssuanceOptionalSetAttributes(t *testing.T) {
250
251
252
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = "van"
	jwt := irma.NewIdentityProviderJwt("testip", req)
253
254
255
	sessionHelper(t, jwt, "issue", nil)
}

256
257
258
259
func TestLargeAttribute(t *testing.T) {
	client := parseStorage(t)
	require.NoError(t, client.RemoveAllCredentials())

260
	jwtcontents := getIssuanceJwt("testip", false, "1234567890123456789012345678901234567890") // 40 chars
261
262
263
264
265
266
267
268
269
	sessionHelper(t, jwtcontents, "issue", client)

	cred, err := client.credential(irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
	require.NoError(t, err)
	require.True(t, cred.Signature.Verify(cred.Pk, cred.Attributes))

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

270
	test.ClearTestStorage(t)
271
272
}

273
274
275
276
277
278
279
280
281
282
283
284
285
286
func TestIssuanceSingletonCredential(t *testing.T) {
	client := parseStorage(t)
	jwtcontents := getIssuanceJwt("testip", true, "")
	credid := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")

	require.Len(t, client.attrs(credid), 0)

	sessionHelper(t, jwtcontents, "issue", client)
	require.Len(t, client.attrs(credid), 1)

	sessionHelper(t, jwtcontents, "issue", client)
	require.Len(t, client.attrs(credid), 1)
}

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
/* 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)
	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)
Sietse Ringers's avatar
Sietse Ringers committed
313
314

	test.ClearTestStorage(t)
315
316
}

Sietse Ringers's avatar
Sietse Ringers committed
317
func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *Client) {
318
319
320
321
	sessionHandlerHelper(t, jwtcontents, url, client, nil)
}

func sessionHandlerHelper(t *testing.T, jwtcontents interface{}, url string, client *Client, h Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
322
323
324
325
326
	init := client == nil
	if init {
		client = parseStorage(t)
	}

327
	url = "http://localhost:8088/irma_api_server/api/v2/" + url
Sietse Ringers's avatar
Sietse Ringers committed
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
	//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

343
	c := make(chan *irma.SessionError)
344
345
346
347
	if h == nil {
		h = TestHandler{t, c, client}
	}
	client.NewSession(qr, h)
Sietse Ringers's avatar
Sietse Ringers committed
348
349
350
351
352
353

	if err := <-c; err != nil {
		t.Fatal(*err)
	}

	if init {
354
		test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
355
356
357
	}
}

358
func keyshareSessions(t *testing.T, client *Client) {
359
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
360
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
361
	credid := irma.NewCredentialTypeIdentifier("test.test.mijnirma")
362
	jwt := getCombinedJwt("testip", id)
363
364
365
	jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials = append(
		jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials,
		&irma.CredentialRequest{
Sietse Ringers's avatar
Sietse Ringers committed
366
367
			Validity:         &expiry,
			CredentialTypeID: &credid,
368
			Attributes:       map[string]string{"email": "testusername"},
Sietse Ringers's avatar
Sietse Ringers committed
369
370
371
372
373
		},
	)
	sessionHelper(t, jwt, "issue", client)

	jwt = getDisclosureJwt("testsp", id)
374
375
376
	jwt.(*irma.ServiceProviderJwt).Request.Request.Content = append(
		jwt.(*irma.ServiceProviderJwt).Request.Request.Content,
		&irma.AttributeDisjunction{
Sietse Ringers's avatar
Sietse Ringers committed
377
			Label:      "foo",
378
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
Sietse Ringers's avatar
Sietse Ringers committed
379
380
381
382
383
		},
	)
	sessionHelper(t, jwt, "verification", client)

	jwt = getSigningJwt("testsigclient", id)
384
385
386
	jwt.(*irma.SignatureRequestorJwt).Request.Request.Content = append(
		jwt.(*irma.SignatureRequestorJwt).Request.Request.Content,
		&irma.AttributeDisjunction{
Sietse Ringers's avatar
Sietse Ringers committed
387
			Label:      "foo",
388
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
Sietse Ringers's avatar
Sietse Ringers committed
389
390
391
		},
	)
	sessionHelper(t, jwt, "signature", client)
392
393
}

394
// Test pinchange interaction
395
func TestKeyshareChangePin(t *testing.T) {
396
397
	client := parseStorage(t)

Sietse Ringers's avatar
Sietse Ringers committed
398
399
	require.NoError(t, client.keyshareChangePinWorker(irma.NewSchemeManagerIdentifier("test"), "12345", "54321"))
	require.NoError(t, client.keyshareChangePinWorker(irma.NewSchemeManagerIdentifier("test"), "54321", "12345"))
400
401
402
403

	test.ClearTestStorage(t)
}

404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
// Enroll at a keyshare server and do an issuance, disclosure,
// and issuance session, also using irma-demo credentials deserialized from Android storage
func TestKeyshareEnrollmentAndSessions(t *testing.T) {
	client := parseStorage(t)
	credtype := irma.NewCredentialTypeIdentifier("test.test.mijnirma")

	// Remove existing registration at test keyshare server
	require.NoError(t, client.RemoveCredentialByHash(
		client.Attributes(credtype, 0).Hash(),
	))
	require.NoError(t, client.KeyshareRemove(irma.NewSchemeManagerIdentifier("test")))

	// Do a new registration session
	c := make(chan error) // channel for TestClientHandler to inform us of result
	client.handler.(*TestClientHandler).c = c
	bytes := make([]byte, 8, 8)
	rand.Read(bytes)
Sietse Ringers's avatar
Sietse Ringers committed
421
	require.NoError(t, client.keyshareEnrollWorker(irma.NewSchemeManagerIdentifier("test"), nil, "12345", "en"))
422
423
424
425
426
427
428
	if err := <-c; err != nil {
		t.Fatal(err)
	}

	require.NotNil(t, client.Attributes(credtype, 0))

	keyshareSessions(t, client)
Sietse Ringers's avatar
Sietse Ringers committed
429

430
	test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
431
432
433
434
435
436
437
438
}

// Use the existing keyshare enrollment and credentials deserialized from Android storage
// 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)

439
	keyshareSessions(t, client)
Sietse Ringers's avatar
Sietse Ringers committed
440

441
	test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
442
}