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

import (
4
	"strconv"
5
	"time"
6
	"path/filepath"
Sietse Ringers's avatar
Sietse Ringers committed
7

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

17
// This file contains most methods of the Client (c.f. session.go
Sietse Ringers's avatar
Sietse Ringers committed
18
19
// and updates.go).
//
Sietse Ringers's avatar
Sietse Ringers committed
20
21
22
23
24
25
26
27
// 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
28
29
30
31
32
33
34
// 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
35
// immediately need to be available anyway.
Sietse Ringers's avatar
Sietse Ringers committed
36
37
38
39
40
//
// - 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).

41
type Client struct {
Sietse Ringers's avatar
Sietse Ringers committed
42
	// Stuff we manage on disk
43
	secretkey        *secretKey
44
	attributes       map[irma.CredentialTypeIdentifier][]*irma.AttributeList
45
	credentialsCache map[irma.CredentialTypeIdentifier]map[int]*credential
46
	keyshareServers  map[irma.SchemeManagerIdentifier]*keyshareServer
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
// MissingAttributes contains all attribute requests that the client cannot satisfy with its
// current attributes.
98
type MissingAttributes map[int]map[int]map[int]MissingAttribute
99
100
101
102
103

// MissingAttribute is an irma.AttributeRequest that is satisfied by none of the client's attributes
// (with Go's default JSON marshaler instead of that of irma.AttributeRequest).
type MissingAttribute irma.AttributeRequest

104
105
106
107
type secretKey struct {
	Key *big.Int
}

108
// New creates a new Client that uses the directory
109
110
111
112
// 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),
113
// and handler is used for informing the user of new stuff, and when a
114
// enrollment to a keyshare server needs to happen.
115
// The client returned by this function has been fully deserialized
116
117
// and is ready for use.
//
Sietse Ringers's avatar
Sietse Ringers committed
118
119
// NOTE: It is the responsibility of the caller that there exists a (properly
// protected) directory at storagePath!
120
func New(
121
122
123
	storagePath string,
	irmaConfigurationPath string,
	androidStoragePath string,
124
	handler ClientHandler,
125
) (*Client, error) {
126
	var err error
127
	if err = fs.AssertPathExists(storagePath); err != nil {
128
129
		return nil, err
	}
130
	if err = fs.AssertPathExists(irmaConfigurationPath); err != nil {
131
132
133
		return nil, err
	}

134
	cm := &Client{
135
		credentialsCache:      make(map[irma.CredentialTypeIdentifier]map[int]*credential),
136
137
		keyshareServers:       make(map[irma.SchemeManagerIdentifier]*keyshareServer),
		attributes:            make(map[irma.CredentialTypeIdentifier][]*irma.AttributeList),
138
139
		irmaConfigurationPath: irmaConfigurationPath,
		androidStoragePath:    androidStoragePath,
140
		handler:               handler,
141
142
	}

143
	cm.Configuration, err = irma.NewConfigurationFromAssets(filepath.Join(storagePath,"irma_configuration"), irmaConfigurationPath)
144
145
146
	if err != nil {
		return nil, err
	}
147

148
	schemeMgrErr := cm.Configuration.ParseOrRestoreFolder()
149
150
151
152
153
	// 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
154
	}
155
156

	// Ensure storage path exists, and populate it with necessary files
157
	cm.storage = storage{storagePath: storagePath, Configuration: cm.Configuration}
Sietse Ringers's avatar
Sietse Ringers committed
158
	if err = cm.storage.EnsureStorageExists(); err != nil {
159
160
161
		return nil, err
	}

162
	if cm.Preferences, err = cm.storage.LoadPreferences(); err != nil {
163
164
		return nil, err
	}
165
	cm.applyPreferences()
166

167
	// Perform new update functions from clientUpdates, if any
168
169
170
171
172
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
Sietse Ringers's avatar
Sietse Ringers committed
173
	if cm.secretkey, err = cm.storage.LoadSecretKey(); err != nil {
174
175
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
176
	if cm.attributes, err = cm.storage.LoadAttributes(); err != nil {
177
178
		return nil, err
	}
179
	if cm.keyshareServers, err = cm.storage.LoadKeyshareServers(); err != nil {
180
181
182
		return nil, err
	}

183
	if len(cm.UnenrolledSchemeManagers()) > 1 {
184
185
186
		return nil, errors.New("Too many keyshare servers")
	}

187
	return cm, schemeMgrErr
188
189
}

190
// CredentialInfoList returns a list of information of all contained credentials.
191
192
func (client *Client) CredentialInfoList() irma.CredentialInfoList {
	list := irma.CredentialInfoList([]*irma.CredentialInfo{})
193

194
	for _, attrlistlist := range client.attributes {
195
		for _, attrlist := range attrlistlist {
196
			info := attrlist.Info()
197
198
199
			if info == nil {
				continue
			}
200
			list = append(list, info)
Sietse Ringers's avatar
Sietse Ringers committed
201
202
		}
	}
203

Sietse Ringers's avatar
Sietse Ringers committed
204
205
206
	return list
}

207
// addCredential adds the specified credential to the Client, saving its signature
Sietse Ringers's avatar
Sietse Ringers committed
208
// imediately, and optionally cm.attributes as well.
209
func (client *Client) addCredential(cred *credential, storeAttributes bool) (err error) {
210
211
212
213
	id := irma.NewCredentialTypeIdentifier("")
	if cred.CredentialType() != nil {
		id = cred.CredentialType().Identifier()
	}
Sietse Ringers's avatar
Sietse Ringers committed
214

215
	// Don't add duplicate creds
216
	for _, attrlistlist := range client.attributes {
217
		for _, attrs := range attrlistlist {
218
			if attrs.Hash() == cred.AttributeList().Hash() {
219
220
221
222
223
224
				return nil
			}
		}
	}

	// If this is a singleton credential type, ensure we have at most one by removing any previous instance
225
226
227
228
	if !id.Empty() && cred.CredentialType().IsSingleton {
		for len(client.attrs(id)) != 0 {
			client.remove(id, 0, false)
		}
229
230
231
	}

	// Append the new cred to our attributes and credentials
232
	client.attributes[id] = append(client.attrs(id), cred.AttributeList())
233
	if !id.Empty() {
234
235
		if _, exists := client.credentialsCache[id]; !exists {
			client.credentialsCache[id] = make(map[int]*credential)
236
237
		}
		counter := len(client.attributes[id]) - 1
238
		client.credentialsCache[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
239
240
	}

241
	if err = client.storage.StoreSignature(cred); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
242
243
244
		return
	}
	if storeAttributes {
245
		err = client.storage.StoreAttributes(client.attributes)
Sietse Ringers's avatar
Sietse Ringers committed
246
247
248
249
250
251
252
253
254
255
256
257
258
259
	}
	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

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

	// Remove credential
275
	if creds, exists := client.credentialsCache[id]; exists {
276
		if _, exists := creds[index]; exists {
Sietse Ringers's avatar
Sietse Ringers committed
277
			delete(creds, index)
278
			client.credentialsCache[id] = creds
279
280
281
282
		}
	}

	// Remove signature from storage
283
	if err := client.storage.DeleteSignature(attrs); err != nil {
284
285
286
		return err
	}

287
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
288
289
290
	removed[id] = attrs.Strings()

	if storenow {
291
		return client.storage.AddLogEntry(&LogEntry{
292
			Type:    actionRemoval,
293
			Time:    irma.Timestamp(time.Now()),
294
295
296
297
			Removed: removed,
		})
	}
	return nil
298
299
}

Sietse Ringers's avatar
Sietse Ringers committed
300
// RemoveCredential removes the specified credential.
301
func (client *Client) RemoveCredential(id irma.CredentialTypeIdentifier, index int) error {
302
	return client.remove(id, index, true)
303
304
}

Sietse Ringers's avatar
Sietse Ringers committed
305
// RemoveCredentialByHash removes the specified credential.
306
307
func (client *Client) RemoveCredentialByHash(hash string) error {
	cred, index, err := client.credentialByHash(hash)
308
309
310
	if err != nil {
		return err
	}
311
	return client.RemoveCredential(cred.CredentialType().Identifier(), index)
312
313
}

Sietse Ringers's avatar
Sietse Ringers committed
314
// RemoveAllCredentials removes all credentials.
315
func (client *Client) RemoveAllCredentials() error {
316
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
317
	for _, attrlistlist := range client.attributes {
Sietse Ringers's avatar
Sietse Ringers committed
318
319
320
321
		for _, attrs := range attrlistlist {
			if attrs.CredentialType() != nil {
				removed[attrs.CredentialType().Identifier()] = attrs.Strings()
			}
322
			_ = client.storage.DeleteSignature(attrs)
323
324
		}
	}
325
	client.attributes = map[irma.CredentialTypeIdentifier][]*irma.AttributeList{}
326
	if err := client.storage.StoreAttributes(client.attributes); err != nil {
327
328
		return err
	}
329
330
331

	logentry := &LogEntry{
		Type:    actionRemoval,
332
		Time:    irma.Timestamp(time.Now()),
333
334
		Removed: removed,
	}
335
	return client.storage.AddLogEntry(logentry)
336
337
}

338
// Attribute and credential getter methods
Sietse Ringers's avatar
Sietse Ringers committed
339

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

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

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

369
func (client *Client) attributesByHash(hash string) (*irma.AttributeList, int) {
370
	for _, attrlistlist := range client.attributes {
371
		for index, attrs := range attrlistlist {
372
			if attrs.Hash() == hash {
373
				return attrs, index
374
375
376
			}
		}
	}
377
378
379
380
381
382
383
384
385
	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
	}
386
387
388
	return nil, 0, nil
}

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

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

437
	return client.credentialsCache[id][counter], nil
438
439
}

