client.go 32.1 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
	}
	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 {
518
519
520
521
522
523
524
		if len(con) == 0 {
			// An empty conjunction means the containing disjunction is optional
			// so it is satisfied by sending no attributes
			candidates = append(candidates, []*irma.AttributeIdentifier{})
			continue
		}

525
526
527
528
529
530
531
		// 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 {
532
533
			continue
		}
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
559
560
561
562
563
564
565

		// 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)
566
567
				continue
			}
568
569
			for _, cred := range creds {
				if attr.Satisfy(attr.Type, cred.UntranslatedAttribute(attr.Type)) {
570
571
572
					continue
				}
			}
573
			missing[i] = append(missing[i], attr)
574
575
576
		}
	}

577
	return missing
578
579
}

580
// CheckSatisfiability checks if this client has the required attributes
Sietse Ringers's avatar
Sietse Ringers committed
581
582
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
583
584
585
586
587
588
589
590
591
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)
592
		if len(candidates[i]) == 0 {
593
			missing[i] = m
594
595
		}
	}
596
597

	return
598
}
599

600
601
602
603
604
605
606
607
608
609
// 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,
610
) {
611
	if choice == nil || choice.Attributes == nil {
612
		return []attributeGroup{}, irma.DisclosedAttributeIndices{}, nil
613
	}
614

615
616
	// maps an irma.CredentialIdentifier to its index in the final ProofList
	credIndices := make(map[irma.CredentialIdentifier]int)
617
	todisclose := make([]attributeGroup, 0, len(choice.Attributes))
618
	attributeIndices := make(irma.DisclosedAttributeIndices, len(choice.Attributes))
619
620
621
622
623
624
625
626
627
628
629
630
631
632
	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]
			}
633

634
635
636
637
			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
638
			}
639

640
641
642
643
644
645
646
647
			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)
648
649
650
		}
	}

651
	return todisclose, attributeIndices, nil
652
653
}

654
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
655
656
func (client *Client) ProofBuilders(choice *irma.DisclosureChoice, request irma.SessionRequest,
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *atum.Timestamp, error) {
657
	todisclose, attributeIndices, err := client.groupCredentials(choice)
658
	if err != nil {
659
		return nil, nil, nil, err
660
661
	}

662
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
663
664
	for _, grp := range todisclose {
		cred, err := client.credentialByID(grp.cred)
665
		if err != nil {
666
			return nil, nil, nil, err
667
		}
668
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(grp.attrs))
669
	}
670

671
672
	var timestamp *atum.Timestamp
	if r, ok := request.(*irma.SignatureRequest); ok {
673
674
675
676
677
678
679
680
681
		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)
		}
682
		timestamp, err = irma.GetTimestamp(r.Message, sigs, disclosed, client.Configuration)
683
		if err != nil {
684
			return nil, nil, nil, err
685
686
687
		}
	}

688
	return builders, attributeIndices, timestamp, nil
689
690
691
}

// Proofs computes disclosure proofs containing the attributes specified by choice.
692
693
func (client *Client) Proofs(choice *irma.DisclosureChoice, request irma.SessionRequest) (*irma.Disclosure, *atum.Timestamp, error) {
	builders, choices, timestamp, err := client.ProofBuilders(choice, request)
694
	if err != nil {
695
		return nil, nil, err
696
697
	}

698
	_, issig := request.(*irma.SignatureRequest)
699
	return &irma.Disclosure{
700
		Proofs:  builders.BuildProofList(request.Base().GetContext(), request.GetNonce(timestamp), issig),
701
		Indices: choices,
702
	}, timestamp, nil
Sietse Ringers's avatar
Sietse Ringers committed
703
704
}

705
706
707
708
709
// 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)
}

