client.go 31.7 KB
Newer Older
1
package irmaclient
2
3

import (
4
	"strconv"
5
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
6

7
	"github.com/bwesterb/go-atum"
8
	"github.com/getsentry/raven-go"
Sietse Ringers's avatar
Sietse Ringers committed
9
	"github.com/go-errors/errors"
10
11
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
12
13
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
14
15
)

16
// This file contains most methods of the Client (c.f. session.go
Sietse Ringers's avatar
Sietse Ringers committed
17
18
// and updates.go).
//
Sietse Ringers's avatar
Sietse Ringers committed
19
20
21
22
23
24
25
26
// Clients are the main entry point into this package for the user of this package.
// The Client struct:
// - (De)serializes credentials and keyshare server information
// from storage, as well as logs of earlier IRMA sessions
// - it provides access to the attributes and all related information of its credentials,
// - it is the starting point for new IRMA sessions;
// - and it computes some of the messages in the client side of the IRMA protocol.
//
Sietse Ringers's avatar
Sietse Ringers committed
27
28
29
30
31
32
33
// The storage of credentials is split up in several parts:
//
// - The CL-signature of each credential is stored separately, so that we can
// load it on demand (i.e., during an IRMA session), instead of immediately
// at initialization.
//
// - The attributes of all credentials are stored together, as they all
34
// immediately need to be available anyway.
Sietse Ringers's avatar
Sietse Ringers committed
35
36
37
38
39
//
// - The secret key (the zeroth attribute of every credential), being the same
// across all credentials, is stored only once in a separate file (storing this
// in multiple places would be bad).

40
type Client struct {
Sietse Ringers's avatar
Sietse Ringers committed
41
	// Stuff we manage on disk
42
	secretkey        *secretKey
43
	attributes       map[irma.CredentialTypeIdentifier][]*irma.AttributeList
44
	credentialsCache map[irma.CredentialTypeIdentifier]map[int]*credential
45
	keyshareServers  map[irma.SchemeManagerIdentifier]*keyshareServer
Sietse Ringers's avatar
Sietse Ringers committed
46
	logs             []*LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
47
	updates          []update
48

Sietse Ringers's avatar
Sietse Ringers committed
49
50
51
52
	// Where we store/load it to/from
	storage storage

	// Other state
53
54
55
56
57
	Preferences           Preferences
	Configuration         *irma.Configuration
	irmaConfigurationPath string
	androidStoragePath    string
	handler               ClientHandler
58
59
}

60
61
62
63
// SentryDSN should be set in the init() function
// Setting it to an empty string means no crash reports
var SentryDSN = ""

64
65
type Preferences struct {
	EnableCrashReporting bool
66
67
}

68
var defaultPreferences = Preferences{
69
	EnableCrashReporting: true,
70
71
}

72
// KeyshareHandler is used for asking the user for his email address and PIN,
73
// for enrolling at a keyshare server.
74
type KeyshareHandler interface {
Tomas's avatar
Tomas committed
75
	EnrollmentFailure(manager irma.SchemeManagerIdentifier, err error)
76
	EnrollmentSuccess(manager irma.SchemeManagerIdentifier)
77
78
}

79
80
81
type ChangePinHandler interface {
	ChangePinFailure(manager irma.SchemeManagerIdentifier, err error)
	ChangePinSuccess(manager irma.SchemeManagerIdentifier)
82
83
	ChangePinIncorrect(manager irma.SchemeManagerIdentifier, attempts int)
	ChangePinBlocked(manager irma.SchemeManagerIdentifier, timeout int)
84
85
}

Sietse Ringers's avatar
Sietse Ringers committed
86
87
// ClientHandler informs the user that the configuration or the list of attributes
// that this client uses has been updated.
88
89
type ClientHandler interface {
	KeyshareHandler
90
	ChangePinHandler
91

92
	UpdateConfiguration(new *irma.IrmaIdentifierSet)
93
	UpdateAttributes()
Sietse Ringers's avatar
Sietse Ringers committed
94
95
}

96
97
98
99
type secretKey struct {
	Key *big.Int
}

