irmago_test.go 13.2 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
package irmago
2
3

import (
4
5
	"fmt"
	"math/big"
Sietse Ringers's avatar
Sietse Ringers committed
6
	"os"
7
8
	"testing"
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
9

10
11
	"encoding/json"

Sietse Ringers's avatar
Sietse Ringers committed
12
	"github.com/mhe/gabi"
Sietse Ringers's avatar
Sietse Ringers committed
13
	"github.com/stretchr/testify/require"
14
15
)

Sietse Ringers's avatar
Sietse Ringers committed
16
func TestMain(m *testing.M) {
Sietse Ringers's avatar
Sietse Ringers committed
17
18
	retCode := m.Run()

Sietse Ringers's avatar
Sietse Ringers committed
19
20
	// TODO make testdata/storage

Sietse Ringers's avatar
Sietse Ringers committed
21
22
	err := os.RemoveAll("testdata/storage/test")
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
23
		fmt.Println("Could not delete test storage")
Sietse Ringers's avatar
Sietse Ringers committed
24
25
26
		os.Exit(1)
	}

Sietse Ringers's avatar
Sietse Ringers committed
27
28
	os.Exit(retCode)
}
Sietse Ringers's avatar
Sietse Ringers committed
29

Sietse Ringers's avatar
Sietse Ringers committed
30
31
type IgnoringKeyshareHandler struct{}

32
func (i *IgnoringKeyshareHandler) StartRegistration(m *SchemeManager, callback func(e, p string)) {
Sietse Ringers's avatar
Sietse Ringers committed
33
}
Sietse Ringers's avatar
Sietse Ringers committed
34

35
func parseStorage(t *testing.T) *CredentialManager {
36
	exists, err := PathExists("testdata/storage/test")
Sietse Ringers's avatar
Sietse Ringers committed
37
38
39
	require.NoError(t, err, "pathexists() failed")
	if !exists {
		require.NoError(t, os.Mkdir("testdata/storage/test", 0755), "Could not create test storage")
Sietse Ringers's avatar
Sietse Ringers committed
40
	}
41
	manager, err := NewCredentialManager(
42
43
		"testdata/storage/test",
		"testdata/irma_configuration",
44
		"testdata/oldstorage",
45
		&IgnoringKeyshareHandler{},
46
47
48
	)
	require.NoError(t, err)
	return manager
Sietse Ringers's avatar
Sietse Ringers committed
49
50
51
}

func teardown(t *testing.T) {
52
	require.NoError(t, os.RemoveAll("testdata/storage/test"))
Sietse Ringers's avatar
Sietse Ringers committed
53
54
}

55
56
57
58
59
60
61
// A convenience function for initializing big integers from known correct (10
// base) strings. Use with care, errors are ignored.
func s2big(s string) (r *big.Int) {
	r, _ = new(big.Int).SetString(s, 10)
	return
}

