client.go 34 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
	logs             []*LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
48
	updates          []update
49

Sietse Ringers's avatar
Sietse Ringers committed
50
51
52
53
	// Where we store/load it to/from
	storage storage

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

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

65
66
type Preferences struct {
	EnableCrashReporting bool
67
68
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

188
	return cm, schemeMgrErr
189
190
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	logentry := &LogEntry{
		Type:    actionRemoval,
333
		Time:    irma.Timestamp(time.Now()),
334
335
		Removed: removed,
	}
336
	if err := client.addLogEntry(logentry); err != nil {
337
338
		return err
	}
339
	return client.storage.StoreLogs(client.logs)
340
341
}

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

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

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

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

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

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

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

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

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

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

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

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

537
538
539
540
541
542
543
		// 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 {
544
545
			continue
		}
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569

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

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

591
	return missing
592
593
}

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

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

	return
612
}
613

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

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

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

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

665
	return todisclose, attributeIndices, nil
666
667
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

886
887
888
// 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.
889
890
891
func (client *Client) KeyshareVerifyPin(pin string, schemeid irma.SchemeManagerIdentifier) (bool, int, int, error) {
	scheme := client.Configuration.SchemeManagers[schemeid]