session_test.go 11.9 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"

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

18
19
20
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
21
			Label:      "foo",
22
			Attributes: []irma.AttributeTypeIdentifier{id},
Sietse Ringers's avatar
Sietse Ringers committed
23
24
25
26
		}}),
	})
}

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

43
func getIssuanceRequest(defaultValidity bool) *irma.IssuanceRequest {
44
	temp := irma.Timestamp(irma.FloorToEpochBoundary(time.Now().AddDate(1, 0, 0)))
45
	var expiry *irma.Timestamp
46
47
	credid1 := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	credid2 := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
48

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

53
	return &irma.IssuanceRequest{
54
		Credentials: []*irma.CredentialRequest{
Sietse Ringers's avatar
Sietse Ringers committed
55
			{
56
				Validity:         expiry,
Sietse Ringers's avatar
Sietse Ringers committed
57
58
59
				CredentialTypeID: &credid1,
				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,
Sietse Ringers's avatar
Sietse Ringers committed
66
67
68
69
70
71
				CredentialTypeID: &credid2,
				Attributes: map[string]string{
					"BSN": "299792458",
				},
			},
		},
72
73
74
	}
}

75
76
func getNameIssuanceRequest() *irma.IssuanceRequest {
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
	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
}

96
97
98
99
100
101
102
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

103
104
105
}

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

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

func TestSigningSession(t *testing.T) {
126
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
127
128
129
130
131
132
133
	name := "testsigclient"

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

func TestDisclosureSession(t *testing.T) {
134
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
135
136
137
138
139
140
	name := "testsp"

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

141
142
143
144
145
146
147
148
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
149
func TestIssuanceSession(t *testing.T) {
150
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
Sietse Ringers's avatar
Sietse Ringers committed
151
152
	name := "testip"

153
	jwtcontents := getCombinedJwt(name, id)
Sietse Ringers's avatar
Sietse Ringers committed
154
155
156
	sessionHelper(t, jwtcontents, "issue", nil)
}

157
158
func TestDefaultCredentialValidity(t *testing.T) {
	client := parseStorage(t)
159
	jwtcontents := getIssuanceJwt("testip", true, "")
160
161
162
	sessionHelper(t, jwtcontents, "issue", client)
}

163
func TestIssuanceOptionalEmptyAttributes(t *testing.T) {
164
165
166
167
168
169
170
171
172
	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)
173
174
175
176
	sessionHelper(t, jwt, "issue", nil)
}

func TestIssuanceOptionalSetAttributes(t *testing.T) {
177
178
179
	req := getNameIssuanceRequest()
	req.Credentials[0].Attributes["prefix"] = "van"
	jwt := irma.NewIdentityProviderJwt("testip", req)
180
181
182
	sessionHelper(t, jwt, "issue", nil)
}

183
184
185
186
func TestLargeAttribute(t *testing.T) {
	client := parseStorage(t)
	require.NoError(t, client.RemoveAllCredentials())

187
	jwtcontents := getIssuanceJwt("testip", false, "1234567890123456789012345678901234567890") // 40 chars
188
189
190
191
192
193
194
195
196
	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)

197
	test.ClearTestStorage(t)
198
199
}

200
201
202
203
204
205
206
207
208
209
210
211
212
213
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)
}

214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/* 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
240
241

	test.ClearTestStorage(t)
242
243
}

Sietse Ringers's avatar
Sietse Ringers committed
244
func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *Client) {
245
246
247
248
	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
249
250
251
252
253
	init := client == nil
	if init {
		client = parseStorage(t)
	}

254
	url = "http://localhost:8088/irma_api_server/api/v2/" + url
Sietse Ringers's avatar
Sietse Ringers committed
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
	//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

270
	c := make(chan *irma.SessionError)
271
272
273
	if h == nil {
		h = TestHandler{t, c, client}
	}
274
	client.newQrSession(qr, h)
Sietse Ringers's avatar
Sietse Ringers committed
275
276
277
278
279
280

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

	if init {
281
		test.ClearTestStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
282
283
284
	}
}

285
func keyshareSessions(t *testing.T, client *Client) {
286
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
287
	expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
288
	credid := irma.NewCredentialTypeIdentifier("test.test.mijnirma")
289
	jwt := getCombinedJwt("testip", id)
290
291
292
	jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials = append(
		jwt.(*irma.IdentityProviderJwt).Request.Request.Credentials,
		&irma.CredentialRequest{
Sietse Ringers's avatar
Sietse Ringers committed
293
294
			Validity:         &expiry,
			CredentialTypeID: &credid,
295
			Attributes:       map[string]string{"email": "testusername"},
Sietse Ringers's avatar
Sietse Ringers committed
296
297
298
299
300
		},
	)
	sessionHelper(t, jwt, "issue", client)

	jwt = getDisclosureJwt("testsp", id)
301
302
303
	jwt.(*irma.ServiceProviderJwt).Request.Request.Content = append(
		jwt.(*irma.ServiceProviderJwt).Request.Request.Content,
		&irma.AttributeDisjunction{
Sietse Ringers's avatar
Sietse Ringers committed
304
			Label:      "foo",
305
			Attributes: []irma.AttributeTypeIdentifier{irma.NewAttributeTypeIdentifier("test.test.mijnirma.email")},
Sietse Ringers's avatar
Sietse Ringers committed
306
307
308
309
310
		},
	)
	sessionHelper(t, jwt, "verification", client)

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

321
// Test pinchange interaction
322
func TestKeyshareChangePin(t *testing.T) {
323
324
	client := parseStorage(t)

Sietse Ringers's avatar
Sietse Ringers committed
325
326
	require.NoError(t, client.keyshareChangePinWorker(irma.NewSchemeManagerIdentifier("test"), "12345", "54321"))
	require.NoError(t, client.keyshareChangePinWorker(irma.NewSchemeManagerIdentifier("test"), "54321", "12345"))
327
328
329
330

	test.ClearTestStorage(t)
}

331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// 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
348
	require.NoError(t, client.keyshareEnrollWorker(irma.NewSchemeManagerIdentifier("test"), nil, "12345", "en"))
349
350
351
352
353
354
355
	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
356

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

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

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

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