62
63
func verifyManagerIsUnmarshaled(t *testing.T, manager *CredentialManager) {
	cred, err := manager.credential(NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
64
65
66
	require.NoError(t, err, "could not fetch credential")
	require.NotNil(t, cred, "Credential should exist")
	require.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")
67

Sietse Ringers's avatar
Sietse Ringers committed
68
	cred, err = manager.credential(NewCredentialTypeIdentifier("test.test.mijnirma"), 0)
69
70
71
	require.NoError(t, err, "could not fetch credential")
	require.NotNil(t, cred, "Credential should exist")
	require.NotNil(t, cred.Signature.KeyshareP)
72

73
	require.NotEmpty(t, manager.CredentialInfoList())
74

75
76
77
78
	pk, err := cred.PublicKey()
	require.NoError(t, err)
	require.True(t,
		cred.Signature.Verify(pk, cred.Attributes),
79
80
81
		"Credential should be valid",
	)
}
Sietse Ringers's avatar
Sietse Ringers committed
82

Sietse Ringers's avatar
Sietse Ringers committed
83
func verifyCredentials(t *testing.T, manager *CredentialManager) {
84
85
	var pk *gabi.PublicKey
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
86
	for credtype, credsmap := range manager.credentials {
87
		for index, cred := range credsmap {
88
89
			pk, err = cred.PublicKey()
			require.NoError(t, err)
90
			require.True(t,
91
				cred.Credential.Signature.Verify(pk, cred.Attributes),
92
93
				"Credential %s-%d was invalid", credtype.String(), index,
			)
94
95
96
97
			require.Equal(t, cred.Attributes[0], manager.secretkey.Key,
				"Secret key of credential %s-%d unequal to main secret key",
				cred.CredentialType().Identifier().String(), index,
			)
98
99
100
101
		}
	}
}

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
func verifyPaillierKey(t *testing.T, PrivateKey *paillierPrivateKey) {
	require.NotNil(t, PrivateKey)
	require.NotNil(t, PrivateKey.L)
	require.NotNil(t, PrivateKey.U)
	require.NotNil(t, PrivateKey.PublicKey.N)

	require.Equal(t, big.NewInt(1), new(big.Int).Exp(big.NewInt(2), PrivateKey.L, PrivateKey.N))
	require.Equal(t, PrivateKey.NSquared, new(big.Int).Exp(PrivateKey.N, big.NewInt(2), nil))

	plaintext := "Hello Paillier!"
	ciphertext, err := PrivateKey.Encrypt([]byte(plaintext))
	require.NoError(t, err)
	decrypted, err := PrivateKey.Decrypt(ciphertext)
	require.NoError(t, err)
	require.Equal(t, plaintext, string(decrypted))
}

119
120
121
func verifyKeyshareIsUnmarshaled(t *testing.T, manager *CredentialManager) {
	require.NotNil(t, manager.paillierKeyCache)
	require.NotNil(t, manager.keyshareServers)
122
	test := NewSchemeManagerIdentifier("test")
123
124
	require.Contains(t, manager.keyshareServers, test)
	kss := manager.keyshareServers[test]
125
126
127
	require.NotEmpty(t, kss.Nonce)

	verifyPaillierKey(t, kss.PrivateKey)
128
	verifyPaillierKey(t, manager.paillierKeyCache)
129
130
}

131
132
133
134
135
136
137
func verifyStoreIsLoaded(t *testing.T, store *ConfigurationStore, android bool) {
	require.Contains(t, store.SchemeManagers, NewSchemeManagerIdentifier("irma-demo"))
	require.Contains(t, store.SchemeManagers, NewSchemeManagerIdentifier("test"))
	if android {
		require.Contains(t, store.SchemeManagers, NewSchemeManagerIdentifier("test2"))
	}

138
139
140
141
142
	pk, err := store.PublicKey(NewIssuerIdentifier("irma-demo.RU"), 0)
	require.NoError(t, err)
	require.NotNil(t, pk)
	require.NotNil(t, pk.N, "irma-demo.RU public key has no modulus")
	require.Equal(t,
143
		"Irma Demo",
144
		store.SchemeManagers[NewSchemeManagerIdentifier("irma-demo")].Name["en"],
145
		"irma-demo scheme manager has unexpected name")
146
	require.Equal(t,
147
		"Radboud University Nijmegen",
148
		store.Issuers[NewIssuerIdentifier("irma-demo.RU")].Name["en"],
149
		"irma-demo.RU issuer has unexpected name")
150
	require.Equal(t,
151
		"Student Card",
152
		store.Credentials[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].ShortName["en"],
153
154
		"irma-demo.RU.studentCard has unexpected name")

155
	require.Equal(t,
156
		"studentID",
157
		store.Credentials[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].Attributes[2].ID,
158
159
160
161
		"irma-demo.RU.studentCard.studentID has unexpected name")

	// Hash algorithm pseudocode:
	// Base64(SHA256("irma-demo.RU.studentCard")[0:16])
162
	require.Contains(t, store.reverseHashes, "1stqlPad5edpfS1Na1U+DA==",
163
		"irma-demo.RU.studentCard had improper hash")
164
	require.Contains(t, store.reverseHashes, "CLjnADMBYlFcuGOT7Z0xRg==",
165
		"irma-demo.MijnOverheid.root had improper hash")
166
167
168
}

func TestAndroidParse(t *testing.T) {
169
	manager := parseStorage(t)
170
	verifyStoreIsLoaded(t, manager.ConfigurationStore, true)
171
	verifyManagerIsUnmarshaled(t, manager)
Sietse Ringers's avatar
Sietse Ringers committed
172
	verifyCredentials(t, manager)
173
	verifyKeyshareIsUnmarshaled(t, manager)
174
175
176
177
178

	teardown(t)
}

func TestUnmarshaling(t *testing.T) {
Sietse Ringers's avatar
Sietse Ringers committed
179
180
181
182
183
184
185
186
	manager := parseStorage(t)

	// Do session so we can examine its log item later
	logs, err := manager.Logs()
	require.NoError(t, err)
	jwt := getIssuanceJwt("testip", NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
	sessionHelper(t, jwt, "issue", manager)

187
	newmanager, err := NewCredentialManager("testdata/storage/test", "testdata/irma_configuration", "testdata/oldstorage", nil)
188
	require.NoError(t, err)
189
	verifyManagerIsUnmarshaled(t, newmanager)
Sietse Ringers's avatar
Sietse Ringers committed
190
	verifyCredentials(t, newmanager)
191
	verifyKeyshareIsUnmarshaled(t, newmanager)
Sietse Ringers's avatar
Sietse Ringers committed
192

Sietse Ringers's avatar
Sietse Ringers committed
193
194
195
196
197
198
199
200
201
	newlogs, err := newmanager.Logs()
	require.NoError(t, err)
	require.True(t, len(newlogs) == len(logs)+1)

	entry := newlogs[len(newlogs)-1]
	require.NotNil(t, entry)
	sessionjwt, _, err := entry.Jwt()
	require.NoError(t, err)
	require.Equal(t, "testip", sessionjwt.(*IdentityProviderJwt).ServerName)
Sietse Ringers's avatar
Sietse Ringers committed
202
203
204
	require.NoError(t, err)
	require.NotEmpty(t, entry.Disclosed)
	require.NotEmpty(t, entry.Received)
Sietse Ringers's avatar
Sietse Ringers committed
205
206
	response, err := entry.GetResponse()
	require.NoError(t, err)
Sietse Ringers's avatar
Sietse Ringers committed
207
208
	require.NotNil(t, response)
	require.IsType(t, &gabi.IssueCommitmentMessage{}, response)
Sietse Ringers's avatar
Sietse Ringers committed
209

Sietse Ringers's avatar
Sietse Ringers committed
210
	teardown(t)
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
}

func TestMetadataAttribute(t *testing.T) {
	metadata := NewMetadataAttribute()
	if metadata.Version() != 0x02 {
		t.Errorf("Unexpected metadata version: %d", metadata.Version())
	}

	expiry := metadata.SigningDate().Unix() + int64(metadata.ValidityDuration()*ExpiryFactor)
	if !time.Unix(expiry, 0).Equal(metadata.Expiry()) {
		t.Errorf("Invalid signing date")
	}

	if metadata.KeyCounter() != 0 {
		t.Errorf("Unexpected key counter")
	}
}

func TestMetadataCompatibility(t *testing.T) {
230
231
	store, err := NewConfigurationStore("testdata/irma_configuration", "")
	require.NoError(t, err)
232
	require.NoError(t, store.ParseFolder())
233
234

	// An actual metadata attribute of an IRMA credential extracted from the IRMA app
Sietse Ringers's avatar
Sietse Ringers committed
235
	attr := MetadataFromInt(s2big("49043481832371145193140299771658227036446546573739245068"), store)
236
	require.NotNil(t, attr.CredentialType(), "attr.CredentialType() should not be nil")
237

238
	require.Equal(t,
239
		NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
240
241
242
		attr.CredentialType().Identifier(),
		"Metadata credential type was not irma-demo.RU.studentCard",
	)
243
244
245
246
	require.Equal(t, byte(0x02), attr.Version(), "Unexpected metadata version")
	require.Equal(t, time.Unix(1499904000, 0), attr.SigningDate(), "Unexpected signing date")
	require.Equal(t, time.Unix(1516233600, 0), attr.Expiry(), "Unexpected expiry date")
	require.Equal(t, 2, attr.KeyCounter(), "Unexpected key counter")
Sietse Ringers's avatar
Sietse Ringers committed
247
248

	teardown(t)
249
}
250
251

func TestAttributeDisjunctionMarshaling(t *testing.T) {
252
253
254
	store, err := NewConfigurationStore("testdata/irma_configuration", "")
	require.NoError(t, err)
	require.NoError(t, store.ParseFolder())
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
	disjunction := AttributeDisjunction{}

	var _ json.Unmarshaler = &disjunction
	var _ json.Marshaler = &disjunction

	id := NewAttributeTypeIdentifier("MijnOverheid.ageLower.over18")

	attrsjson := `
	{
		"label": "Over 18",
		"attributes": {
			"MijnOverheid.ageLower.over18": "yes",
			"Thalia.age.over18": "Yes"
		}
	}`
	require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction))
	require.True(t, disjunction.HasValues())
	require.Contains(t, disjunction.Attributes, id)
	require.Contains(t, disjunction.Values, id)
	require.Equal(t, disjunction.Values[id], "yes")

	disjunction = AttributeDisjunction{}
	attrsjson = `
	{
		"label": "Over 18",
		"attributes": [
			"MijnOverheid.ageLower.over18",
			"Thalia.age.over18"
		]
	}`
	require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction))
	require.False(t, disjunction.HasValues())
	require.Contains(t, disjunction.Attributes, id)

