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

import (
4
	"crypto/rand"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"math/big"
Sietse Ringers's avatar
Sietse Ringers committed
6
	"sort"
7
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
8

9
	"github.com/credentials/go-go-gadget-paillier"
10
11
	"github.com/credentials/irmago"
	"github.com/credentials/irmago/internal/fs"
12
	"github.com/getsentry/raven-go"
Sietse Ringers's avatar
Sietse Ringers committed
13
	"github.com/go-errors/errors"
14
15
16
	"github.com/mhe/gabi"
)

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

33
// Client (de)serializes credentials and keyshare server information
Sietse Ringers's avatar
Sietse Ringers committed
34
35
36
37
// 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.
38
type Client struct {
Sietse Ringers's avatar
Sietse Ringers committed
39
	// Stuff we manage on disk
40
	secretkey        *secretKey
41
42
43
	attributes       map[irma.CredentialTypeIdentifier][]*irma.AttributeList
	credentials      map[irma.CredentialTypeIdentifier]map[int]*credential
	keyshareServers  map[irma.SchemeManagerIdentifier]*keyshareServer
44
	paillierKeyCache *paillierPrivateKey
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
	// Where we store/load it to/from
	storage storage

51
52
53
	// configuration
	config clientConfiguration

Sietse Ringers's avatar
Sietse Ringers committed
54
	// Other state
55
	Configuration            *irma.Configuration
56
	UnenrolledSchemeManagers []irma.SchemeManagerIdentifier
57
58
59
	irmaConfigurationPath    string
	androidStoragePath       string
	handler                  ClientHandler
60
	state                    *issuanceState
61
62
}

63
64
65
66
67
68
69
70
71
72
type clientConfiguration struct {
	SendCrashReports bool
	ravenDSN         string
}

var defaultClientConfig clientConfiguration = clientConfiguration{
	SendCrashReports: true,
	ravenDSN:         "", // Set this in the init() function, empty string -> no crash reports
}

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 {
76
77
	EnrollmentError(manager irma.SchemeManagerIdentifier, err error)
	EnrollmentSuccess(manager irma.SchemeManagerIdentifier)
78
79
80
81
82
}

type ClientHandler interface {
	KeyshareHandler

83
	UpdateConfiguration(new *irma.IrmaIdentifierSet)
84
	UpdateAttributes()
Sietse Ringers's avatar
Sietse Ringers committed
85
86
}

87
88
89
90
type secretKey struct {
	Key *big.Int
}

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

117
	cm := &Client{
118
119
120
		credentials:           make(map[irma.CredentialTypeIdentifier]map[int]*credential),
		keyshareServers:       make(map[irma.SchemeManagerIdentifier]*keyshareServer),
		attributes:            make(map[irma.CredentialTypeIdentifier][]*irma.AttributeList),
121
122
		irmaConfigurationPath: irmaConfigurationPath,
		androidStoragePath:    androidStoragePath,
123
		handler:               handler,
124
125
	}

126
	cm.Configuration, err = irma.NewConfiguration(storagePath+"/irma_configuration", irmaConfigurationPath)
127
128
129
	if err != nil {
		return nil, err
	}
130
	if err = cm.Configuration.ParseFolder(); err != nil {
131
		return nil, err
132
133
134
	}

	// Ensure storage path exists, and populate it with necessary files
135
	cm.storage = storage{storagePath: storagePath, Configuration: cm.Configuration}
Sietse Ringers's avatar
Sietse Ringers committed
136
	if err = cm.storage.EnsureStorageExists(); err != nil {
137
138
139
		return nil, err
	}

140
141
142
143
144
	if cm.config, err = cm.storage.LoadClientConfig(); err != nil {
		return nil, err
	}
	cm.applyClientConfig()

145
	// Perform new update functions from clientUpdates, if any
