client.go 33.5 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
	}
	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 {
480
481
				if attr.Type.CredentialTypeIdentifier() != cred.Type {
					continue
482
				}
483
484
485
486
487
488
489
490
491
492
493
				attrs, _ := client.attributesByHash(cred.Hash)
				val := attrs.UntranslatedAttribute(attr.Type)
				if !attr.Satisfy(attr.Type, val) {
					// if the attribute in this credential instance has the wrong value, then we have
					// to discard the entire candidate set
					continue outer
				}
				candidateSet = append(candidateSet, &irma.AttributeIdentifier{
					Type:           attr.Type,
					CredentialHash: cred.Hash,
				})
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
			}
		}
		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 {
521
522
523
524
525
526
527
		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
		}

528
529
530
531
532
533
534
		// 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 {
535
536
			continue
		}
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
566
567
568

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

580
	return missing
581
582
}

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

	return
601
}
602

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

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

637
638
639
640
			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
641
			}
642

643
644
645
646
647
648
649
650
			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)
651
652
653
		}
	}

654
	return todisclose, attributeIndices, nil
655
656
}

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

665
	var builders gabi.ProofBuilderList
666
667
	for _, grp := range todisclose {
		cred, err := client.credentialByID(grp.cred)
668
		if err != nil {
669
			return nil, nil, nil, err
670
		}
671
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(grp.attrs))
672
	}
673

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

691
	return builders, attributeIndices, timestamp, nil
692
693
694
}

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

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

708
709
710
711
712
// 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)
}

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

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
798
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
799
}
800

Sietse Ringers's avatar
Sietse Ringers committed
801
802
// Keyshare server handling

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

813
814
815
816
func (client *Client) UnenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	return client.genSchemeManagersList(false)
}

817
func (client *Client) EnrolledSchemeManagers() []irma.SchemeManagerIdentifier {
818
	return client.genSchemeManagersList(true)
819
820
}

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

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

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

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

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

	return nil
Sietse Ringers's avatar
Sietse Ringers committed
873
874
}

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

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

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

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

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

919
920
	switch res.Status {
	case kssPinSuccess:
921
		client.handler.ChangePinSuccess(managerID)
922
	case kssPinFailure:
923
924
925
926
927
		attempts, err := strconv.Atoi(res.Message)
		if err != nil {
			return err
		}
		client.handler.ChangePinIncorrect(managerID, attempts)
928
	case kssPinError:
929
930
931
932
933
		timeout, err := strconv.Atoi(res.Message)
		if err != nil {
			return err
		}
		client.handler.ChangePinBlocked(managerID, timeout)
934
935
	default:
		return errors.New("Unknown keyshare response")
936
	}
937

David Venhoek's avatar