710
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
711
712
// 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.
713
func (client *Client) IssuanceProofBuilders(request *irma.IssuanceRequest, choice *irma.DisclosureChoice,
714
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
715
	issuerProofNonce, err := generateIssuerProofNonce()
Sietse Ringers's avatar
Sietse Ringers committed
716
	if err != nil {
717
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
718
	}
719
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
720
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
721
		var pk *gabi.PublicKey
722
		pk, err = client.Configuration.PublicKey(futurecred.CredentialTypeID.IssuerIdentifier(), futurecred.KeyCounter)
723
		if err != nil {
724
			return nil, nil, nil, err
725
		}
726
		credBuilder := gabi.NewCredentialBuilder(
727
728
			pk, request.GetContext(), client.secretkey.Key, issuerProofNonce)
		builders = append(builders, credBuilder)
Sietse Ringers's avatar
Sietse Ringers committed
729
	}
Sietse Ringers's avatar
Sietse Ringers committed
730

731
	disclosures, choices, _, err := client.ProofBuilders(choice, request)
Sietse Ringers's avatar
Sietse Ringers committed
732
	if err != nil {
733
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
734
	}
735
	builders = append(disclosures, builders...)
736
	return builders, choices, issuerProofNonce, nil
Sietse Ringers's avatar
Sietse Ringers committed
737
}
Sietse Ringers's avatar
Sietse Ringers committed
738

739
740
// 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.
741
func (client *Client) IssueCommitments(request *irma.IssuanceRequest, choice *irma.DisclosureChoice,
742
) (*irma.IssueCommitmentMessage, gabi.ProofBuilderList, error) {
743
	builders, choices, issuerProofNonce, err := client.IssuanceProofBuilders(request, choice)
Sietse Ringers's avatar
Sietse Ringers committed
744
	if err != nil {
745
		return nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
746
	}
747
748
	return &irma.IssueCommitmentMessage{
		IssueCommitmentMessage: &gabi.IssueCommitmentMessage{
749
			Proofs: builders.BuildProofList(request.GetContext(), request.GetNonce(nil), false),
750
751
752
753
			Nonce2: issuerProofNonce,
		},
		Indices: choices,
	}, builders, nil
Sietse Ringers's avatar
Sietse Ringers committed
754
755
}

756
757
758
759
// 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
760
761
762
		return errors.New("Received unexpected amount of signatures")
	}

763
764
	// 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
765
	gabicreds := []*gabi.Credential{}
766
767
768
769
770
771
772
773
	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]
774
		attrs, err := request.Credentials[i-offset].AttributeList(client.Configuration, irma.GetMetadataVersion(request.Base().ProtocolVersion))
Sietse Ringers's avatar
Sietse Ringers committed
775
776
777
		if err != nil {
			return err
		}
778
		cred, err := credbuilder.ConstructCredential(sig, attrs.Ints)
Sietse Ringers's avatar
Sietse Ringers committed
779
780
781
		if err != nil {
			return err
		}
782
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
783
784
	}

785
	for _, gabicred := range gabicreds {
786
		newcred, err := newCredential(gabicred, client.Configuration)
787
788
789
		if err != nil {
			return err
		}
790
		if err = client.addCredential(newcred, true); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
791
792
			return err
		}
Sietse Ringers's avatar
Sietse Ringers committed
793
	}
794

Sietse Ringers's avatar
Sietse Ringers committed
795
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
796
}
797

Sietse Ringers's avatar
Sietse Ringers committed
798
799
// Keyshare server handling