146
147
148
149
150
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
Sietse Ringers's avatar
Sietse Ringers committed
151
	if cm.secretkey, err = cm.storage.LoadSecretKey(); err != nil {
152
153
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
154
	if cm.attributes, err = cm.storage.LoadAttributes(); err != nil {
155
156
		return nil, err
	}
157
	if cm.keyshareServers, err = cm.storage.LoadKeyshareServers(); err != nil {
158
159
		return nil, err
	}
160
	if cm.paillierKeyCache, err = cm.storage.LoadPaillierKeys(); err != nil {
161
162
		return nil, err
	}
163
164
165
	if cm.paillierKeyCache == nil {
		cm.paillierKey(false)
	}
166

167
168
	cm.UnenrolledSchemeManagers = cm.unenrolledSchemeManagers()
	if len(cm.UnenrolledSchemeManagers) > 1 {
169
170
171
172
173
174
		return nil, errors.New("Too many keyshare servers")
	}

	return cm, nil
}

175
// CredentialInfoList returns a list of information of all contained credentials.
176
177
func (client *Client) CredentialInfoList() irma.CredentialInfoList {
	list := irma.CredentialInfoList([]*irma.CredentialInfo{})
178

179
	for _, attrlistlist := range client.attributes {
180
181
182
		for index, attrlist := range attrlistlist {
			info := attrlist.Info()
			info.Index = index
183
			list = append(list, info)
Sietse Ringers's avatar
Sietse Ringers committed
184
185
		}
	}
186

Sietse Ringers's avatar
Sietse Ringers committed
187
188
189
190
	sort.Sort(list)
	return list
}

191
// addCredential adds the specified credential to the Client, saving its signature
Sietse Ringers's avatar
Sietse Ringers committed
192
// imediately, and optionally cm.attributes as well.
193
func (client *Client) addCredential(cred *credential, storeAttributes bool) (err error) {
Sietse Ringers's avatar
Sietse Ringers committed
194
195
	id := cred.CredentialType().Identifier()

196
	// Don't add duplicate creds
197
	for _, attrlistlist := range client.attributes {
198
		for _, attrs := range attrlistlist {
199
			if attrs.Hash() == cred.AttributeList().Hash() {
200
201
202
203
204
205
				return nil
			}
		}
	}

	// If this is a singleton credential type, ensure we have at most one by removing any previous instance
206
207
	if cred.CredentialType().IsSingleton && len(client.creds(id)) > 0 {
		client.remove(id, 0, false) // Index is 0, because if we're here we have exactly one
208
209
210
	}

	// Append the new cred to our attributes and credentials
211
212
213
	client.attributes[id] = append(client.attrs(id), cred.AttributeList())
	if _, exists := client.credentials[id]; !exists {
		client.credentials[id] = make(map[int]*credential)
Sietse Ringers's avatar
Sietse Ringers committed
214
	}
215
216
	counter := len(client.attributes[id]) - 1
	client.credentials[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
217

218
	if err = client.storage.StoreSignature(cred); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
219
220
221
		return
	}
	if storeAttributes {
222
		err = client.storage.StoreAttributes(client.attributes)
Sietse Ringers's avatar
Sietse Ringers committed
223
224
225
226
227
228
229
230
231
232
233
234
235
236
	}
	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

237
func (client *Client) remove(id irma.CredentialTypeIdentifier, index int, storenow bool) error {
238
	// Remove attributes
239
	list, exists := client.attributes[id]
240
241
242
243
	if !exists || index >= len(list) {
		return errors.Errorf("Can't remove credential %s-%d: no such credential", id.String(), index)
	}
	attrs := list[index]
244
	client.attributes[id] = append(list[:index], list[index+1:]...)
245
	if storenow {
246
		if err := client.storage.StoreAttributes(client.attributes); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
247
248
			return err
		}
249
250
251
	}

	// Remove credential
252
	if creds, exists := client.credentials[id]; exists {
253
		if _, exists := creds[index]; exists {
Sietse Ringers's avatar
Sietse Ringers committed
254
			delete(creds, index)
255
			client.credentials[id] = creds
256
257
258
259
		}
	}

	// Remove signature from storage
260
	if err := client.storage.DeleteSignature(attrs); err != nil {
261
262
263
		return err
	}

264
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
265
266
267
	removed[id] = attrs.Strings()

	if storenow {
268
		return client.addLogEntry(&LogEntry{
269
			Type:    actionRemoval,
270
			Time:    irma.Timestamp(time.Now()),
271
272
273
274
			Removed: removed,
		})
	}
	return nil
275
276
}

