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

import (
4
	"strconv"
5
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
6

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

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

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

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

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

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

63
64
type Preferences struct {
	EnableCrashReporting bool
65
66
}

67
var defaultPreferences = Preferences{
68
	EnableCrashReporting: true,
69
70
}

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

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

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

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

95
96
97
98
type secretKey struct {
	Key *big.Int
}

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

125
	cm := &Client{
126
		credentialsCache:      make(map[irma.CredentialTypeIdentifier]map[int]*credential),
127
128
		keyshareServers:       make(map[irma.SchemeManagerIdentifier]*keyshareServer),
		attributes:            make(map[irma.CredentialTypeIdentifier][]*irma.AttributeList),
129
130
		irmaConfigurationPath: irmaConfigurationPath,
		androidStoragePath:    androidStoragePath,
131
		handler:               handler,
132
133
	}

134
	cm.Configuration, err = irma.NewConfigurationFromAssets(storagePath+"/irma_configuration", irmaConfigurationPath)
135
136
137
	if err != nil {
		return nil, err
	}
138

139
	schemeMgrErr := cm.Configuration.ParseOrRestoreFolder()
140
141
142
143
144
	// 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
145
	}
146
147

	// Ensure storage path exists, and populate it with necessary files
148
	cm.storage = storage{storagePath: storagePath, Configuration: cm.Configuration}
Sietse Ringers's avatar
Sietse Ringers committed
149
	if err = cm.storage.EnsureStorageExists(); err != nil {
150
151
152
		return nil, err
	}

153
	if cm.Preferences, err = cm.storage.LoadPreferences(); err != nil {
154
155
		return nil, err
	}
156
	cm.applyPreferences()
157

158
	// Perform new update functions from clientUpdates, if any
159
160
161
162
163
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
Sietse Ringers's avatar
Sietse Ringers committed
164
	if cm.secretkey, err = cm.storage.LoadSecretKey(); err != nil {
165
166
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
167
	if cm.attributes, err = cm.storage.LoadAttributes(); err != nil {
168
169
		return nil, err
	}
170
	if cm.keyshareServers, err = cm.storage.LoadKeyshareServers(); err != nil {
171
172
173
		return nil, err
	}

174
	if len(cm.UnenrolledSchemeManagers()) > 1 {
175
176
177
		return nil, errors.New("Too many keyshare servers")
	}

178
	return cm, schemeMgrErr
179
180
}

181
// CredentialInfoList returns a list of information of all contained credentials.
182
183
func (client *Client) CredentialInfoList() irma.CredentialInfoList {
	list := irma.CredentialInfoList([]*irma.CredentialInfo{})
184

185
	for _, attrlistlist := range client.attributes {
186
		for _, attrlist := range attrlistlist {
187
			info := attrlist.Info()
188
189
190
			if info == nil {
				continue
			}
191
			list = append(list, info)
Sietse Ringers's avatar
Sietse Ringers committed
192
193
		}
	}
194

Sietse Ringers's avatar
Sietse Ringers committed
195
196
197
	return list
}