100
// New creates a new Client that uses the directory
101
102
103
104
// specified by storagePath for (de)serializing itself. irmaConfigurationPath
// is the path to a (possibly readonly) folder containing irma_configuration;
// androidStoragePath is an optional path to the files of the old android app
// (specify "" if you do not want to parse the old android app files),
105
// and handler is used for informing the user of new stuff, and when a
106
// enrollment to a keyshare server needs to happen.
107
// The client returned by this function has been fully deserialized
108
109
// and is ready for use.
//
Sietse Ringers's avatar
Sietse Ringers committed
110
111
// NOTE: It is the responsibility of the caller that there exists a (properly
// protected) directory at storagePath!
112
func New(
113
114
115
	storagePath string,
	irmaConfigurationPath string,
	androidStoragePath string,
116
	handler ClientHandler,
117
) (*Client, error) {
118
	var err error
119
	if err = fs.AssertPathExists(storagePath); err != nil {
120
121
		return nil, err
	}
122
	if err = fs.AssertPathExists(irmaConfigurationPath); err != nil {
123
124
125
		return nil, err
	}

126
	cm := &Client{
127
		credentialsCache:      make(map[irma.CredentialTypeIdentifier]map[int]*credential),
128
129
		keyshareServers:       make(map[irma.SchemeManagerIdentifier]*keyshareServer),
		attributes:            make(map[irma.CredentialTypeIdentifier][]*irma.AttributeList),
130
131
		irmaConfigurationPath: irmaConfigurationPath,
		androidStoragePath:    androidStoragePath,
132
		handler:               handler,
133
134
	}

135
	cm.Configuration, err = irma.NewConfigurationFromAssets(storagePath+"/irma_configuration", irmaConfigurationPath)
136
137
138
	if err != nil {
		return nil, err
	}
139

140
	schemeMgrErr := cm.Configuration.ParseOrRestoreFolder()
141
142
143
144
145
	// If schemMgrErr is of type SchemeManagerError, we continue and
	// return it at the end; otherwise bail out now
	_, isSchemeMgrErr := schemeMgrErr.(*irma.SchemeManagerError)
	if schemeMgrErr != nil && !isSchemeMgrErr {
		return nil, schemeMgrErr
146
	}
147
148

	// Ensure storage path exists, and populate it with necessary files
149
	cm.storage = storage{storagePath: storagePath, Configuration: cm.Configuration}
Sietse Ringers's avatar
Sietse Ringers committed
150
	if err = cm.storage.EnsureStorageExists(); err != nil {
151
152
153
		return nil, err
	}

154
	if cm.Preferences, err = cm.storage.LoadPreferences(); err != nil {
155
156
		return nil, err
	}
157
	cm.applyPreferences()
158

159
	// Perform new update functions from clientUpdates, if any
160
161
162
163
164
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
Sietse Ringers's avatar
Sietse Ringers committed
165
	if cm.secretkey, err = cm.storage.LoadSecretKey(); err != nil {
166
167
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
168
	if cm.attributes, err = cm.storage.LoadAttributes(); err != nil {
169
170
		return nil, err
	}
171
	if cm.keyshareServers, err = cm.storage.LoadKeyshareServers(); err != nil {
172
173
174
		return nil, err
	}

175
	if len(cm.UnenrolledSchemeManagers()) > 1 {
176
177
178
		return nil, errors.New("Too many keyshare servers")
	}

179
	return cm, schemeMgrErr
180
181
}

182
// CredentialInfoList returns a list of information of all contained credentials.
183
184
func (client *Client) CredentialInfoList() irma.CredentialInfoList {
	list := irma.CredentialInfoList([]*irma.CredentialInfo{})
185

186
	for _, attrlistlist := range client.attributes {
187
		for _, attrlist := range attrlistlist {
188
			info := attrlist.Info()
189
190
191
			if info == nil {
				continue
			}
192
			list = append(list, info)
Sietse Ringers's avatar
Sietse Ringers committed
193
194
		}
	}
195

Sietse Ringers's avatar
Sietse Ringers committed
196
197
198
	return list
}

199
// addCredential adds the specified credential to the Client, saving its signature
Sietse Ringers's avatar
Sietse Ringers committed
200
// imediately, and optionally cm.attributes as well.
201
func (client *Client) addCredential(cred *credential, storeAttributes bool) (err error) {
202
203
204
205
	id := irma.NewCredentialTypeIdentifier("")
	if cred.CredentialType() != nil {
		id = cred.CredentialType().Identifier()
	}
Sietse Ringers's avatar
Sietse Ringers committed
206

207
	// Don't add duplicate creds
208
	for _, attrlistlist := range client.attributes {
209
		for _, attrs := range attrlistlist {
210
			if attrs.Hash() == cred.AttributeList().Hash() {
211
212
213
214
215
216
				return nil
			}
		}
	}

	// If this is a singleton credential type, ensure we have at most one by removing any previous instance
217
218
219
220
	if !id.Empty() && cred.CredentialType().IsSingleton {
		for len(client.attrs(id)) != 0 {
			client.remove(id, 0, false)
		}
221
222
223
	}

	// Append the new cred to our attributes and credentials
224
	client.attributes[id] = append(client.attrs(id), cred.AttributeList())
225
	if !id.Empty() {
226
227
		if _, exists := client.credentialsCache[id]; !exists {
			client.credentialsCache[id] = make(map[int]*credential)
228
229
		}
		counter := len(client.attributes[id]) - 1
230
		client.credentialsCache[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
231
232
	}

233
	if err = client.storage.StoreSignature(cred); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
234
235
236
		return
	}
	if storeAttributes {
237
		err = client.storage.StoreAttributes(client.attributes)
Sietse Ringers's avatar
Sietse Ringers committed
238
239
240
241
242
243
244
245
246
247
248
249
250
251
	}
	return
}

func generateSecretKey() (*secretKey, error) {
	key, err := gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
	if err != nil {
		return nil, err
	}
	return &secretKey{Key: key}, nil
}

// Removal methods

252
func (client *Client) remove(id irma.CredentialTypeIdentifier, index int, storenow bool) error {
253
	// Remove attributes
254
	list, exists := client.attributes[id]
255
256
257
258
	if !exists || index >= len(list) {
		return errors.Errorf("Can't remove credential %s-%d: no such credential", id.String(), index)
	}
	attrs := list[index]
259
	client.attributes[id] = append(list[:index], list[index+1:]...)
260
	if storenow {
261
		if err := client.storage.StoreAttributes(client.attributes); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
262
263
			return err
		}
264
265
266
	}

	// Remove credential
267
	if creds, exists := client.credentialsCache[id]; exists {
268
		if _, exists := creds[index]; exists {
Sietse Ringers's avatar
Sietse Ringers committed
269
			delete(creds, index)
270
			client.credentialsCache[id] = creds
271
272
273
274
		}
	}

	// Remove signature from storage
275
	if err := client.storage.DeleteSignature(attrs); err != nil {
276
277
278
		return err
	}

279
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
280
281
282
	removed[id] = attrs.Strings()

	if storenow {
283
		return client.addLogEntry(&LogEntry{
284
			Type:    actionRemoval,
285
			Time:    irma.Timestamp(time.Now()),
286
287
288
289
			Removed: removed,
		})
	}
	return nil
290
291
}