Sietse Ringers's avatar
Sietse Ringers committed
289
	require.True(t, disjunction.MatchesStore(store))
290
291
292
293
294

	require.False(t, disjunction.Satisfied())
	disjunction.selected = &disjunction.Attributes[0]
	require.True(t, disjunction.Satisfied())
}
295
296

func TestCandidates(t *testing.T) {
297
	manager := parseStorage(t)
298
299
300
301
302

	attrtype := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
	disjunction := &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
	}
303
	attrs := manager.Candidates(disjunction)
304
305
306
307
308
309
310
311
312
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)

	attr := attrs[0]
	require.NotNil(t, attr)
	require.Equal(t, attr.Type, attrtype)

	disjunction = &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
313
		Values:     map[AttributeTypeIdentifier]string{attrtype: "456"},
314
	}
315
	attrs = manager.Candidates(disjunction)
316
317
318
319
320
321
322
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)

	disjunction = &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
		Values:     map[AttributeTypeIdentifier]string{attrtype: "foobarbaz"},
	}
323
	attrs = manager.Candidates(disjunction)
324
325
326
327
328
	require.NotNil(t, attrs)
	require.Empty(t, attrs)

	teardown(t)
}
329
330

func TestTimestamp(t *testing.T) {
331
332
	mytime := Timestamp(time.Unix(1500000000, 0))
	timestruct := struct{ Time *Timestamp }{Time: &mytime}
333
334
335
	bytes, err := json.Marshal(timestruct)
	require.NoError(t, err)

336
	timestruct = struct{ Time *Timestamp }{}
337
338
339
	require.NoError(t, json.Unmarshal(bytes, &timestruct))
	require.Equal(t, time.Time(*timestruct.Time).Unix(), int64(1500000000))
}
Sietse Ringers's avatar
Sietse Ringers committed
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383

