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

import (
4
	"path/filepath"
5
	"strconv"
6
	"time"
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
	"github.com/privacybydesign/keyproof/common"
16
17
)

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

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

Sietse Ringers's avatar
Sietse Ringers committed
50
51
	// Where we store/load it to/from
	storage storage
52
53
	// Legacy storage needed when client has not updated to the new storage yet
	fileStorage fileStorage
Sietse Ringers's avatar
Sietse Ringers committed
54
55

	// Other state
56
57
58
59
	Preferences           Preferences
	Configuration         *irma.Configuration
	irmaConfigurationPath string
	handler               ClientHandler
60
61
}

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

66
67
type Preferences struct {
	EnableCrashReporting bool
68
69
}

70
var defaultPreferences = Preferences{
71
	EnableCrashReporting: true,
72
73
}

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

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

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

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

98
99
// MissingAttributes contains all attribute requests that the client cannot satisfy with its
// current attributes.
100
type MissingAttributes map[int]map[int]map[int]MissingAttribute
101
102
103
104
105

// 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

106
107
108
109
type secretKey struct {
	Key *big.Int
}

110
// New creates a new Client that uses the directory
111
112
// specified by storagePath for (de)serializing itself. irmaConfigurationPath
// is the path to a (possibly readonly) folder containing irma_configuration;
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
	storagePath string,
	irmaConfigurationPath string,
123
	handler ClientHandler,