Sietse Ringers's avatar
Sietse Ringers committed
292
// RemoveCredential removes the specified credential.
293
func (client *Client) RemoveCredential(id irma.CredentialTypeIdentifier, index int) error {
294
	return client.remove(id, index, true)
295
296
}

Sietse Ringers's avatar
Sietse Ringers committed
297
// RemoveCredentialByHash removes the specified credential.
298
299
func (client *Client) RemoveCredentialByHash(hash string) error {
	cred, index, err := client.credentialByHash(hash)
300
301
302
	if err != nil {
		return err
	}
303
	return client.RemoveCredential(cred.CredentialType().Identifier(), index)
304
305
}

Sietse Ringers's avatar
Sietse Ringers committed
306
// RemoveAllCredentials removes all credentials.
307
func (client *Client) RemoveAllCredentials() error {
308
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
309
	for _, attrlistlist := range client.attributes {
Sietse Ringers's avatar
Sietse Ringers committed
310
311
312
313
		for _, attrs := range attrlistlist {
			if attrs.CredentialType() != nil {
				removed[attrs.CredentialType().Identifier()] = attrs.Strings()
			}
314
			client.storage.DeleteSignature(attrs)
315
316
		}
	}
317
	client.attributes = map[irma.CredentialTypeIdentifier][]*irma.AttributeList{}
318
	if err := client.storage.StoreAttributes(client.attributes); err != nil {
319
320
		return err
	}
321
322
323

	logentry := &LogEntry{
		Type:    actionRemoval,
324
		Time:    irma.Timestamp(time.Now()),
325
326
		Removed: removed,
	}
327
	if err := client.addLogEntry(logentry); err != nil {
328
329
		return err
	}
330
	return client.storage.StoreLogs(client.logs)
331
332
}

333
// Attribute and credential getter methods
Sietse Ringers's avatar
Sietse Ringers committed
334