277
func (client *Client) RemoveCredential(id irma.CredentialTypeIdentifier, index int) error {
278
	return client.remove(id, index, true)
279
280
}

281
282
func (client *Client) RemoveCredentialByHash(hash string) error {
	cred, index, err := client.credentialByHash(hash)
283
284
285
	if err != nil {
		return err
	}
286
	return client.RemoveCredential(cred.CredentialType().Identifier(), index)
287
288
}

289
func (client *Client) RemoveAllCredentials() error {
290
	removed := map[irma.CredentialTypeIdentifier][]irma.TranslatedString{}
291
	for _, attrlistlist := range client.attributes {
Sietse Ringers's avatar
Sietse Ringers committed
292
293
294
295
		for _, attrs := range attrlistlist {
			if attrs.CredentialType() != nil {
				removed[attrs.CredentialType().Identifier()] = attrs.Strings()
			}
296
			client.storage.DeleteSignature(attrs)
297
298
		}
	}
299
	client.attributes = map[irma.CredentialTypeIdentifier][]*irma.AttributeList{}
300
	if err := client.storage.StoreAttributes(client.attributes); err != nil {
301
302
		return err
	}
303
304
305

	logentry := &LogEntry{
		Type:    actionRemoval,
306
		Time:    irma.Timestamp(time.Now()),
307
308
		Removed: removed,
	}
309
	if err := client.addLogEntry(logentry); err != nil {
310
311
		return err
	}
312
	return client.storage.StoreLogs(client.logs)
313
314
}

315
// Attribute and credential getter methods
Sietse Ringers's avatar
Sietse Ringers committed
316