124
) (*Client, error) {
125
	var err error
126
	if err = fs.AssertPathExists(storagePath); err != nil {
127
128
		return nil, err
	}
129
	if err = fs.AssertPathExists(irmaConfigurationPath); err != nil {
130
131
132
		return nil, err
	}

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

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

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

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

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
191
192
193
func (client *Client) Close() error {
	return client.storage.Close()
}

194
// CredentialInfoList returns a list of information of all contained credentials.
195
196
func (client *Client) CredentialInfoList() irma.CredentialInfoList {
	list := irma.CredentialInfoList([]*irma.CredentialInfo{})
197

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

Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
	return list
}

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

219
220
221
222
	// If we receive a duplicate credential it should overwrite the previous one; remove it first
	// (it makes no sense to possess duplicate credentials, but the new signature might contain new
	// functionality such as a nonrevocation witness, so it does not suffice to just return here)
	index := -1
223
	for _, attrlistlist := range client.attributes {
224
		for i, attrs := range attrlistlist {
225
			if attrs.Hash() == cred.AttributeList().Hash() {
226
227
				index = i
				break
228
229
230
			}
		}
	}
231
232
233
234
235
	if index != -1 {
		if err = client.remove(id, index, false); err != nil {
			return err
		}
	}
236
237

	// If this is a singleton credential type, ensure we have at most one by removing any previous instance
238
239
240
241
	// If a credential already exists with exactly the same attribute values (except metadata), delete the previous credential
	if !id.Empty() {
		if cred.CredentialType().IsSingleton {
			for len(client.attrs(id)) != 0 {
242
243
244
				if err = client.remove(id, 0, false); err != nil {
					return
				}
245
246
247
248
249
			}
		}

		for i := len(client.attrs(id)) - 1; i >= 0; i-- { // Go backwards through array because remove manipulates it
			if client.attrs(id)[i].EqualsExceptMetadata(cred.AttributeList()) {
250
251
252
				if err = client.remove(id, i, false); err != nil {
					return
				}
253
			}
254
		}
255
256
257
	}

	// Append the new cred to our attributes and credentials
258
	client.attributes[id] = append(client.attrs(id), cred.AttributeList())
259
	if !id.Empty() {
260
261
		if _, exists := client.credentialsCache[id]; !exists {
			client.credentialsCache[id] = make(map[int]*credential)
262
263
		}
		counter := len(client.attributes[id]) - 1
264
		client.credentialsCache[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
265
266
	}

267
	return client.storage.Transaction(func(tx *transaction) error {
268
269
270
271
272
		if err = client.storage.TxStoreSignature(tx, cred); err != nil {
			return err
		}
		return client.storage.TxStoreAttributes(tx, id, client.attributes[id])
	})
Sietse Ringers's avatar
Sietse Ringers committed
273
274
275
}

func generateSecretKey() (*secretKey, error) {
276
277
278
	return &secretKey{
		Key: common.RandomBigInt(new(big.Int).Lsh(big.NewInt(1), uint(gabi.DefaultSystemParameters[1024].Lm))),
	}, nil
Sietse Ringers's avatar
Sietse Ringers committed
279
280
281
282
}

// Removal methods

283
func (client *Client) remove(id irma.CredentialTypeIdentifier, index int, storeLog bool) error {
284
	// Remove attributes
285
	list, exists := client.attributes[id]
286
287
288
289
	if !exists || index >= len(list) {
		return errors.Errorf("Can't remove credential %s-%d: no such credential", id.String(), index)
	}
	attrs := list[index]
290
	client.attributes[id] = append(list[:index], list[index+1:]...)
291

292
293
294
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
	removed[id] = attrs.Strings()

295
	err := client.storage.Transaction(func(tx *transaction) error {
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
		if err := client.storage.TxDeleteSignature(tx, attrs); err != nil {
			return err
		}
		if err := client.storage.TxStoreAttributes(tx, id, client.attributes[id]); err != nil {
			return err
		}
		if storeLog {
			return client.storage.TxAddLogEntry(tx, &LogEntry{
				Type:    ActionRemoval,
				Time:    irma.Timestamp(time.Now()),
				Removed: removed,
			})
		}
		return nil
	})
	if err != nil {
312
		return err
313
314
	}

315
	// Remove credential from cache
316
	if creds, exists := client.credentialsCache[id]; exists {
317
		if _, exists := creds[index]; exists {
Sietse Ringers's avatar
Sietse Ringers committed
318
			delete(creds, index)
319
			client.credentialsCache[id] = creds
320
321
		}
	}
322
	return nil
323
324
}

325
// RemoveCredential removes the specified credential if that is allowed.
326
func (client *Client) RemoveCredential(id irma.CredentialTypeIdentifier, index int) error {
327
328
329
	if client.Configuration.CredentialTypes[id].DisallowDelete {
		return errors.Errorf("configuration does not allow removal of credential type %s", id.String())
	}
330
	return client.remove(id, index, true)
331
332
}

Sietse Ringers's avatar
Sietse Ringers committed
333
// RemoveCredentialByHash removes the specified credential.
334
335
func (client *Client) RemoveCredentialByHash(hash string) error {
	cred, index, err := client.credentialByHash(hash)
336
337
338
	if err != nil {
		return err
	}
339
	return client.RemoveCredential(cred.CredentialType().Identifier(), index)
340
341
}

342
343
344
345
// Removes all attributes, signatures, logs and userdata
// Includes the user's secret key, keyshare servers and preferences/updates
// A fresh secret key is installed.
func (client *Client) RemoveStorage() error {
Leon's avatar
Leon committed
346
	var err error
347

348
349
350
351
	// Remove data from memory
	client.attributes = make(map[irma.CredentialTypeIdentifier][]*irma.AttributeList)
	client.keyshareServers = make(map[irma.SchemeManagerIdentifier]*keyshareServer)
	client.credentialsCache = make(map[irma.CredentialTypeIdentifier]map[int]*credential)
352

Leon's avatar
Leon committed
353
354
355
	if err = client.storage.DeleteAll(); err != nil {
		return err
	}
356
357
358
359
360
361
362

	// Client assumes there is always a secret key, so we have to load a new one
	client.secretkey, err = client.storage.LoadSecretKey()
	if err != nil {
		return err
	}

Leon's avatar
Leon committed
363
	// TODO: do we consider this setting as user data?
364
365
366
367
368
369
	if client.Preferences, err = client.storage.LoadPreferences(); err != nil {
		return err
	}
	client.applyPreferences()

	return nil
370
371
}

372
// Attribute and credential getter methods
Sietse Ringers's avatar
Sietse Ringers committed
373

Sietse Ringers's avatar
Sietse Ringers committed
374
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
375
func (client *Client) attrs(id irma.CredentialTypeIdentifier) []*irma.AttributeList {
376
	list, exists := client.attributes[id]
Sietse Ringers's avatar
Sietse Ringers committed
377
	if !exists {
378
		list = make([]*irma.AttributeList, 0, 1)
379
		client.attributes[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
380
381
382
383
384
	}
	return list
}

// creds returns cm.credentials[id], initializing it to an empty map if neccesary
385
func (client *Client) creds(id irma.CredentialTypeIdentifier) map[int]*credential {
386
	list, exists := client.credentialsCache[id]
Sietse Ringers's avatar
Sietse Ringers committed
387
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
388
		list = make(map[int]*credential)
389
		client.credentialsCache[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
390
391
392
393
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
394
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
395
func (client *Client) Attributes(id irma.CredentialTypeIdentifier, counter int) (attributes *irma.AttributeList) {
396
	list := client.attrs(id)
Sietse Ringers's avatar
Sietse Ringers committed
397
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
398
399
400
401
402
		return
	}
	return list[counter]
}

403
func (client *Client) attributesByHash(hash string) (*irma.AttributeList, int) {
404
	for _, attrlistlist := range client.attributes {
405
		for index, attrs := range attrlistlist {
406
			if attrs.Hash() == hash {
407
				return attrs, index
408
409
410
			}
		}
	}
411
412
413
414
415
416
417
418
419
	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
	}
420
421
422
	return nil, 0, nil
}

423
func (client *Client) credentialByID(id irma.CredentialIdentifier) (*credential, error) {
424
	if _, exists := client.attributes[id.Type]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
425
426
		return nil, nil
	}
427
	for index, attrs := range client.attributes[id.Type] {
428
		if attrs.Hash() == id.Hash {
429
			return client.credential(attrs.CredentialType().Identifier(), index)
Sietse Ringers's avatar
Sietse Ringers committed
430
431
432
		}
	}
	return nil, nil
433
434
}

Sietse Ringers's avatar
Sietse Ringers committed
435
// credential returns the requested credential, or nil if we do not have it.
436
func (client *Client) credential(id irma.CredentialTypeIdentifier, counter int) (cred *credential, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
437
	// If the requested credential is not in credential map, we check if its attributes were
438
	// deserialized during New(). If so, there should be a corresponding signature file,
Sietse Ringers's avatar
Sietse Ringers committed
439
	// so we read that, construct the credential, and add it to the credential map
440
441
	if _, exists := client.creds(id)[counter]; !exists {
		attrs := client.Attributes(id, counter)
Sietse Ringers's avatar
Sietse Ringers committed
442
443
444
		if attrs == nil { // We do not have the requested cred
			return
		}
445
		sig, witness, err := client.storage.LoadSignature(attrs)
Sietse Ringers's avatar
Sietse Ringers committed
446
447
448
449
450
451
452
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
453
		pk, err := attrs.PublicKey()
454
455
456
		if err != nil {
			return nil, err
		}
457
458
459
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
460
		cred, err := newCredential(&gabi.Credential{
461
462
463
464
			Attributes:           append([]*big.Int{client.secretkey.Key}, attrs.Ints...),
			Signature:            sig,
			NonRevocationWitness: witness,
			Pk:                   pk,
465
		}, client.Configuration)
466
467
468
		if err != nil {
			return nil, err
		}
469
		client.credentialsCache[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
470
471
	}

472
	return client.credentialsCache[id][counter], nil
473
474
}

Sietse Ringers's avatar
Sietse Ringers committed
475
// Methods used in the IRMA protocol
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
// 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
501

502
503
504
505
506
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))
507
		}
