client.go 34 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
// 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(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.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
	if err := client.addLogEntry(logentry); err != nil {
336
337
		return err
	}
338
	return client.storage.StoreLogs(client.logs)
339
340
}

341
// Attribute and credential getter methods
Sietse Ringers's avatar
Sietse Ringers committed
342

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

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

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

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

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

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

440
	return client.credentialsCache[id][counter], nil
441
442
}

Sietse Ringers's avatar
Sietse Ringers committed
443
// Methods used in the IRMA protocol
444

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

470
471
472
473
474
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))
475
		}
476
477
478
479
480
481
482
483
484
485
486
487
	}
	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 {
488
489
				if attr.Type.CredentialTypeIdentifier() != cred.Type {
					continue
490
				}
491
492
493
494
495
496
497
498
499
500
501
				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,
				})
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
			}
		}
		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) (
524
	candidates [][]*irma.AttributeIdentifier, missing map[int]map[int]MissingAttribute,
525
526
527
528
) {
	candidates = [][]*irma.AttributeIdentifier{}

	for _, con := range discon {
529
530
531
532
533
534
535
		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
		}

536
537
538
539
540
541
542
		// 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 {
543
544
			continue
		}
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.
569
570
func (client *Client) missingAttributes(discon irma.AttributeDisCon) map[int]map[int]MissingAttribute {
	missing := make(map[int]map[int]MissingAttribute, len(discon))
571
572

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

590
	return missing
591
592
}

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

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

	return
611
}
612

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

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

647
648
649
650
			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
651
			}
652

653
654
655
656
657
658
659
660
			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)
661
662
663
		}
	}

664
	return todisclose, attributeIndices, nil
665
666
}

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

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

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

701
	return builders, attributeIndices, timestamp, nil
702
703
704
}

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

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

718
719
720
721
722
// 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)
}

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

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
808
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
809
}
810

Sietse Ringers's avatar
Sietse Ringers committed
811
812
// Keyshare server handling

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

823
824
825
826
func (client *Client) UnenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	return client.genSchemeManagersList(false)
}

827
func (client *Client) EnrolledSchemeManagers() []irma.SchemeManagerIdentifier {
828
	return client.genSchemeManagersList(true)
829
830
}

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

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

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

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

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

	return nil
Sietse Ringers's avatar
Sietse Ringers committed
883
884
}

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