Sietse Ringers's avatar
Sietse Ringers committed
335
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
336
func (client *Client) attrs(id irma.CredentialTypeIdentifier) []*irma.AttributeList {
337
	list, exists := client.attributes[id]
Sietse Ringers's avatar
Sietse Ringers committed
338
	if !exists {
339
		list = make([]*irma.AttributeList, 0, 1)
340
		client.attributes[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
341
342
343
344
345
	}
	return list
}

// creds returns cm.credentials[id], initializing it to an empty map if neccesary
346
func (client *Client) creds(id irma.CredentialTypeIdentifier) map[int]*credential {
347
	list, exists := client.credentialsCache[id]
Sietse Ringers's avatar
Sietse Ringers committed
348
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
349
		list = make(map[int]*credential)
350
		client.credentialsCache[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
351
352
353
354
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
355
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
356
func (client *Client) Attributes(id irma.CredentialTypeIdentifier, counter int) (attributes *irma.AttributeList) {
357
	list := client.attrs(id)
Sietse Ringers's avatar
Sietse Ringers committed
358
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
359
360
361
362
363
		return
	}
	return list[counter]
}

364
func (client *Client) attributesByHash(hash string) (*irma.AttributeList, int) {
365
	for _, attrlistlist := range client.attributes {
366
		for index, attrs := range attrlistlist {
367
			if attrs.Hash() == hash {
368
				return attrs, index
369
370
371
			}
		}
	}
372
373
374
375
376
377
378
379
380
	return nil, 0
}

func (client *Client) credentialByHash(hash string) (*credential, int, error) {
	attrs, index := client.attributesByHash(hash)
	if attrs != nil {
		cred, err := client.credential(attrs.CredentialType().Identifier(), index)
		return cred, index, err
	}
381
382
383
	return nil, 0, nil
}

384
func (client *Client) credentialByID(id irma.CredentialIdentifier) (*credential, error) {
385
	if _, exists := client.attributes[id.Type]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
386
387
		return nil, nil
	}
388
	for index, attrs := range client.attributes[id.Type] {
389
		if attrs.Hash() == id.Hash {
390
			return client.credential(attrs.CredentialType().Identifier(), index)
Sietse Ringers's avatar
Sietse Ringers committed
391
392
393
		}
	}
	return nil, nil
394
395
}

Sietse Ringers's avatar
Sietse Ringers committed
396
// credential returns the requested credential, or nil if we do not have it.
397
func (client *Client) credential(id irma.CredentialTypeIdentifier, counter int) (cred *credential, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
398
	// If the requested credential is not in credential map, we check if its attributes were
399
	// deserialized during New(). If so, there should be a corresponding signature file,
Sietse Ringers's avatar
Sietse Ringers committed
400
	// so we read that, construct the credential, and add it to the credential map
401
402
	if _, exists := client.creds(id)[counter]; !exists {
		attrs := client.Attributes(id, counter)
Sietse Ringers's avatar
Sietse Ringers committed
403
404
405
		if attrs == nil { // We do not have the requested cred
			return
		}
406
		sig, err := client.storage.LoadSignature(attrs)
Sietse Ringers's avatar
Sietse Ringers committed
407
408
409
410
411
412
413
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
414
		pk, err := attrs.PublicKey()
415
416
417
		if err != nil {
			return nil, err
		}
418
419
420
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
421
		cred, err := newCredential(&gabi.Credential{
422
			Attributes: append([]*big.Int{client.secretkey.Key}, attrs.Ints...),
423
			Signature:  sig,
424
			Pk:         pk,
425
		}, client.Configuration)
426
427
428
		if err != nil {
			return nil, err
		}
429
		client.credentialsCache[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
430
431
	}

432
	return client.credentialsCache[id][counter], nil
433
434
}

Sietse Ringers's avatar
Sietse Ringers committed
435
// Methods used in the IRMA protocol
436

437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
// credCandidates returns a list containing a list of candidate credential instances for each item
// in the conjunction. (A credential instance from the client is a candidate it it contains
// attributes required in this conjunction). If one credential type occurs multiple times in the
// conjunction it is not added twice.
func (client *Client) credCandidates(con irma.AttributeCon) credCandidateSet {
	var candidates [][]*irma.CredentialIdentifier
	for _, credtype := range con.CredentialTypes() {
		creds := client.attributes[credtype]
		if len(creds) == 0 {
			return nil // we'll need at least one instance of each credtype in this conjunction
		}
		var c []*irma.CredentialIdentifier
		for _, cred := range creds {
			if !cred.IsValid() {
				continue
			}
			c = append(c, &irma.CredentialIdentifier{Type: credtype, Hash: cred.Hash()})
		}
		candidates = append(candidates, c)
	}
	return candidates
}

type credCandidateSet [][]*irma.CredentialIdentifier
461

462
463
464
465
466
func (set credCandidateSet) multiply(candidates []*irma.CredentialIdentifier) credCandidateSet {
	result := make(credCandidateSet, 0, len(set)*len(candidates))
	for _, cred := range candidates {
		for _, toDisclose := range set {
			result = append(result, append(toDisclose, cred))
467
		}
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
	}
	return result
}

func (set credCandidateSet) expand(client *Client, con irma.AttributeCon) [][]*irma.AttributeIdentifier {
	var result [][]*irma.AttributeIdentifier

outer:
	for _, s := range set {
		var candidateSet []*irma.AttributeIdentifier
		for _, cred := range s {
			for _, attr := range con {
				if attr.Type.CredentialTypeIdentifier() == cred.Type {
					id := &irma.AttributeIdentifier{Type: attr.Type, CredentialHash: cred.Hash}
					if attr.Value != nil {
						attrs, _ := client.attributesByHash(cred.Hash)
						val := attrs.UntranslatedAttribute(id.Type)
						if val == nil || *attr.Value != *val {
							continue outer
						}
					}
					candidateSet = append(candidateSet, id)
				}
			}
		}
		result = append(result, candidateSet)
	}

	return result
}

func cartesianProduct(candidates [][]*irma.CredentialIdentifier) credCandidateSet {
	set := credCandidateSet{[]*irma.CredentialIdentifier{}} // Unit element for this multiplication
	for _, c := range candidates {
		set = set.multiply(c)
	}
	return set
}

// Candidates returns attributes present in this client that satisfy the specified attribute
// disjunction. It returns a list of candidate attribute sets, each of which would satisfy the
// specified disjunction. If the disjunction cannot be satisfied by the attributes that the client
// currently posesses (ie. len(candidates) == 0), then the second return parameter lists the missing
// attributes that would be necessary to satisfy the disjunction.
func (client *Client) Candidates(discon irma.AttributeDisCon) (
	candidates [][]*irma.AttributeIdentifier, missing map[int]irma.AttributeCon,
) {
	candidates = [][]*irma.AttributeIdentifier{}

	for _, con := range discon {
		// Build a list containing, for each attribute in this conjunction, a list of credential
		// instances containing the attribute. Writing schematically a sample conjunction of three
		// attribute types as [ a.a.a.a, a.a.a.b, a.a.b.x ], we map this to:
		// [ [ a.a.a #1, a.a.a #2] , [ a.a.b #1 ] ]
		// assuming the client has 2 instances of a.a.a and 1 instance of a.a.b.
		c := client.credCandidates(con)
		if len(c) == 0 {
525
526
			continue
		}
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558

		// The cartesian product of the list of lists constructed above results in a list of which
		// each item is a list of credentials containing attributes that together will satisfy the
		// current conjunction
		// [ [ a.a.a #1, a.a.b #1 ], [ a.a.a #2, a.a.b #1 ] ]
		c = cartesianProduct(c)

		// Expand each credential instance to those attribute instances within it that the con
		// is asking for, resulting in attribute sets each of which would satisfy the conjunction,
		// and therefore the containing disjunction
		// [ [ a.a.a.a #1, a.a.a.b #1, a.a.b.x #1 ], [ a.a.a.a #2, a.a.a.b #2, a.a.b.x #1 ] ]
		candidates = append(candidates, c.expand(client, con)...)
	}

	if len(candidates) == 0 {
		missing = client.missingAttributes(discon)
	}

	return
}

// missingAttributes returns for each of the conjunctions in the specified disjunction
// a list of attributes that the client does not posess but which would be required to
// satisfy the conjunction.
func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]irma.AttributeCon {
	missing := map[int]irma.AttributeCon{}

	for i, con := range discon {
		for _, attr := range con {
			creds := client.attributes[attr.Type.CredentialTypeIdentifier()]
			if len(creds) == 0 {
				missing[i] = append(missing[i], attr)
559
560
				continue
			}
561
562
			for _, cred := range creds {
				if attr.Satisfy(attr.Type, cred.UntranslatedAttribute(attr.Type)) {
563
564
565
					continue
				}
			}
566
			missing[i] = append(missing[i], attr)
567
568
569
		}
	}

570
	return missing
571
572
}

573
// CheckSatisfiability checks if this client has the required attributes
Sietse Ringers's avatar
Sietse Ringers committed
574
575
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
576
577
578
579
580
581
582
583
584
func (client *Client) CheckSatisfiability(condiscon irma.AttributeConDisCon) (
	candidates [][][]*irma.AttributeIdentifier, missing map[int]map[int]irma.AttributeCon,
) {
	candidates = make([][][]*irma.AttributeIdentifier, len(condiscon))
	missing = map[int]map[int]irma.AttributeCon{}

	for i, discon := range condiscon {
		var m map[int]irma.AttributeCon
		candidates[i], m = client.Candidates(discon)
585
		if len(candidates[i]) == 0 {
586
			missing[i] = m
587
588
		}
	}
589
590

	return
591
}
592

593
594
595
596
597
598
599
600
601
602
// attributeGroup points to a credential and some of its attributes which are to be disclosed
type attributeGroup struct {
	cred  irma.CredentialIdentifier
	attrs []int
}

// Given the user's choice of attributes to be disclosed, group them per credential out of which they
// are to be disclosed
func (client *Client) groupCredentials(choice *irma.DisclosureChoice) (
	[]attributeGroup, irma.DisclosedAttributeIndices, error,
603
) {
604
	todisclose := make([]attributeGroup, 0, 10)
605
	if choice == nil || choice.Attributes == nil {
606
		return todisclose, irma.DisclosedAttributeIndices{}, nil
607
	}
608

609
610
	// maps an irma.CredentialIdentifier to its index in the final ProofList
	credIndices := make(map[irma.CredentialIdentifier]int)
611
	attributeIndices := make(irma.DisclosedAttributeIndices, len(choice.Attributes))
612
613
614
615
616
617
618
619
620
621
622
623
624
625
	for i, attributeset := range choice.Attributes {
		attributeIndices[i] = []*irma.DisclosedAttributeIndex{}
		for _, attribute := range attributeset {
			var credIndex int
			ici := attribute.CredentialIdentifier()
			if _, present := credIndices[ici]; !present {
				credIndex = len(todisclose)
				credIndices[ici] = credIndex
				todisclose = append(todisclose, attributeGroup{
					cred: ici, attrs: []int{1}, // Always disclose metadata
				})
			} else {
				credIndex = credIndices[ici]
			}
626

627
628
629
630
			identifier := attribute.Type
			if identifier.IsCredential() {
				attributeIndices[i] = append(attributeIndices[i], &irma.DisclosedAttributeIndex{CredentialIndex: credIndex, AttributeIndex: 1, Identifier: ici})
				continue // In this case we only disclose the metadata attribute, which is already handled above
631
			}
632

633
634
635
636
637
638
639
640
			attrIndex, err := client.Configuration.CredentialTypes[identifier.CredentialTypeIdentifier()].IndexOf(identifier)
			if err != nil {
				return nil, nil, err
			}
			// These attribute indices will be used in the []*big.Int at gabi.credential.Attributes,
			// which doesn't know about the secret key and metadata attribute, so +2
			attributeIndices[i] = append(attributeIndices[i], &irma.DisclosedAttributeIndex{CredentialIndex: credIndex, AttributeIndex: attrIndex + 2, Identifier: ici})
			todisclose[credIndex].attrs = append(todisclose[credIndex].attrs, attrIndex+2)
641
642
643
		}
	}

644
	return todisclose, attributeIndices, nil
645
646
}

647
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
648
649
func (client *Client) ProofBuilders(choice *irma.DisclosureChoice, request irma.SessionRequest,
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *atum.Timestamp, error) {
650
	todisclose, attributeIndices, err := client.groupCredentials(choice)
651
	if err != nil {
652
		return nil, nil, nil, err
653
654
	}

655
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
656
657
	for _, grp := range todisclose {
		cred, err := client.credentialByID(grp.cred)
658
		if err != nil {
659
			return nil, nil, nil, err
660
		}
661
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(grp.attrs))
662
	}
663

664
665
	var timestamp *atum.Timestamp
	if r, ok := request.(*irma.SignatureRequest); ok {
666
667
668
669
670
671
672
673
674
		var sigs []*big.Int
		var disclosed [][]*big.Int
		var s *big.Int
		var d []*big.Int
		for _, builder := range builders {
			s, d = builder.(*gabi.DisclosureProofBuilder).TimestampRequestContributions()
			sigs = append(sigs, s)
			disclosed = append(disclosed, d)
		}
675
		timestamp, err = irma.GetTimestamp(r.Message, sigs, disclosed)
676
		if err != nil {
677
			return nil, nil, nil, err
678
679
680
		}
	}

681
	return builders, attributeIndices, timestamp, nil
682
683
684
}