508
509
510
511
512
513
514
515
516
517
518
519
	}
	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 {
520
521
				if attr.Type.CredentialTypeIdentifier() != cred.Type {
					continue
522
				}
523
524
525
526
527
528
529
530
531
532
533
				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,
				})
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
			}
		}
		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) (
556
	candidates [][]*irma.AttributeIdentifier, missing map[int]map[int]MissingAttribute,
557
558
559
560
) {
	candidates = [][]*irma.AttributeIdentifier{}

	for _, con := range discon {
561
562
563
564
565
566
567
		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
		}

568
569
570
571
572
573
574
		// 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 {
575
576
			continue
		}
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600

		// 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.
601
602
func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]map[int]MissingAttribute {
	missing := make(map[int]map[int]MissingAttribute, len(discon))
603
604

	for i, con := range discon {
605
606
607
608
		missing[i] = map[int]MissingAttribute{}
	conloop:
		for j, req := range con {
			creds := client.attributes[req.Type.CredentialTypeIdentifier()]
609
			if len(creds) == 0 {
610
				missing[i][j] = MissingAttribute(req)
611
612
				continue
			}
613
			for _, cred := range creds {
614
				if cred.IsValid() && req.Satisfy(req.Type, cred.UntranslatedAttribute(req.Type)) {
615
					continue conloop
616
617
				}
			}
618
			missing[i][j] = MissingAttribute(req)
619
620
621
		}
	}

622
	return missing
623
624
}

625
// CheckSatisfiability checks if this client has the required attributes
Sietse Ringers's avatar
Sietse Ringers committed
626
627
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
628
func (client *Client) CheckSatisfiability(condiscon irma.AttributeConDisCon) (
629
	candidates [][][]*irma.AttributeIdentifier, missing MissingAttributes,
630
631
) {
	candidates = make([][][]*irma.AttributeIdentifier, len(condiscon))
632
	missing = MissingAttributes{}
633
634

	for i, discon := range condiscon {
635
		var m map[int]map[int]MissingAttribute
636
		candidates[i], m = client.Candidates(discon)
637
		if len(candidates[i]) == 0 {
638
			missing[i] = m
639
640
		}
	}
641
642

	return
643
}
644

