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
// 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
27
// immediately need to be available anyway.
Sietse Ringers's avatar
Sietse Ringers committed
28
29
30
31
32
//
// - 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)
	}
}