198
// addCredential adds the specified credential to the Client, saving its signature
Sietse Ringers's avatar
Sietse Ringers committed
199
// imediately, and optionally cm.attributes as well.
200
func (client *Client) addCredential(cred *credential, storeAttributes bool) (err error) {
201
202
203
204
	id := irma.NewCredentialTypeIdentifier("")
	if cred.CredentialType() != nil {
		id = cred.CredentialType().Identifier()
	}
Sietse Ringers's avatar
Sietse Ringers committed
205

206
	// Don't add duplicate creds
207
	for _, attrlistlist := range client.attributes {
208
		for _, attrs := range attrlistlist {
209
			if attrs.Hash() == cred.AttributeList().Hash() {
210
211
212
213
214
215
				return nil
			}
		}
	}

	// If this is a singleton credential type, ensure we have at most one by removing any previous instance
216
217
218
219
	if !id.Empty() && cred.CredentialType().IsSingleton {
		for len(client.attrs(id)) != 0 {
			client.remove(id, 0, false)
		}
220
221
222
	}

	// Append the new cred to our attributes and credentials
223
	client.attributes[id] = append(client.attrs(id), cred.AttributeList())
224
	if !id.Empty() {
225
226
		if _, exists := client.credentialsCache[id]; !exists {
			client.credentialsCache[id] = make(map[int]*credential)
227
228
		}
		counter := len(client.attributes[id]) - 1
229
		client.credentialsCache[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
230
231
	}

232
	if err = client.storage.StoreSignature(cred); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
233
234
235
		return
	}
	if storeAttributes {
236
		err = client.storage.StoreAttributes(client.attributes)
Sietse Ringers's avatar
Sietse Ringers committed
237
238
239
240
241
242
243
244
245
246
247
248
249
250
	}
	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

251
func (client *Client) remove(id irma.CredentialTypeIdentifier, index int, storenow bool) error {
252
	// Remove attributes
253
	list, exists := client.attributes[id]
254
255
256
257
	if !exists || index >= len(list) {
		return errors.Errorf("Can't remove credential %s-%d: no such credential", id.String(), index)
	}
	attrs := list[index]
258
	client.attributes[id] = append(list[:index], list[index+1:]...)
259
	if storenow {
260
		if err := client.storage.StoreAttributes(client.attributes); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
261
262
			return err
		}
263
264
265
	}

	// Remove credential
266
	if creds, exists := client.credentialsCache[id]; exists {
267
		if _, exists := creds[index]; exists {
Sietse Ringers's avatar
Sietse Ringers committed
268
			delete(creds, index)
269
			client.credentialsCache[id] = creds
270
271
272
273
		}
	}

	// Remove signature from storage
274
	if err := client.storage.DeleteSignature(attrs); err != nil {
275
276
277
		return err
	}

278
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
279
280
281
	removed[id] = attrs.Strings()

	if storenow {
282
		return client.addLogEntry(&LogEntry{
283
			Type:    actionRemoval,
284
			Time:    irma.Timestamp(time.Now()),
285
286
287
288
			Removed: removed,
		})
	}
	return nil
289
290
}

Sietse Ringers's avatar
Sietse Ringers committed
291
// RemoveCredential removes the specified credential.
292
func (client *Client) RemoveCredential(id irma.CredentialTypeIdentifier, index int) error {
293
	return client.remove(id, index, true)
294
295
}

Sietse Ringers's avatar
Sietse Ringers committed
296
// RemoveCredentialByHash removes the specified credential.
297
298
func (client *Client) RemoveCredentialByHash(hash string) error {
	cred, index, err := client.credentialByHash(hash)
299
300
301
	if err != nil {
		return err
	}
302
	return client.RemoveCredential(cred.CredentialType().Identifier(), index)
303
304
}

Sietse Ringers's avatar
Sietse Ringers committed
305
// RemoveAllCredentials removes all credentials.
306
func (client *Client) RemoveAllCredentials() error {
307
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
308
	for _, attrlistlist := range client.attributes {
Sietse Ringers's avatar
Sietse Ringers committed
309
310
311
312
		for _, attrs := range attrlistlist {
			if attrs.CredentialType() != nil {
				removed[attrs.CredentialType().Identifier()] = attrs.Strings()
			}
313
			client.storage.DeleteSignature(attrs)
314
315
		}
	}
316
	client.attributes = map[irma.CredentialTypeIdentifier][]*irma.AttributeList{}
317
	if err := client.storage.StoreAttributes(client.attributes); err != nil {
318
319
		return err
	}
320
321
322

	logentry := &LogEntry{
		Type:    actionRemoval,
323
		Time:    irma.Timestamp(time.Now()),
324
325
		Removed: removed,
	}
326
	if err := client.addLogEntry(logentry); err != nil {
327
328
		return err
	}
329
	return client.storage.StoreLogs(client.logs)
330
331
}

332
// Attribute and credential getter methods
Sietse Ringers's avatar
Sietse Ringers committed
333

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