800
func (client *Client) genSchemeManagersList(enrolled bool) []irma.SchemeManagerIdentifier {
801
	list := []irma.SchemeManagerIdentifier{}
802
	for name, manager := range client.Configuration.SchemeManagers {
803
		if _, contains := client.keyshareServers[name]; manager.Distributed() && contains == enrolled {
Sietse Ringers's avatar
Sietse Ringers committed
804
			list = append(list, manager.Identifier())
Sietse Ringers's avatar
Sietse Ringers committed
805
806
807
808
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
809

810
811
812
813
func (client *Client) UnenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	return client.genSchemeManagersList(false)
}

814
func (client *Client) EnrolledSchemeManagers() []irma.SchemeManagerIdentifier {
815
	return client.genSchemeManagersList(true)
816
817
}

818
// KeyshareEnroll attempts to enroll at the keyshare server of the specified scheme manager.
819
func (client *Client) KeyshareEnroll(manager irma.SchemeManagerIdentifier, email *string, pin string, lang string) {
Sietse Ringers's avatar
Sietse Ringers committed
820
	go func() {
821
		err := client.keyshareEnrollWorker(manager, email, pin, lang)
Sietse Ringers's avatar
Sietse Ringers committed
822
		if err != nil {
Tomas's avatar
Tomas committed
823
			client.handler.EnrollmentFailure(manager, err)
Sietse Ringers's avatar
Sietse Ringers committed
824
825
		}
	}()
826
827
}

828
func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifier, email *string, pin string, lang string) error {
829
	manager, ok := client.Configuration.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
830
831
832
833
834
835
836
837
838
839
	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")
	}

840
	transport := irma.NewHTTPTransport(manager.KeyshareServer)
841
	kss, err := newKeyshareServer(managerID)
Sietse Ringers's avatar
Sietse Ringers committed
842
843
844
	if err != nil {
		return err
	}
845
	message := keyshareEnrollment{
846
847
848
		Email:    email,
		Pin:      kss.HashedPin(pin),
		Language: lang,
Sietse Ringers's avatar
Sietse Ringers committed
849
850
	}

851
852
	qr := &irma.Qr{}
	err = transport.Post("client/register", qr, message)
Sietse Ringers's avatar
Sietse Ringers committed
853
854
855
856
	if err != nil {
		return err
	}

857
858
859
860
861
	// 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.
862
	client.keyshareServers[managerID] = kss
863
	client.newQrSession(qr, &keyshareEnrollmentHandler{
864
865
866
867
868
869
		client: client,
		pin:    pin,
		kss:    kss,
	})

	return nil
Sietse Ringers's avatar
Sietse Ringers committed
870
871
}

872
873
874
// 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.
875
876
877
func (client *Client) KeyshareVerifyPin(pin string, schemeid irma.SchemeManagerIdentifier) (bool, int, int, error) {
	scheme := client.Configuration.SchemeManagers[schemeid]
	if scheme == nil || !scheme.Distributed() {
878
879
880
881
882
		return false, 0, 0, &irma.SessionError{
			Err:       errors.Errorf("Can't verify pin of scheme %s", schemeid.String()),
			ErrorType: irma.ErrorUnknownSchemeManager,
			Info:      schemeid.String(),
		}
883
884
	}
	kss := client.keyshareServers[schemeid]
885
	return verifyPinWorker(pin, kss, irma.NewHTTPTransport(scheme.KeyshareServer))
886
887
}

888
func (client *Client) KeyshareChangePin(manager irma.SchemeManagerIdentifier, oldPin string, newPin string) {
889
	go func() {
890
		err := client.keyshareChangePinWorker(manager, oldPin, newPin)
891
		if err != nil {
892
			client.handler.ChangePinFailure(manager, err)
893
894
895
896
		}
	}()
}

897
func (client *Client) keyshareChangePinWorker(managerID irma.SchemeManagerIdentifier, oldPin string, newPin string) error {
898
899
900
901
902
	kss, ok := client.keyshareServers[managerID]
	if !ok {
		return errors.New("Unknown keyshare server")
	}

903
	transport := irma.NewHTTPTransport(client.Configuration.SchemeManagers[managerID].KeyshareServer)
904
	message := keyshareChangepin{
905
		Username: kss.Username,
906
907
		OldPin:   kss.HashedPin(oldPin),
		NewPin:   kss.HashedPin(newPin),
908
909
910
911
912
913
914
	}

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