Sietse Ringers's avatar
Sietse Ringers committed
317
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
318
func (client *Client) attrs(id irma.CredentialTypeIdentifier) []*irma.AttributeList {
319
	list, exists := client.attributes[id]
Sietse Ringers's avatar
Sietse Ringers committed
320
	if !exists {
321
		list = make([]*irma.AttributeList, 0, 1)
322
		client.attributes[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
323
324
325
326
327
	}
	return list
}

// creds returns cm.credentials[id], initializing it to an empty map if neccesary
328
func (client *Client) creds(id irma.CredentialTypeIdentifier) map[int]*credential {
329
	list, exists := client.credentials[id]
Sietse Ringers's avatar
Sietse Ringers committed
330
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
331
		list = make(map[int]*credential)
332
		client.credentials[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
333
334
335
336
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
337
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
338
func (client *Client) Attributes(id irma.CredentialTypeIdentifier, counter int) (attributes *irma.AttributeList) {
339
	list := client.attrs(id)
Sietse Ringers's avatar
Sietse Ringers committed
340
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
341
342
343
344
345
		return
	}
	return list[counter]
}

346
347
func (client *Client) credentialByHash(hash string) (*credential, int, error) {
	for _, attrlistlist := range client.attributes {
348
		for index, attrs := range attrlistlist {
349
			if attrs.Hash() == hash {
350
				cred, err := client.credential(attrs.CredentialType().Identifier(), index)
351
352
353
354
355
356
357
				return cred, index, err
			}
		}
	}
	return nil, 0, nil
}

358
func (client *Client) credentialByID(id irma.CredentialIdentifier) (*credential, error) {
359
	if _, exists := client.attributes[id.Type]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
360
361
		return nil, nil
	}
362
	for index, attrs := range client.attributes[id.Type] {
363
		if attrs.Hash() == id.Hash {
364
			return client.credential(attrs.CredentialType().Identifier(), index)
Sietse Ringers's avatar
Sietse Ringers committed
365
366
367
		}
	}
	return nil, nil
368
369
}

Sietse Ringers's avatar
Sietse Ringers committed
370
// credential returns the requested credential, or nil if we do not have it.
371
func (client *Client) credential(id irma.CredentialTypeIdentifier, counter int) (cred *credential, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
372
	// If the requested credential is not in credential map, we check if its attributes were
373
	// deserialized during New(). If so, there should be a corresponding signature file,
Sietse Ringers's avatar
Sietse Ringers committed
374
	// so we read that, construct the credential, and add it to the credential map
375
376
	if _, exists := client.creds(id)[counter]; !exists {
		attrs := client.Attributes(id, counter)
Sietse Ringers's avatar
Sietse Ringers committed
377
378
379
		if attrs == nil { // We do not have the requested cred
			return
		}
380
		sig, err := client.storage.LoadSignature(attrs)
Sietse Ringers's avatar
Sietse Ringers committed
381
382
383
384
385
386
387
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
388
		pk, err := attrs.PublicKey()
389
390
391
		if err != nil {
			return nil, err
		}
392
393
394
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
395
		cred, err := newCredential(&gabi.Credential{
396
			Attributes: append([]*big.Int{client.secretkey.Key}, attrs.Ints...),
397
			Signature:  sig,
398
			Pk:         pk,
399
		}, client.Configuration)
400
401
402
		if err != nil {
			return nil, err
		}
403
		client.credentials[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
404
405
	}

406
	return client.credentials[id][counter], nil
407
408
}

Sietse Ringers's avatar
Sietse Ringers committed
409
// Methods used in the IRMA protocol
410

411
// Candidates returns a list of attributes present in this client
Sietse Ringers's avatar
Sietse Ringers committed
412
// that satisfy the specified attribute disjunction.
413
414
func (client *Client) Candidates(disjunction *irma.AttributeDisjunction) []*irma.AttributeIdentifier {
	candidates := make([]*irma.AttributeIdentifier, 0, 10)
415
416

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
417
		credID := attribute.CredentialTypeIdentifier()
418
		if !client.Configuration.Contains(credID) {
419
420
			continue
		}
421
		creds := client.attributes[credID]
422
423
424
425
		count := len(creds)
		if count == 0 {
			continue
		}
426
		for _, attrs := range creds {
427
			id := &irma.AttributeIdentifier{Type: attribute, Hash: attrs.Hash()}
428
429
430
			if attribute.IsCredential() {
				candidates = append(candidates, id)
			} else {
431
				val := attrs.UntranslatedAttribute(attribute)
432
				if val == "" { // This won't handle empty attributes correctly
433
434
435
436
437
438
439
440
441
442
443
444
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

445
// CheckSatisfiability checks if this client has the required attributes
Sietse Ringers's avatar
Sietse Ringers committed
446
447
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
448
func (client *Client) CheckSatisfiability(
449
450
451
452
	disjunctions irma.AttributeDisjunctionList,
) ([][]*irma.AttributeIdentifier, irma.AttributeDisjunctionList) {
	candidates := [][]*irma.AttributeIdentifier{}
	missing := irma.AttributeDisjunctionList{}
453
	for i, disjunction := range disjunctions {
454
		candidates = append(candidates, []*irma.AttributeIdentifier{})
455
		candidates[i] = client.Candidates(disjunction)
456
		if len(candidates[i]) == 0 {
457
458
459
			missing = append(missing, disjunction)
		}
	}
460
	return candidates, missing
461
}
462

463
464
func (client *Client) groupCredentials(choice *irma.DisclosureChoice) (map[irma.CredentialIdentifier][]int, error) {
	grouped := make(map[irma.CredentialIdentifier][]int)
465
466
467
	if choice == nil || choice.Attributes == nil {
		return grouped, nil
	}
468
469
470
471
472
473
474
475

	for _, attribute := range choice.Attributes {
		identifier := attribute.Type
		ici := attribute.CredentialIdentifier()

		// If this is the first attribute of its credential type that we encounter
		// in the disclosure choice, then there is no slice yet at grouped[ici]
		if _, present := grouped[ici]; !present {
476
477
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
478
479
480
481
482
483
			grouped[ici] = indices
		}

		if identifier.IsCredential() {
			continue // In this case we only disclose the metadata attribute, which is already handled
		}
484
		index, err := client.Configuration.CredentialTypes[identifier.CredentialTypeIdentifier()].IndexOf(identifier)
485
486
487
488
		if err != nil {
			return nil, err
		}

Sietse Ringers's avatar
Sietse Ringers committed
489
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
490
		// which doesn't know about the secret key and metadata attribute, so +2
491
		grouped[ici] = append(grouped[ici], index+2)
492
493
494
495
496
	}

	return grouped, nil
}

497
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
498
func (client *Client) ProofBuilders(choice *irma.DisclosureChoice) (gabi.ProofBuilderList, error) {
499
	todisclose, err := client.groupCredentials(choice)
500
501
502
503
	if err != nil {
		return nil, err
	}

504
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
505
	for id, list := range todisclose {
506
		cred, err := client.credentialByID(id)
507
508
509
510
511
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
512
	return builders, nil
513
}
Sietse Ringers's avatar
Sietse Ringers committed
514

Sietse Ringers's avatar
Sietse Ringers committed
515
// Proofs computes disclosure proofs containing the attributes specified by choice.
516
func (client *Client) Proofs(choice *irma.DisclosureChoice, request irma.IrmaSession, issig bool) (gabi.ProofList, error) {
517
	builders, err := client.ProofBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
518
519
520
	if err != nil {
		return nil, err
	}
521
	return builders.BuildProofList(request.GetContext(), request.GetNonce(), issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
522
523
}

524
525
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
// for the future credentials as well as possibly any disclosed attributes.
526
func (client *Client) IssuanceProofBuilders(request *irma.IssuanceRequest) (gabi.ProofBuilderList, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
527
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
528
529
530
	if err != nil {
		return nil, err
	}
531
	client.state = state
Sietse Ringers's avatar
Sietse Ringers committed
532

533
	proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
534
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
535
		var pk *gabi.PublicKey
536
		pk, err = client.Configuration.PublicKey(futurecred.CredentialTypeID.IssuerIdentifier(), futurecred.KeyCounter)
537
538
539
		if err != nil {
			return nil, err
		}
540
		credBuilder := gabi.NewCredentialBuilder(
541
			pk, request.GetContext(), client.secretkey.Key, state.nonce2)
542
		state.builders = append(state.builders, credBuilder)
Sietse Ringers's avatar
Sietse Ringers committed
543
544
		proofBuilders = append(proofBuilders, credBuilder)
	}
Sietse Ringers's avatar
Sietse Ringers committed
545

546
	disclosures, err := client.ProofBuilders(request.Choice)
Sietse Ringers's avatar
Sietse Ringers committed
547
548
549
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
550
	proofBuilders = append(disclosures, proofBuilders...)
Sietse Ringers's avatar
Sietse Ringers committed
551
552
	return proofBuilders, nil
}
Sietse Ringers's avatar
Sietse Ringers committed
553

Sietse Ringers's avatar
Sietse Ringers committed
554
555
// IssueCommitments computes issuance commitments, along with disclosure proofs
// specified by choice.
556
func (client *Client) IssueCommitments(request *irma.IssuanceRequest) (*gabi.IssueCommitmentMessage, error) {
557
	proofBuilders, err := client.IssuanceProofBuilders(request)
Sietse Ringers's avatar
Sietse Ringers committed
558
559
560
	if err != nil {
		return nil, err
	}
561
	list := proofBuilders.BuildProofList(request.GetContext(), request.GetNonce(), false)
562
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: client.state.nonce2}, nil
Sietse Ringers's avatar
Sietse Ringers committed
563
564
}

Sietse Ringers's avatar
Sietse Ringers committed
565
566
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
567
func (client *Client) ConstructCredentials(msg []*gabi.IssueSignatureMessage, request *irma.IssuanceRequest) error {
568
	if len(msg) != len(client.state.builders) {
Sietse Ringers's avatar
Sietse Ringers committed
569
570
571
		return errors.New("Received unexpected amount of signatures")
	}

572
573
	// 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
574
	gabicreds := []*gabi.Credential{}
Sietse Ringers's avatar
Sietse Ringers committed
575
	for i, sig := range msg {
576
		attrs, err := request.Credentials[i].AttributeList(client.Configuration)
Sietse Ringers's avatar
Sietse Ringers committed
577
578
579
		if err != nil {
			return err
		}
580
		cred, err := client.state.builders[i].ConstructCredential(sig, attrs.Ints)
Sietse Ringers's avatar
Sietse Ringers committed
581
582
583
		if err != nil {
			return err
		}
584
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
585
586
	}

587
	for _, gabicred := range gabicreds {
588
		newcred, err := newCredential(gabicred, client.Configuration)
589
590
591
		if err != nil {
			return err
		}
592
		if err = client.addCredential(newcred, true); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
593
594
			return err
		}
Sietse Ringers's avatar
Sietse Ringers committed
595
	}
596

Sietse Ringers's avatar
Sietse Ringers committed
597
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
598
}
599

Sietse Ringers's avatar
Sietse Ringers committed
600
601
// Keyshare server handling

602
// PaillierKey returns a new Paillier key (and generates a new one in a goroutine).
603
604
func (client *Client) paillierKey(wait bool) *paillierPrivateKey {
	cached := client.paillierKeyCache
Sietse Ringers's avatar
Sietse Ringers committed
605
	ch := make(chan bool)
606

607
608
	// Would just write client.paillierKeyCache instead of cached here, but the worker
	// modifies client.paillierKeyCache, and we must be sure that the boolean here and
609
	// the if-clause below match.
610
	go client.paillierKeyWorker(cached == nil && wait, ch)
611
	if cached == nil && wait {
Sietse Ringers's avatar
Sietse Ringers committed
612
		<-ch
613
		// generate yet another one for future calls, but no need to wait now
614
		go client.paillierKeyWorker(false, ch)
Sietse Ringers's avatar
Sietse Ringers committed
615
	}
616
	return client.paillierKeyCache
617
}
Sietse Ringers's avatar
Sietse Ringers committed
618

619
func (client *Client) paillierKeyWorker(wait bool, ch chan bool) {
620
	newkey, _ := paillier.GenerateKey(rand.Reader, 2048)
621
622
	client.paillierKeyCache = (*paillierPrivateKey)(newkey)
	client.storage.StorePaillierKeys(client.paillierKeyCache)
623
624
625
626
627
	if wait {
		ch <- true
	}
}

628
629
func (client *Client) unenrolledSchemeManagers() []irma.SchemeManagerIdentifier {
	list := []irma.SchemeManagerIdentifier{}
630
	for name, manager := range client.Configuration.SchemeManagers {
631
		if _, contains := client.keyshareServers[name]; manager.Distributed() && !contains {
Sietse Ringers's avatar
Sietse Ringers committed
632
			list = append(list, manager.Identifier())
Sietse Ringers's avatar
Sietse Ringers committed
633
634
635
636
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
637

638
// KeyshareEnroll attempts to enroll at the keyshare server of the specified scheme manager.
639
func (client *Client) KeyshareEnroll(manager irma.SchemeManagerIdentifier, email, pin string) {
Sietse Ringers's avatar
Sietse Ringers committed
640
	go func() {
641
		defer func() {
642
			handlePanic(func(err *irma.SessionError) {
643
644
				if client.handler != nil {
					client.handler.EnrollmentError(manager, err)
645
646
647
648
				}
			})
		}()

649
650
		err := client.keyshareEnrollWorker(manager, email, pin)
		client.UnenrolledSchemeManagers = client.unenrolledSchemeManagers()
Sietse Ringers's avatar
Sietse Ringers committed
651
		if err != nil {
652
			client.handler.EnrollmentError(manager, err)
Sietse Ringers's avatar
Sietse Ringers committed
653
		} else {
654
			client.handler.EnrollmentSuccess(manager)
Sietse Ringers's avatar
Sietse Ringers committed
655
656
		}
	}()
657

658
659
}

660
func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifier, email, pin string) error {
661
	manager, ok := client.Configuration.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
662
663
664
665
666
667
668
669
670
671
	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")
	}

672
	transport := irma.NewHTTPTransport(manager.KeyshareServer)
673
	kss, err := newKeyshareServer(client.paillierKey(true), manager.KeyshareServer, email)
Sietse Ringers's avatar
Sietse Ringers committed
674
675
676
	if err != nil {
		return err
	}
677
	message := keyshareEnrollment{
Sietse Ringers's avatar
Sietse Ringers committed
678
679
680
681
682
683
684
685
686
687
688
		Username:  email,
		Pin:       kss.HashedPin(pin),
		PublicKey: (*paillierPublicKey)(&kss.PrivateKey.PublicKey),
	}

	result := &struct{}{}
	err = transport.Post("web/users/selfenroll", result, message)
	if err != nil {
		return err
	}

689
690
	client.keyshareServers[managerID] = kss
	return client.storage.StoreKeyshareServers(client.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
691
692
}

693
// KeyshareRemove unenrolls the keyshare server of the specified scheme manager.
694
func (client *Client) KeyshareRemove(manager irma.SchemeManagerIdentifier) error {
695
	if _, contains := client.keyshareServers[manager]; !contains {
Sietse Ringers's avatar
Sietse Ringers committed
696
697
		return errors.New("Can't uninstall unknown keyshare server")
	}
698
699
	delete(client.keyshareServers, manager)
	return client.storage.StoreKeyshareServers(client.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
700
}
Sietse Ringers's avatar
Sietse Ringers committed
701

702
func (client *Client) KeyshareRemoveAll() error {
703
	client.keyshareServers = map[irma.SchemeManagerIdentifier]*keyshareServer{}
704
705
	client.UnenrolledSchemeManagers = client.unenrolledSchemeManagers()
	return client.storage.StoreKeyshareServers(client.keyshareServers)
706
707
}

Sietse Ringers's avatar
Sietse Ringers committed
708
709
// Add, load and store log entries

710
711
712
func (client *Client) addLogEntry(entry *LogEntry) error {
	client.logs = append(client.logs, entry)
	return client.storage.StoreLogs(client.logs)
713
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
714
715
}

716
717
func (client *Client) Logs() ([]*LogEntry, error) {
	if client.logs == nil || len(client.logs) == 0 {
Sietse Ringers's avatar
Sietse Ringers committed
718
		var err error
719
		client.logs, err = client.storage.LoadLogs()
Sietse Ringers's avatar
Sietse Ringers committed
720
721
722
723
		if err != nil {
			return nil, err
		}
	}
724
	return client.logs, nil
Sietse Ringers's avatar
Sietse Ringers committed
725
}
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743

func (client *Client) SendCrashReports(val bool) {
	if val == client.config.SendCrashReports {
		return
	}

	client.config.SendCrashReports = val
	if val {
		raven.SetDSN(client.config.ravenDSN)
	}
	_ = client.storage.StoreClientConfig(client.config)
}

func (client *Client) applyClientConfig() {
	if client.config.SendCrashReports && client.config.ravenDSN != "" {
		raven.SetDSN(client.config.ravenDSN)
	}
}