// Proofs computes disclosure proofs containing the attributes specified by choice.
685
686
func (client *Client) Proofs(choice *irma.DisclosureChoice, request irma.SessionRequest) (*irma.Disclosure, *atum.Timestamp, error) {
	builders, choices, timestamp, err := client.ProofBuilders(choice, request)
687
	if err != nil {
688
		return nil, nil, err
689
690
	}

691
	_, issig := request.(*irma.SignatureRequest)
692
	return &irma.Disclosure{
693
		Proofs:  builders.BuildProofList(request.Base().GetContext(), request.GetNonce(timestamp), issig),
694
		Indices: choices,
695
	}, timestamp, nil
Sietse Ringers's avatar
Sietse Ringers committed
696
697
}

698
699
700
701
702
// generateIssuerProofNonce generates a nonce which the issuer must use in its gabi.ProofS.
func generateIssuerProofNonce() (*big.Int, error) {
	return gabi.RandomBigInt(gabi.DefaultSystemParameters[4096].Lstatzk)
}

703
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
704
705
// for the future credentials as well as possibly any disclosed attributes, and generates
// a nonce against which the issuer's proof of knowledge must verify.
706
func (client *Client) IssuanceProofBuilders(request *irma.IssuanceRequest, choice *irma.DisclosureChoice,
707
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
708
	issuerProofNonce, err := generateIssuerProofNonce()
Sietse Ringers's avatar
Sietse Ringers committed
709
	if err != nil {
710
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
711
	}
712
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
713
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
714
		var pk *gabi.PublicKey
715
		pk, err = client.Configuration.PublicKey(futurecred.CredentialTypeID.IssuerIdentifier(), futurecred.KeyCounter)
716
		if err != nil {
717
			return nil, nil, nil, err
718
		}
719
		credBuilder := gabi.NewCredentialBuilder(
720
721
			pk, request.GetContext(), client.secretkey.Key, issuerProofNonce)
		builders = append(builders, credBuilder)
Sietse Ringers's avatar
Sietse Ringers committed
722
	}