func TestServiceProvider(t *testing.T) {
	var spjwt ServiceProviderJwt

	var spjson = `{
		"sprequest": {
			"validity": 60,
			"timeout": 60,
			"request": {
				"content": [
					{
						"label": "ID",
						"attributes": ["irma-demo.RU.studentCard.studentID"]
					}
				]
			}
		}
	}`

	require.NoError(t, json.Unmarshal([]byte(spjson), &spjwt))
	require.NotNil(t, spjwt.Request.Request.Content)
	require.NotEmpty(t, spjwt.Request.Request.Content)
	require.NotNil(t, spjwt.Request.Request.Content[0])
	require.NotEmpty(t, spjwt.Request.Request.Content[0])
	require.NotNil(t, spjwt.Request.Request.Content[0].Attributes)
	require.NotEmpty(t, spjwt.Request.Request.Content[0].Attributes)
	require.Equal(t, spjwt.Request.Request.Content[0].Attributes[0].Name(), "studentID")

	require.NotNil(t, spjwt.Request.Request.Content.Find(NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")))
}

func TestTransport(t *testing.T) {
	transport := NewHTTPTransport("https://xkcd.com")
	obj := &struct {
		Num   int    `json:"num"`
		Img   string `json:"img"`
		Title string `json:"title"`
	}{}

	err := transport.Get("614/info.0.json", obj)
	if err != nil { // require.NoError() does not work because of the type of err
		t.Fatalf("%+v\n", err)
	}
}
Sietse Ringers's avatar
Sietse Ringers committed
384
385

func TestPaillier(t *testing.T) {
386
	manager := parseStorage(t)
Sietse Ringers's avatar
Sietse Ringers committed
387
388
389
390
391

	challenge, _ := gabi.RandomBigInt(256)
	comm, _ := gabi.RandomBigInt(1000)
	resp, _ := gabi.RandomBigInt(1000)

392
	sk := manager.paillierKey(true)
Sietse Ringers's avatar
Sietse Ringers committed
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
	bytes, err := sk.Encrypt(challenge.Bytes())
	require.NoError(t, err)
	cipher := new(big.Int).SetBytes(bytes)

	bytes, err = sk.Encrypt(comm.Bytes())
	require.NoError(t, err)
	commcipher := new(big.Int).SetBytes(bytes)

	// [[ c ]]^resp * [[ comm ]]
	cipher.Exp(cipher, resp, sk.NSquared).Mul(cipher, commcipher).Mod(cipher, sk.NSquared)

	bytes, err = sk.Decrypt(cipher.Bytes())
	require.NoError(t, err)
	plaintext := new(big.Int).SetBytes(bytes)
	expected := new(big.Int).Set(challenge)
	expected.Mul(expected, resp).Add(expected, comm)

	require.Equal(t, plaintext, expected)

	teardown(t)
}
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439

func TestCredentialRemoval(t *testing.T) {
	manager := parseStorage(t)
	id := NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
	id2 := NewCredentialTypeIdentifier("test.test.mijnirma")

	cred, err := manager.credential(id, 0)
	require.NoError(t, err)
	require.NotNil(t, cred)
	err = manager.RemoveCredentialByHash(cred.AttributeList().hash())
	require.NoError(t, err)
	cred, err = manager.credential(id, 0)
	require.NoError(t, err)
	require.Nil(t, cred)

	cred, err = manager.credential(id2, 0)
	require.NoError(t, err)
	require.NotNil(t, cred)
	err = manager.RemoveCredential(id2, 0)
	require.NoError(t, err)
	cred, err = manager.credential(id2, 0)
	require.NoError(t, err)
	require.Nil(t, cred)

	teardown(t)
}