// creds returns cm.credentials[id], initializing it to an empty map if neccesary
345
func (client *Client) creds(id irma.CredentialTypeIdentifier) map[int]*credential {
346
	list, exists := client.credentialsCache[id]
Sietse Ringers's avatar
Sietse Ringers committed
347
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
348
		list = make(map[int]*credential)
349
		client.credentialsCache[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
350
351
352
353
	}
	return list
}

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

363
364
func (client *Client) credentialByHash(hash string) (*credential, int, error) {
	for _, attrlistlist := range client.attributes {
365
		for index, attrs := range attrlistlist {
366
			if attrs.Hash() == hash {
367
				cred, err := client.credential(attrs.CredentialType().Identifier(), index)
368
369
370
371
372
373
374
				return cred, index, err
			}
		}
	}
	return nil, 0, nil
}

375
func (client *Client) credentialByID(id irma.CredentialIdentifier) (*credential, error) {
376
	if _, exists := client.attributes[id.Type]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
377
378
		return nil, nil
	}
379
	for index, attrs := range client.attributes[id.Type] {
380
		if attrs.Hash() == id.Hash {
381
			return client.credential(attrs.CredentialType().Identifier(), index)
Sietse Ringers's avatar
Sietse Ringers committed
382
383
384
		}
	}
	return nil, nil
385
386
}

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

423
	return client.credentialsCache[id][counter], nil
424
425
}

Sietse Ringers's avatar
Sietse Ringers committed
426
// Methods used in the IRMA protocol
427

428
// Candidates returns a list of attributes present in this client
Sietse Ringers's avatar
Sietse Ringers committed
429
// that satisfy the specified attribute disjunction.
430
431
func (client *Client) Candidates(disjunction *irma.AttributeDisjunction) []*irma.AttributeIdentifier {
	candidates := make([]*irma.AttributeIdentifier, 0, 10)
432
433

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
434
		credID := attribute.CredentialTypeIdentifier()
435
		if !client.Configuration.Contains(credID) {
436
437
			continue
		}
438
		creds := client.attributes[credID]
439
440
441
442
		count := len(creds)
		if count == 0 {
			continue
		}
443
		for _, attrs := range creds {
444
445
446
			if !attrs.IsValid() {
				continue
			}
447
			id := &irma.AttributeIdentifier{Type: attribute, CredentialHash: attrs.Hash()}
448
449
450
			if attribute.IsCredential() {
				candidates = append(candidates, id)
			} else {
451
				val := attrs.UntranslatedAttribute(attribute)
452
				if val == nil {
453
454
					continue
				}
455
				if !disjunction.HasValues() {
456
					candidates = append(candidates, id)
457
458
459
460
461
				} else {
					requiredValue, present := disjunction.Values[attribute]
					if !present || requiredValue == nil || *val == *requiredValue {
						candidates = append(candidates, id)
					}
462
463
464
465
466
467
468
469
				}
			}
		}
	}

	return candidates
}

470
// CheckSatisfiability checks if this client has the required attributes
Sietse Ringers's avatar
Sietse Ringers committed
471
472
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
473
func (client *Client) CheckSatisfiability(
474
475
476
477
	disjunctions irma.AttributeDisjunctionList,
) ([][]*irma.AttributeIdentifier, irma.AttributeDisjunctionList) {
	candidates := [][]*irma.AttributeIdentifier{}
	missing := irma.AttributeDisjunctionList{}
478
	for i, disjunction := range disjunctions {
479
		candidates = append(candidates, []*irma.AttributeIdentifier{})
480
		candidates[i] = client.Candidates(disjunction)
481
		if len(candidates[i]) == 0 {
482
483
484
			missing = append(missing, disjunction)
		}
	}
485
	return candidates, missing
486
}
487

488
489
490
491
492
493
494
495
496
497
// 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,
498
) {
499
	if choice == nil || choice.Attributes == nil {
500
		return []attributeGroup{}, irma.DisclosedAttributeIndices{}, nil
501
	}
502

503
504
	// maps an irma.CredentialIdentifier to its index in the final ProofList
	credIndices := make(map[irma.CredentialIdentifier]int)
505
	todisclose := make([]attributeGroup, 0, len(choice.Attributes))
506
507
	attributeIndices := make(irma.DisclosedAttributeIndices, len(choice.Attributes))
	for i, attribute := range choice.Attributes {
508
		var credIndex int
509
		ici := attribute.CredentialIdentifier()
510
511
512
513
514
515
516
517
		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]
518
519
		}