Sietse Ringers's avatar
Sietse Ringers committed
440
// Methods used in the IRMA protocol
441

442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
// 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
466

467
468
469
470
471
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))
472
		}
473
474
475
476
477
478
479
480
481
482
483
484
	}
	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 {
485
486
				if attr.Type.CredentialTypeIdentifier() != cred.Type {
					continue
487
				}
488
489
490
491
492
493
494
495
496
497
498
				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,
				})
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) (
521
	candidates [][]*irma.AttributeIdentifier, missing map[int]map[int]MissingAttribute,
522
523
524
525
) {
	candidates = [][]*irma.AttributeIdentifier{}

	for _, con := range discon {
526
527
528
529
530
531
532
		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
		}

533
534
535
536
537
538
539
		// 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 {
540
541
			continue
		}
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.
566
567
func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]map[int]MissingAttribute {
	missing := make(map[int]map[int]MissingAttribute, len(discon))
568
569

	for i, con := range discon {
570
571
572
573
		missing[i] = map[int]MissingAttribute{}
	conloop:
		for j, req := range con {
			creds := client.attributes[req.Type.CredentialTypeIdentifier()]
574
			if len(creds) == 0 {
575
				missing[i][j] = MissingAttribute(req)
576
577
				continue
			}
578
			for _, cred := range creds {
579
				if cred.IsValid() && req.Satisfy(req.Type, cred.UntranslatedAttribute(req.Type)) {
580
					continue conloop
581
582
				}
			}
583
			missing[i][j] = MissingAttribute(req)
584
585
586
		}
	}

587
	return missing
588
589
}