Sietse Ringers's avatar
Sietse Ringers committed
723

724
	disclosures, choices, _, err := client.ProofBuilders(choice, request)
Sietse Ringers's avatar
Sietse Ringers committed
725
	if err != nil {
726
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
727
	}
728
	builders = append(disclosures, builders...)
729
	return builders, choices, issuerProofNonce, nil
Sietse Ringers's avatar
Sietse Ringers committed
730
}
Sietse Ringers's avatar
Sietse Ringers committed
731

732
733
// IssueCommitments computes issuance commitments, along with disclosure proofs specified by choice,
// and also returns the credential builders which will become the new credentials upon combination with the issuer's signature.
734
func (client *Client) IssueCommitments(request *irma.IssuanceRequest, choice *irma.DisclosureChoice,
735
) (*irma.IssueCommitmentMessage, gabi.ProofBuilderList, error) {
736
	builders, choices, issuerProofNonce, err := client.IssuanceProofBuilders(request, choice)
Sietse Ringers's avatar
Sietse Ringers committed
737
	if err != nil {
738
		return nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
739
	}
740
741
	return &irma.IssueCommitmentMessage{
		IssueCommitmentMessage: &gabi.IssueCommitmentMessage{
742
			Proofs: builders.BuildProofList(request.GetContext(), request.GetNonce(nil), false),
743
744
745
746
			Nonce2: issuerProofNonce,
		},
		Indices: choices,
	}, builders, nil
Sietse Ringers's avatar
Sietse Ringers committed
747
748
}