520
		identifier := attribute.Type
521
		if identifier.IsCredential() {
522
			attributeIndices[i] = []*irma.DisclosedAttributeIndex{
523
				{CredentialIndex: credIndex, AttributeIndex: 1, Identifier: ici},
524
			}
525
			continue // In this case we only disclose the metadata attribute, which is already handled above
526
		}
527
528

		attrIndex, err := client.Configuration.CredentialTypes[identifier.CredentialTypeIdentifier()].IndexOf(identifier)
529
		if err != nil {
530
531
			return nil, nil, err
		}
532
533
		// 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
534
		attributeIndices[i] = []*irma.DisclosedAttributeIndex{
535
			{CredentialIndex: credIndex, AttributeIndex: attrIndex + 2, Identifier: ici},
536
		}
537
		todisclose[credIndex].attrs = append(todisclose[credIndex].attrs, attrIndex+2)
538
539
	}

540
	return todisclose, attributeIndices, nil
541
542
}

543
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
544
545
func (client *Client) ProofBuilders(choice *irma.DisclosureChoice, request irma.SessionRequest, issig bool,
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, error) {
546
	todisclose, attributeIndices, err := client.groupCredentials(choice)
547
	if err != nil {
548
		return nil, nil, err
549
550
	}

551
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
552
553
	for _, grp := range todisclose {
		cred, err := client.credentialByID(grp.cred)
554
		if err != nil {
555
			return nil, nil, err
556
		}
557
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(grp.attrs))
558
	}
559
560
561
562
563
564
565
566
567
568
569
570
571
572

	if issig {
		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)
		}
		r := request.(*irma.SignatureRequest)
		r.Timestamp, err = irma.GetTimestamp(r.Message, sigs, disclosed)
		if err != nil {
573
			return nil, nil, err
574
575
576
		}
	}

577
	return builders, attributeIndices, nil
578
579
580
}

// Proofs computes disclosure proofs containing the attributes specified by choice.
581
582
func (client *Client) Proofs(choice *irma.DisclosureChoice, request irma.SessionRequest, issig bool) (*irma.Disclosure, error) {
	builders, choices, err := client.ProofBuilders(choice, request, issig)
583
584
585
586
	if err != nil {
		return nil, err
	}

587
588
589
590
	return &irma.Disclosure{
		Proofs:  builders.BuildProofList(request.GetContext(), request.GetNonce(), issig),
		Indices: choices,
	}, nil
Sietse Ringers's avatar
Sietse Ringers committed
591
592
}

593
594
595
596
597
// 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)
}

598
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
599
600
// 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.
601
602
func (client *Client) IssuanceProofBuilders(request *irma.IssuanceRequest,
) (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
603
	issuerProofNonce, err := generateIssuerProofNonce()
Sietse Ringers's avatar
Sietse Ringers committed
604
	if err != nil {
605
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
606
	}
607
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
608
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
609
		var pk *gabi.PublicKey
610
		pk, err = client.Configuration.PublicKey(futurecred.CredentialTypeID.IssuerIdentifier(), futurecred.KeyCounter)
611
		if err != nil {
612
			return nil, nil, nil, err
613
		}
614
		credBuilder := gabi.NewCredentialBuilder(
615
616
			pk, request.GetContext(), client.secretkey.Key, issuerProofNonce)
		builders = append(builders, credBuilder)
Sietse Ringers's avatar
Sietse Ringers committed
617
	}
Sietse Ringers's avatar
Sietse Ringers committed
618

619
	disclosures, choices, err := client.ProofBuilders(request.Choice, request, false)
Sietse Ringers's avatar
Sietse Ringers committed
620
	if err != nil {
621
		return nil, nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
622
	}
623
	builders = append(disclosures, builders...)
624
	return builders, choices, issuerProofNonce, nil
Sietse Ringers's avatar
Sietse Ringers committed
625
}
Sietse Ringers's avatar
Sietse Ringers committed
626