645
646
647
648
649
650
651
652
653
654
// 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,
655
) {
656
	if choice == nil || choice.Attributes == nil {
657
		return []attributeGroup{}, irma.DisclosedAttributeIndices{}, nil
658
	}
659

660
661
	// maps an irma.CredentialIdentifier to its index in the final ProofList
	credIndices := make(map[irma.CredentialIdentifier]int)
662
	todisclose := make([]attributeGroup, 0, len(choice.Attributes))
663
	attributeIndices := make(irma.DisclosedAttributeIndices, len(choice.Attributes))
664
665
666
667
668
669
670
671
672
673
674
675
676
677
	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]
			}
678

679
680
681
682
			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
683
			}
684

685
686
687
688
689
690
691
692
			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)
693
694
695
		}
	}

696
	return todisclose, attributeIndices, nil
697
698
}

699
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
700
701
func (client *Client) ProofBuilders(choice *irma.DisclosureChoice, request irma.SessionRequest,
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *atum.Timestamp, error) {
702
	todisclose, attributeIndices, err := client.groupCredentials(choice)
703
	if err != nil {
704
		return nil, nil, nil, err
705
706
	}

707
	var builders gabi.ProofBuilderList
708
	var builder gabi.ProofBuilder
709
710
	for _, grp := range todisclose {
		cred, err := client.credentialByID(grp.cred)
711
		if err != nil {
712
			return nil, nil, nil, err
713
		}
714
		nonrev, updated, err := cred.prepareNonrevocation(client.Configuration, request)
715
716
717
		if err != nil {
			return nil, nil, nil, err
		}
718
719
720
721
722
		if updated {
			if err = client.storage.StoreSignature(cred); err != nil {
				return nil, nil, nil, err
			}
		}
723
724
725
726
727
		builder, err = cred.CreateDisclosureProofBuilder(grp.attrs, nonrev)
		if err != nil {
			return nil, nil, nil, err
		}
		builders = append(builders, builder)
728
	}
729

730
731
	var timestamp *atum.Timestamp
	if r, ok := request.(*irma.SignatureRequest); ok {
732
733
734
735
736
737
738
739
740
		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)
		}
741
		timestamp, err = irma.GetTimestamp(r.Message, sigs, disclosed, client.Configuration)
742
		if err != nil {
743
			return nil, nil, nil, err
744
745
746
		}
	}

747
	return builders, attributeIndices, timestamp, nil
748
749
750
}

// Proofs computes disclosure proofs containing the attributes specified by choice.
751
752
func (client *Client) Proofs(choice *irma.DisclosureChoice, request irma.SessionRequest) (*irma.Disclosure, *atum.Timestamp, error) {
	builders, choices, timestamp, err := client.ProofBuilders(choice, request)
753
	if err != nil {
754
		return nil, nil, err
755
756
	}

757
	_, issig := request.(*irma.SignatureRequest)
758
	return &irma.Disclosure{
759
		Proofs:  builders.BuildProofList(request.Base().GetContext(), request.GetNonce(timestamp), issig),
760
		Indices: choices,
761
	}, timestamp, nil
Sietse Ringers's avatar
Sietse Ringers committed
762
763
}

764
765
// generateIssuerProofNonce generates a nonce which the issuer must use in its gabi.ProofS.
func generateIssuerProofNonce() (*big.Int, error) {
766
	return common.RandomBigInt(new(big.Int).Lsh(big.NewInt(1), uint(gabi.DefaultSystemParameters[4096].Lstatzk))), nil
767
768
}

769
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
770
771
// 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.
772
func (client *Client) IssuanceProofBuilders(request *irma.IssuanceRequest, choice *irma.DisclosureChoice,
773
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
774
	issuerProofNonce, err := generateIssuerProofNonce()
Sietse Ringers's avatar
Sietse Ringers committed
775
	if err != nil {
776
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
777
	}
778
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
779
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
780
		var pk *gabi.PublicKey
781
		pk, err = client.Configuration.PublicKey(futurecred.CredentialTypeID.IssuerIdentifier(), futurecred.KeyCounter)
782
		if err != nil {
783
			return nil, nil, nil, err
784
		}
785
		credBuilder := gabi.NewCredentialBuilder(
786
787
			pk, request.GetContext(), client.secretkey.Key, issuerProofNonce)
		builders = append(builders, credBuilder)
Sietse Ringers's avatar
Sietse Ringers committed
788
	}