749
750
751
752
// ConstructCredentials constructs and saves new credentials using the specified issuance signature messages
// and credential builders.
func (client *Client) ConstructCredentials(msg []*gabi.IssueSignatureMessage, request *irma.IssuanceRequest, builders gabi.ProofBuilderList) error {
	if len(msg) > len(builders) {
Sietse Ringers's avatar
Sietse Ringers committed
753
754
755
		return errors.New("Received unexpected amount of signatures")
	}

756
757
	// First collect all credentials in a slice, so that if one of them induces an error,
	// we save none of them to fail the session cleanly
758
	gabicreds := []*gabi.Credential{}
759
760
761
762
763
764
765
766
	offset := 0
	for i, builder := range builders {
		credbuilder, ok := builder.(*gabi.CredentialBuilder)
		if !ok { // Skip builders of disclosure proofs
			offset++
			continue
		}
		sig := msg[i-offset]
767
		attrs, err := request.Credentials[i-offset].AttributeList(client.Configuration, irma.GetMetadataVersion(request.Base().ProtocolVersion))
Sietse Ringers's avatar
Sietse Ringers committed
768
769
770
		if err != nil {
			return err
		}
771
		cred, err := credbuilder.ConstructCredential(sig, attrs.Ints)
Sietse Ringers's avatar
Sietse Ringers committed
772
773
774
		if err != nil {
			return err
		}
775
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
776
777
	}

778
	for _, gabicred := range gabicreds {
779
		newcred, err := newCredential(gabicred, client.Configuration)
780
781
782
		if err != nil {
			return err
		}
783
		if err = client.addCredential(newcred, true); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
784
785
			return err
		}
Sietse Ringers's avatar
Sietse Ringers committed
786
	}
787

Sietse Ringers's avatar
Sietse Ringers committed
788
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
789
}
790

Sietse Ringers's avatar
Sietse Ringers committed
791
792
// Keyshare server handling