627
628
// 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.
629
630
631
func (client *Client) IssueCommitments(request *irma.IssuanceRequest,
) (*irma.IssueCommitmentMessage, gabi.ProofBuilderList, error) {
	builders, choices, issuerProofNonce, err := client.IssuanceProofBuilders(request)
Sietse Ringers's avatar
Sietse Ringers committed
632
	if err != nil {
633
		return nil, nil, err
Sietse Ringers's avatar
Sietse Ringers committed
634
	}
635
636
637
638
639
640
641
	return &irma.IssueCommitmentMessage{
		IssueCommitmentMessage: &gabi.IssueCommitmentMessage{
			Proofs: builders.BuildProofList(request.GetContext(), request.GetNonce(), false),
			Nonce2: issuerProofNonce,
		},
		Indices: choices,
	}, builders, nil
Sietse Ringers's avatar
Sietse Ringers committed
642
643
}

644
645
646
647
// 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
648
649
650
		return errors.New("Received unexpected amount of signatures")
	}

651
652
	// 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
653
	gabicreds := []*gabi.Credential{}
654
655
656
657
658
659
660
661
662
	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]
		attrs, err := request.Credentials[i-offset].AttributeList(client.Configuration, irma.GetMetadataVersion(request.GetVersion()))
Sietse Ringers's avatar
Sietse Ringers committed
663
664
665
		if err != nil {
			return err
		}
666
		cred, err := credbuilder.ConstructCredential(sig, attrs.Ints)
Sietse Ringers's avatar
Sietse Ringers committed
667
668
669
		if err != nil {
			return err
		}
670
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
671
672
	}

673
	for _, gabicred := range gabicreds {
674
		newcred, err := newCredential(gabicred, client.Configuration)
675
676
677
		if err != nil {
			return err
		}
678
		if err = client.addCredential(newcred, true); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
679
680
			return err
		}
Sietse Ringers's avatar
Sietse Ringers committed
681
	}
682

Sietse Ringers's avatar
Sietse Ringers committed
683
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
684
}
685

Sietse Ringers's avatar
Sietse Ringers committed
686
687
// Keyshare server handling

688
func (client *Client) genSchemeManagersList(enrolled bool) []irma.SchemeManagerIdentifier {
689
	list := []irma.SchemeManagerIdentifier{}
690
	for name, manager := range client.Configuration.SchemeManagers {
691
		if _, contains := client.keyshareServers[name]; manager.Distributed() && contains == enrolled {
Sietse Ringers's avatar
Sietse Ringers committed
692
			list = append(list, manager.Identifier())
Sietse Ringers's avatar
Sietse Ringers committed
693
694
695
696
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
697

698
699
700
701
func (client *Client) UnenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	return client.genSchemeManagersList(false)
}

702
func (client *Client) EnrolledSchemeManagers() []irma.SchemeManagerIdentifier {
703
	return client.genSchemeManagersList(true)
704
705
}

706
// KeyshareEnroll attempts to enroll at the keyshare server of the specified scheme manager.
707
func (client *Client) KeyshareEnroll(manager irma.SchemeManagerIdentifier, email *string, pin string, lang string) {
Sietse Ringers's avatar
Sietse Ringers committed
708
	go func() {
709
		err := client.keyshareEnrollWorker(manager, email, pin, lang)
Sietse Ringers's avatar
Sietse Ringers committed
710
		if err != nil {
Tomas's avatar
Tomas committed
711
			client.handler.EnrollmentFailure(manager, err)
Sietse Ringers's avatar
Sietse Ringers committed
712
713
		}
	}()
714
715
}

716
func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifier, email *string, pin string, lang string) error {
717
	manager, ok := client.Configuration.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
718
719
720
721
722
723
724
725
726
727
	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")
	}

728
	transport := irma.NewHTTPTransport(manager.KeyshareServer)
729
	kss, err := newKeyshareServer(managerID, manager.KeyshareServer)
Sietse Ringers's avatar
Sietse Ringers committed
730
731
732
	if err != nil {
		return err
	}
733
	message := keyshareEnrollment{
734
735
736
		Email:    email,
		Pin:      kss.HashedPin(pin),
		Language: lang,
Sietse Ringers's avatar
Sietse Ringers committed
737
738
	}

739
740
	qr := &irma.Qr{}
	err = transport.Post("client/register", qr, message)
Sietse Ringers's avatar
Sietse Ringers committed
741
742
743
744
	if err != nil {
		return err
	}

745
746
747
748
749
	// 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.
750
	client.keyshareServers[managerID] = kss
751
	client.newQrSession(qr, &keyshareEnrollmentHandler{
752
753
754
755
756
757
		client: client,
		pin:    pin,
		kss:    kss,
	})

	return nil
Sietse Ringers's avatar
Sietse Ringers committed
758
759
}