590
// CheckSatisfiability checks if this client has the required attributes
Sietse Ringers's avatar
Sietse Ringers committed
591
592
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
593
func (client *Client) CheckSatisfiability(condiscon irma.AttributeConDisCon) (
594
	candidates [][][]*irma.AttributeIdentifier, missing MissingAttributes,
595
596
) {
	candidates = make([][][]*irma.AttributeIdentifier, len(condiscon))
597
	missing = MissingAttributes{}
598
599

	for i, discon := range condiscon {
600
		var m map[int]map[int]MissingAttribute
601
		candidates[i], m = client.Candidates(discon)
602
		if len(candidates[i]) == 0 {
603
			missing[i] = m
604
605
		}
	}
606
607

	return
608
}
609

610
611
612
613
614
615
616
617
618
619
// 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,
620
) {
621
	if choice == nil || choice.Attributes == nil {
622
		return []attributeGroup{}, irma.DisclosedAttributeIndices{}, nil
623
	}
624

625
626
	// maps an irma.CredentialIdentifier to its index in the final ProofList
	credIndices := make(map[irma.CredentialIdentifier]int)
627
	todisclose := make([]attributeGroup, 0, len(choice.Attributes))
628
	attributeIndices := make(irma.DisclosedAttributeIndices, len(choice.Attributes))
629
630
631
632
633
634
635
636
637
638
639
640
641
642
	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]
			}
643

644
645
646
647
			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
648
			}
649

650
651
652
653
654
655
656
657
			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)
658
659
660
		}
	}

661
	return todisclose, attributeIndices, nil
662
663
}

664
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
665
666
func (client *Client) ProofBuilders(choice *irma.DisclosureChoice, request irma.SessionRequest,
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *atum.Timestamp, error) {
667
	todisclose, attributeIndices, err := client.groupCredentials(choice)
668
	if err != nil {
669
		return nil, nil, nil, err
670
671
	}

672
	var builders gabi.ProofBuilderList
673
674
	for _, grp := range todisclose {
		cred, err := client.credentialByID(grp.cred)
675
		if err != nil {
676
			return nil, nil, nil, err
677
		}
678
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(grp.attrs))
679
	}
680

681
682
	var timestamp *atum.Timestamp
	if r, ok := request.(*irma.SignatureRequest); ok {
683
684
685
686
687
688
689
690
691
		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)
		}
692
		timestamp, err = irma.GetTimestamp(r.Message, sigs, disclosed, client.Configuration)