793
func (client *Client) genSchemeManagersList(enrolled bool) []irma.SchemeManagerIdentifier {
794
	list := []irma.SchemeManagerIdentifier{}
795
	for name, manager := range client.Configuration.SchemeManagers {
796
		if _, contains := client.keyshareServers[name]; manager.Distributed() && contains == enrolled {
Sietse Ringers's avatar
Sietse Ringers committed
797
			list = append(list, manager.Identifier())
Sietse Ringers's avatar
Sietse Ringers committed
798
799
800
801
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
802

803
804
805
806
func (client *Client) UnenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	return client.genSchemeManagersList(false)
}

807
func (client *Client) EnrolledSchemeManagers() []irma.SchemeManagerIdentifier {
808
	return client.genSchemeManagersList(true)
809
810
}

811
// KeyshareEnroll attempts to enroll at the keyshare server of the specified scheme manager.
812
func (client *Client) KeyshareEnroll(manager irma.SchemeManagerIdentifier, email *string, pin string, lang string) {
Sietse Ringers's avatar
Sietse Ringers committed
813
	go func() {
814
		err := client.keyshareEnrollWorker(manager, email, pin, lang)
Sietse Ringers's avatar
Sietse Ringers committed
815
		if err != nil {
Tomas's avatar
Tomas committed
816
			client.handler.EnrollmentFailure(manager, err)
Sietse Ringers's avatar
Sietse Ringers committed
817
818
		}
	}()
819
820
}

821
func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifier, email *string, pin string, lang string) error {
822
	manager, ok := client.Configuration.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
823
824
825
826
827
828
829
830
831
832
	if !ok {
		return errors.New("Unknown scheme manager")
	}
	if len(manager.KeyshareServer) == 0 {
		return errors.New("Scheme manager has no keyshare server")
	}
	if len(pin) < 5 {
		return errors.New("PIN too short, must be at least 5 characters")
	}

833
	transport := irma.NewHTTPTransport(manager.KeyshareServer)
834
	kss, err := newKeyshareServer(managerID, manager.KeyshareServer)
Sietse Ringers's avatar
Sietse Ringers committed
835
836
837
	if err != nil {
		return err
	}
838
	message := keyshareEnrollment{
839
840
841
		Email:    email,
		Pin:      kss.HashedPin(pin),
		Language: lang,
Sietse Ringers's avatar
Sietse Ringers committed
842
843
	}

844
845
	qr := &irma.Qr{}
	err = transport.Post("client/register", qr, message)
Sietse Ringers's avatar
Sietse Ringers committed
846
847
848
849
	if err != nil {
		return err
	}

850
851
852
853
854
	// We add the new keyshare server to the client here, without saving it to disk,
	// and start the issuance session for the keyshare server login attribute -
	// keyshare.go needs the relevant keyshare server to be present in the client.
	// If the session succeeds or fails, the keyshare server is stored to disk or
	// removed from the client by the keyshareEnrollmentHandler.
855
	client.keyshareServers[managerID] = kss
856
	client.newQrSession(qr, &keyshareEnrollmentHandler{
857
858
859
860
861
862
		client: client,
		pin:    pin,
		kss:    kss,
	})

	return nil
Sietse Ringers's avatar
Sietse Ringers committed
863
864
}

865
866
867
// KeyshareVerifyPin verifies the specified PIN at the keyshare server, returning if it succeeded;
// if not, how many tries are left, or for how long the user is blocked. If an error is returned
// it is of type *irma.SessionError.
868
869
870
func (client *Client) KeyshareVerifyPin(pin string, schemeid irma.SchemeManagerIdentifier) (bool, int, int, error) {
	scheme := client.Configuration.SchemeManagers[schemeid]
	if scheme == nil || !scheme.Distributed() {
871
872
873
874
875
		return false, 0, 0, &irma.SessionError{
			Err:       errors.Errorf("Can't verify pin of scheme %s", schemeid.String()),
			ErrorType: irma.ErrorUnknownSchemeManager,
			Info:      schemeid.String(),
		}
876
877
878
879
880
	}
	kss := client.keyshareServers[schemeid]
	return verifyPinWorker(pin, kss, irma.NewHTTPTransport(kss.URL))
}

881
func (client *Client) KeyshareChangePin(manager irma.SchemeManagerIdentifier, oldPin string, newPin string) {
882
	go func() {
883
		err := client.keyshareChangePinWorker(manager, oldPin, newPin)
884
		if err != nil {
885
			client.handler.ChangePinFailure(manager, err)
886
887
888
889
		}
	}()
}

890
func (client *Client) keyshareChangePinWorker(managerID irma.SchemeManagerIdentifier, oldPin string, newPin string) error {
891
892
893
894
895
896
	kss, ok := client.keyshareServers[managerID]
	if !ok {
		return errors.New("Unknown keyshare server")
	}

	transport := irma.NewHTTPTransport(kss.URL)
897
	message := keyshareChangepin{
898
		Username: kss.Username,
899
900
		OldPin:   kss.HashedPin(oldPin),
		NewPin:   kss.HashedPin(newPin),
901
902
903
904
905
906
907
	}

	res := &keysharePinStatus{}
	err := transport.Post("users/change/pin", res, message)
	if err != nil {
		return err
	}
908

909
910
	switch res.Status {
	case kssPinSuccess:
911
		client.handler.ChangePinSuccess(managerID)
912
	case kssPinFailure:
913
914
915
916
917
		attempts, err := strconv.Atoi(res.Message)
		if err != nil {
			return err
		}
		client.handler.ChangePinIncorrect(managerID, attempts)