Sietse Ringers's avatar
Sietse Ringers committed
789

790
	disclosures, choices, _, err := client.ProofBuilders(choice, request)
Sietse Ringers's avatar
Sietse Ringers committed
791
	if err != nil {
792
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
793
	}
794
	builders = append(disclosures, builders...)
795
	return builders, choices, issuerProofNonce, nil
Sietse Ringers's avatar
Sietse Ringers committed
796
}
Sietse Ringers's avatar
Sietse Ringers committed
797

798
799
// 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.
800
func (client *Client) IssueCommitments(request *irma.IssuanceRequest, choice *irma.DisclosureChoice,
801
) (*irma.IssueCommitmentMessage, gabi.ProofBuilderList, error) {
802
	builders, choices, issuerProofNonce, err := client.IssuanceProofBuilders(request, choice)
Sietse Ringers's avatar
Sietse Ringers committed
803
	if err != nil {
804
		return nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
805
	}
806
807
	return &irma.IssueCommitmentMessage{
		IssueCommitmentMessage: &gabi.IssueCommitmentMessage{
808
			Proofs: builders.BuildProofList(request.GetContext(), request.GetNonce(nil), false),
809
810
811
812
			Nonce2: issuerProofNonce,
		},
		Indices: choices,
	}, builders, nil
Sietse Ringers's avatar
Sietse Ringers committed
813
814
}

815
816
817
818
// 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
819
820
821
		return errors.New("Received unexpected amount of signatures")
	}

822
823
	// 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
824
	gabicreds := []*gabi.Credential{}
825
826
827
828
829
830
831
832
	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]
833
		attrs, err := request.Credentials[i-offset].AttributeList(client.Configuration, irma.GetMetadataVersion(request.Base().ProtocolVersion))
Sietse Ringers's avatar
Sietse Ringers committed
834
835
836
		if err != nil {
			return err
		}
837
		cred, err := credbuilder.ConstructCredential(sig, attrs.Ints)
Sietse Ringers's avatar
Sietse Ringers committed
838
839
840
		if err != nil {
			return err
		}
841
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
842
843
	}

844
	for _, gabicred := range gabicreds {
845
		newcred, err := newCredential(gabicred, client.Configuration)
846
847
848
		if err != nil {
			return err
		}
849
		if err = client.addCredential(newcred); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
850
851
			return err
		}
Sietse Ringers's avatar
Sietse Ringers committed
852
	}
853

Sietse Ringers's avatar
Sietse Ringers committed
854
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
855
}
856

Sietse Ringers's avatar
Sietse Ringers committed
857
858
// Keyshare server handling

859
func (client *Client) genSchemeManagersList(enrolled bool) []irma.SchemeManagerIdentifier {
860
	list := []irma.SchemeManagerIdentifier{}
861
	for name, manager := range client.Configuration.SchemeManagers {
862
		if _, contains := client.keyshareServers[name]; manager.Distributed() && contains == enrolled {
Sietse Ringers's avatar
Sietse Ringers committed
863
			list = append(list, manager.Identifier())
Sietse Ringers's avatar
Sietse Ringers committed
864
865
866
867
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
868

869
870
871
872
func (client *Client) UnenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	return client.genSchemeManagersList(false)
}

873
func (client *Client) EnrolledSchemeManagers() []irma.SchemeManagerIdentifier {
874
	return client.genSchemeManagersList(true)
875
876
}

877
// KeyshareEnroll attempts to enroll at the keyshare server of the specified scheme manager.
878
func (client *Client) KeyshareEnroll(manager irma.SchemeManagerIdentifier, email *string, pin string, lang string) {
Sietse Ringers's avatar
Sietse Ringers committed
879
	go func() {
880
		err := client.keyshareEnrollWorker(manager, email, pin, lang)
Sietse Ringers's avatar
Sietse Ringers committed
881
		if err != nil {
Tomas's avatar
Tomas committed
882
			client.handler.EnrollmentFailure(manager, err)
Sietse Ringers's avatar
Sietse Ringers committed
883
884
		}
	}()
885
886
}

887
func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifier, email *string, pin string, lang string) error {
888
	manager, ok := client.Configuration.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
889
890
891
892
893
894
895
896
897
898
	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")
	}

899
	transport := irma.NewHTTPTransport(manager.KeyshareServer)
900
	kss, err := newKeyshareServer(managerID)
Sietse Ringers's avatar
Sietse Ringers committed
901
902
903
	if err != nil {
		return err
	}
904