session_test.go 11.8 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
11

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

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

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

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

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

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

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

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

98
99
func getSigningJwt(name string, id irma.AttributeTypeIdentifier) interface{} {
	return irma.NewSignatureRequestorJwt(name, &irma.SignatureRequest{
Sietse Ringers's avatar
Sietse Ringers committed
100
101
		Message:     "test",
		MessageType: "STRING",
102
103
		DisclosureRequest: irma.DisclosureRequest{
			Content: irma.AttributeDisjunctionList([]*irma.AttributeDisjunction{{
Sietse Ringers's avatar
Sietse Ringers committed
104
				Label:      "foo",
105
				Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
106
107
108
109
110
			}}),
		},
	})
}

111
func getIssuanceRequest(defaultValidity bool) *irma.IssuanceRequest {
112
	temp := irma.Timestamp(irma.FloorToEpochBoundary(time.Now().AddDate(1, 0, 0)))
113
	var expiry *irma.Timestamp
114
115
	credid1 := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	credid2 := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
116

117
118
119
120
	if !defaultValidity {
		expiry = &temp
	}

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

143
144
func getNameIssuanceRequest() *irma.IssuanceRequest {
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
	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
}

164
165
func getIssuanceJwt(name string, defaultValidity bool) interface{} {
	return irma.NewIdentityProviderJwt(name, getIssuanceRequest(defaultValidity))
166
167
168
}

func getCombinedJwt(name string, id irma.AttributeTypeIdentifier) interface{} {
169
	isreq := getIssuanceRequest(false)
170
171
172
173
	isreq.Disclose = irma.AttributeDisjunctionList{
		&irma.AttributeDisjunction{Label: "foo", Attributes: []irma.AttributeTypeIdentifier{id}},
	}
	return irma.NewIdentityProviderJwt(name, isreq)
Sietse Ringers's avatar
Sietse Ringers committed
174
175
176
177
}

// StartSession starts an IRMA session by posting the request,
// and retrieving the QR contents from the specified url.
178
179
180
func StartSession(request interface{}, url string) (*irma.Qr, error) {
	server := irma.NewHTTPTransport(url)
	var response irma.Qr
Sietse Ringers's avatar
Sietse Ringers committed
181
182
183
184
185
186
187
188
	err := server.Post("", &response, request)
	if err != nil {
		return nil, err
	}
	return &response, nil
}

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

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

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

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

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

208
	jwtcontents := getCombinedJwt(name, id)
Sietse Ringers's avatar
Sietse Ringers committed
209
210
211
	sessionHelper(t, jwtcontents, "issue", nil)
}

212
213
214
215
216
217
func TestDefaultCredentialValidity(t *testing.T) {
	client := parseStorage(t)
	jwtcontents := getIssuanceJwt("testip", true)
	sessionHelper(t, jwtcontents, "issue", client)
}

218
func TestIssuanceOptionalEmptyAttributes(t *testing.T) {
219
220
221
222
223
224
225
226
227
	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)
228
229
230
231
	sessionHelper(t, jwt, "issue", nil)
}

func TestIssuanceOptionalSetAttributes(t *testing.T) {
232
233
234
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = "van"
	jwt := irma.NewIdentityProviderJwt("testip", req)
235
236
237
	sessionHelper(t, jwt, "issue", nil)
}

238
239
240
241
func TestLargeAttribute(t *testing.T) {
	client := parseStorage(t)
	require.NoError(t, client.RemoveAllCredentials())

242
	jwtcontents := getIssuanceJwt("testip", false)
243
244
245
246
247
248
249
250
251
	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)

252
	test.ClearTestStorage(t)
253
254
}

Sietse Ringers's avatar
Sietse Ringers committed
255
func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *Client) {
256
257
258
259
	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
260
261
262
263
264
	init := client == nil
	if init {
		client = parseStorage(t)
	}

265
	url = "http://localhost:8088/irma_api_server/api/v2/" + url
Sietse Ringers's avatar
Sietse Ringers committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
	//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

281
	c := make(chan *irma.SessionError)
282
283
284
285
	if h == nil {
		h = TestHandler{t, c, client}
	}
	client.NewSession(qr, h)
Sietse Ringers's avatar
Sietse Ringers committed
286
287
288
289
290
291

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

	if init {
292
		test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
293
294
295
	}
}

296
func keyshareSessions(t *testing.T, client *Client) {
297
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
298
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
299
	credid := irma.NewCredentialTypeIdentifier("test.test.mijnirma")
300
	jwt := getCombinedJwt("testip", id)
301
302
303
	jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials = append(
		jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials,
		&irma.CredentialRequest{
Sietse Ringers's avatar
Sietse Ringers committed
304
305
			Validity:         &expiry,
			CredentialTypeID: &credid,
306
			Attributes:       map[string]string{"email": "testusername"},
Sietse Ringers's avatar
Sietse Ringers committed
307
308
309
310
311
		},
	)
	sessionHelper(t, jwt, "issue", client)

	jwt = getDisclosureJwt("testsp", id)
312
313
314
	jwt.(*irma.ServiceProviderJwt).Request.Request.Content = append(
		jwt.(*irma.ServiceProviderJwt).Request.Request.Content,
		&irma.AttributeDisjunction{
Sietse Ringers's avatar
Sietse Ringers committed
315
			Label:      "foo",
316
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
Sietse Ringers's avatar
Sietse Ringers committed
317
318
319
320
321
		},
	)
	sessionHelper(t, jwt, "verification", client)

	jwt = getSigningJwt("testsigclient", id)
322
323
324
	jwt.(*irma.SignatureRequestorJwt).Request.Request.Content = append(
		jwt.(*irma.SignatureRequestorJwt).Request.Request.Content,
		&irma.AttributeDisjunction{
Sietse Ringers's avatar
Sietse Ringers committed
325
			Label:      "foo",
326
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
Sietse Ringers's avatar
Sietse Ringers committed
327
328
329
		},
	)
	sessionHelper(t, jwt, "signature", client)
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
}

// 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
349
	require.NoError(t, client.keyshareEnrollWorker(irma.NewSchemeManagerIdentifier("test"), nil, "12345", "en"))
350
351
352
353
354
355
356
	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
357

358
	test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
359
360
361
362
363
364
365
366
}

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

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

369
	test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
370
}