693
		if err != nil {
694
			return nil, nil, nil, err
695
696
697
		}
	}

698
	return builders, attributeIndices, timestamp, nil
699
700
701
}

// Proofs computes disclosure proofs containing the attributes specified by choice.
702
703
func (client *Client) Proofs(choice *irma.DisclosureChoice, request irma.SessionRequest) (*irma.Disclosure, *atum.Timestamp, error) {
	builders, choices, timestamp, err := client.ProofBuilders(choice, request)
704
	if err != nil {
705
		return nil, nil, err
706
707
	}

708
	_, issig := request.(*irma.SignatureRequest)
709
	return &irma.Disclosure{
710
		Proofs:  builders.BuildProofList(request.Base().GetContext(), request.GetNonce(timestamp), issig),
711
		Indices: choices,
712
	}, timestamp, nil
Sietse Ringers's avatar
Sietse Ringers committed
713
714
}

715
716
717
718
719
// 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)
}

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

741
	disclosures, choices, _, err := client.ProofBuilders(choice, request)
Sietse Ringers's avatar
Sietse Ringers committed
742
	if err != nil {
743
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
744
	}
745
	builders = append(disclosures, builders...)
746
	return builders, choices, issuerProofNonce, nil
Sietse Ringers's avatar
Sietse Ringers committed
747
}
Sietse Ringers's avatar
Sietse Ringers committed
748

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

766
767
768
769
// 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
770
771
772
		return errors.New("Received unexpected amount of signatures")
	}

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

795
	for _, gabicred := range gabicreds {
796
		newcred, err := newCredential(gabicred, client.Configuration)
797
798
799
		if err != nil {
			return err
		}
800
		if err = client.addCredential(newcred, true); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
801
802
			return err
		}
Sietse Ringers's avatar
Sietse Ringers committed
803
	}
804

Sietse Ringers's avatar
Sietse Ringers committed
805
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
806
}
807

Sietse Ringers's avatar
Sietse Ringers committed
808
809
// Keyshare server handling

810
func (client *Client) genSchemeManagersList(enrolled bool) []irma.SchemeManagerIdentifier {
811
	list := []irma.SchemeManagerIdentifier{}
812
	for name, manager := range client.Configuration.SchemeManagers {
813
		if _, contains := client.keyshareServers[name]; manager.Distributed() && contains == enrolled {
Sietse Ringers's avatar
Sietse Ringers committed
814
			list = append(list, manager.Identifier())
Sietse Ringers's avatar
Sietse Ringers committed
815
816
817
818
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
819

820
821
822
823
func (client *Client) UnenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	return client.genSchemeManagersList(false)
}

824
func (client *Client) EnrolledSchemeManagers() []irma.SchemeManagerIdentifier {
825
	return client.genSchemeManagersList(true)
826
827
}

828
// KeyshareEnroll attempts to enroll at the keyshare server of the specified scheme manager.
829
func (client *Client) KeyshareEnroll(manager irma.SchemeManagerIdentifier, email *string, pin string, lang string) {
Sietse Ringers's avatar
Sietse Ringers committed
830
	go func() {
831
		err := client.keyshareEnrollWorker(manager, email, pin, lang)
Sietse Ringers's avatar
Sietse Ringers committed
832
		if err != nil {
Tomas's avatar
Tomas committed
833
			client.handler.EnrollmentFailure(manager, err)
Sietse Ringers's avatar
Sietse Ringers committed
834
835
		}
	}()
836
837
}

838
func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifier, email *string, pin string, lang string) error {
839
	manager, ok := client.Configuration.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
840
841
842
843
844
845
846
847
848
849
	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")
	}

850
	transport := irma.NewHTTPTransport(manager.KeyshareServer)
851
	kss, err := newKeyshareServer(managerID)
Sietse Ringers's avatar
Sietse Ringers committed
852
853
854
	if err != nil {
		return err
	}
855
	message := keyshareEnrollment{
856
857
858
		Email:    email,
		Pin:      kss.HashedPin(pin),
		Language: lang,
Sietse Ringers's avatar
Sietse Ringers committed
859
860
	}

861
862
	qr := &irma.Qr{}
	err = transport.Post("client/register", qr, message)
Sietse Ringers's avatar
Sietse Ringers committed
863
864
865
866
	if err != nil {
		return err
	}

867
868
869
870
871
	// 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.
872
	client.keyshareServers[managerID] = kss
873
	client.newQrSession(qr, &keyshareEnrollmentHandler{
874
875
876
877
878
879
		client: client,
		pin:    pin,
		kss:    kss,
	})

	return nil
Sietse Ringers's avatar
Sietse Ringers committed
880
881
}

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