760
761
762
// 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.
763
764
765
func (client *Client) KeyshareVerifyPin(pin string, schemeid irma.SchemeManagerIdentifier) (bool, int, int, error) {
	scheme := client.Configuration.SchemeManagers[schemeid]
	if scheme == nil || !scheme.Distributed() {
766
767
768
769
770
		return false, 0, 0, &irma.SessionError{
			Err:       errors.Errorf("Can't verify pin of scheme %s", schemeid.String()),
			ErrorType: irma.ErrorUnknownSchemeManager,
			Info:      schemeid.String(),
		}
771
772
773
774
775
	}
	kss := client.keyshareServers[schemeid]
	return verifyPinWorker(pin, kss, irma.NewHTTPTransport(kss.URL))
}

776
func (client *Client) KeyshareChangePin(manager irma.SchemeManagerIdentifier, oldPin string, newPin string) {
777
	go func() {
778
		err := client.keyshareChangePinWorker(manager, oldPin, newPin)
779
		if err != nil {
780
			client.handler.ChangePinFailure(manager, err)
781
782
783
784
		}
	}()
}

785
func (client *Client) keyshareChangePinWorker(managerID irma.SchemeManagerIdentifier, oldPin string, newPin string) error {
786
787
788
789
790
791
	kss, ok := client.keyshareServers[managerID]
	if !ok {
		return errors.New("Unknown keyshare server")
	}

	transport := irma.NewHTTPTransport(kss.URL)
792
	message := keyshareChangepin{
793
		Username: kss.Username,
794
795
		OldPin:   kss.HashedPin(oldPin),
		NewPin:   kss.HashedPin(newPin),
796
797
798
799
800
801
802
	}

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

804
805
	switch res.Status {
	case kssPinSuccess:
806
		client.handler.ChangePinSuccess(managerID)
807
	case kssPinFailure:
808
809
810
811
812
		attempts, err := strconv.Atoi(res.Message)
		if err != nil {
			return err
		}
		client.handler.ChangePinIncorrect(managerID, attempts)
813
	case kssPinError:
814
815
816
817
818
		timeout, err := strconv.Atoi(res.Message)
		if err != nil {
			return err
		}
		client.handler.ChangePinBlocked(managerID, timeout)
819
820
	default:
		return errors.New("Unknown keyshare response")
821
	}
822

823
824
825
	return nil
}

826
// KeyshareRemove unenrolls the keyshare server of the specified scheme manager.
827
func (client *Client) KeyshareRemove(manager irma.SchemeManagerIdentifier) error {
828
	if _, contains := client.keyshareServers[manager]; !contains {
Sietse Ringers's avatar
Sietse Ringers committed
829
830
		return errors.New("Can't uninstall unknown keyshare server")
	}
831
832
	delete(client.keyshareServers, manager)
	return client.storage.StoreKeyshareServers(client.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
833
}
Sietse Ringers's avatar
Sietse Ringers committed
834

Sietse Ringers's avatar
Sietse Ringers committed
835
// KeyshareRemoveAll removes all keyshare server registrations.
836
func (client *Client) KeyshareRemoveAll() error {
837
	client.keyshareServers = map[irma.SchemeManagerIdentifier]*keyshareServer{}
838
	return client.storage.StoreKeyshareServers(client.keyshareServers)
839
840
}

Sietse Ringers's avatar
Sietse Ringers committed
841
842
// Add, load and store log entries