client.go 22.1 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"
Sietse Ringers's avatar
Sietse Ringers committed
12
	"github.com/go-errors/errors"
13
14
15
	"github.com/mhe/gabi"
)

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

32
// Client (de)serializes credentials and keyshare server information
Sietse Ringers's avatar
Sietse Ringers committed
33
34
35
36
// 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.
37
type Client struct {
Sietse Ringers's avatar
Sietse Ringers committed
38
	// Stuff we manage on disk
39
	secretkey        *secretKey
40
41
42
	attributes       map[irmago.CredentialTypeIdentifier][]*irmago.AttributeList
	credentials      map[irmago.CredentialTypeIdentifier]map[int]*credential
	keyshareServers  map[irmago.SchemeManagerIdentifier]*keyshareServer
43
	paillierKeyCache *paillierPrivateKey
Sietse Ringers's avatar
Sietse Ringers committed
44
	logs             []*LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
45
	updates          []update
46

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

	// Other state
51
52
	ConfigurationStore       *irmago.ConfigurationStore
	UnenrolledSchemeManagers []irmago.SchemeManagerIdentifier
53
54
55
	irmaConfigurationPath    string
	androidStoragePath       string
	handler                  ClientHandler
56
	state                    *issuanceState
57
58
59
}

// KeyshareHandler is used for asking the user for his email address and PIN,
60
// for enrolling at a keyshare server.
61
type KeyshareHandler interface {
62
63
	EnrollmentError(manager irmago.SchemeManagerIdentifier, err error)
	EnrollmentSuccess(manager irmago.SchemeManagerIdentifier)
64
65
66
67
68
}

type ClientHandler interface {
	KeyshareHandler

69
	UpdateConfigurationStore(new *irmago.IrmaIdentifierSet)
70
	UpdateAttributes()
Sietse Ringers's avatar
Sietse Ringers committed
71
72
}

73
74
75
76
type secretKey struct {
	Key *big.Int
}

77
// New creates a new Client that uses the directory
78
79
80
81
// 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),
82
// and handler is used for informing the user of new stuff, and when a
83
// enrollment to a keyshare server needs to happen.
84
// The client returned by this function has been fully deserialized
85
86
// and is ready for use.
//
Sietse Ringers's avatar
Sietse Ringers committed
87
88
// NOTE: It is the responsibility of the caller that there exists a (properly
// protected) directory at storagePath!
89
func New(
90
91
92
	storagePath string,
	irmaConfigurationPath string,
	androidStoragePath string,
93
	handler ClientHandler,
94
) (*Client, error) {
95
	var err error
96
	if err = fs.AssertPathExists(storagePath); err != nil {
97
98
		return nil, err
	}
99
	if err = fs.AssertPathExists(irmaConfigurationPath); err != nil {
100
101
102
		return nil, err
	}

103
	cm := &Client{
104
105
106
		credentials:           make(map[irmago.CredentialTypeIdentifier]map[int]*credential),
		keyshareServers:       make(map[irmago.SchemeManagerIdentifier]*keyshareServer),
		attributes:            make(map[irmago.CredentialTypeIdentifier][]*irmago.AttributeList),
107
108
		irmaConfigurationPath: irmaConfigurationPath,
		androidStoragePath:    androidStoragePath,
109
		handler:               handler,
110
111
	}

112
	cm.ConfigurationStore, err = irmago.NewConfigurationStore(storagePath+"/irma_configuration", irmaConfigurationPath)
113
114
115
116
117
	if err != nil {
		return nil, err
	}
	if err = cm.ConfigurationStore.ParseFolder(); err != nil {
		return nil, err
118
119
120
	}

	// Ensure storage path exists, and populate it with necessary files
121
	cm.storage = storage{storagePath: storagePath, ConfigurationStore: cm.ConfigurationStore}
Sietse Ringers's avatar
Sietse Ringers committed
122
	if err = cm.storage.EnsureStorageExists(); err != nil {
123
124
125
		return nil, err
	}

126
	// Perform new update functions from clientUpdates, if any
127
128
129
130
131
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
Sietse Ringers's avatar
Sietse Ringers committed
132
	if cm.secretkey, err = cm.storage.LoadSecretKey(); err != nil {
133
134
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
135
	if cm.attributes, err = cm.storage.LoadAttributes(); err != nil {
136
137
		return nil, err
	}
138
	if cm.keyshareServers, err = cm.storage.LoadKeyshareServers(); err != nil {
139
140
		return nil, err
	}
141
	if cm.paillierKeyCache, err = cm.storage.LoadPaillierKeys(); err != nil {
142
143
		return nil, err
	}
144
145
146
	if cm.paillierKeyCache == nil {
		cm.paillierKey(false)
	}
147

148
149
	cm.UnenrolledSchemeManagers = cm.unenrolledSchemeManagers()
	if len(cm.UnenrolledSchemeManagers) > 1 {
150
151
152
153
154
155
		return nil, errors.New("Too many keyshare servers")
	}

	return cm, nil
}

156
// CredentialInfoList returns a list of information of all contained credentials.
157
158
func (client *Client) CredentialInfoList() irmago.CredentialInfoList {
	list := irmago.CredentialInfoList([]*irmago.CredentialInfo{})
159

160
	for _, attrlistlist := range client.attributes {
161
162
163
		for index, attrlist := range attrlistlist {
			info := attrlist.Info()
			info.Index = index
164
			list = append(list, info)
Sietse Ringers's avatar
Sietse Ringers committed
165
166
		}
	}
167

Sietse Ringers's avatar
Sietse Ringers committed
168
169
170
171
	sort.Sort(list)
	return list
}

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

177
	// Don't add duplicate creds
178
	for _, attrlistlist := range client.attributes {
179
		for _, attrs := range attrlistlist {
180
			if attrs.Hash() == cred.AttributeList().Hash() {
181
182
183
184
185
186
				return nil
			}
		}
	}

	// If this is a singleton credential type, ensure we have at most one by removing any previous instance
187
188
	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
189
190
191
	}

	// Append the new cred to our attributes and credentials
192
193
194
	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
195
	}
196
197
	counter := len(client.attributes[id]) - 1
	client.credentials[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
198

199
	if err = client.storage.StoreSignature(cred); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
200
201
202
		return
	}
	if storeAttributes {
203
		err = client.storage.StoreAttributes(client.attributes)
Sietse Ringers's avatar
Sietse Ringers committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
	}
	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

218
func (client *Client) remove(id irmago.CredentialTypeIdentifier, index int, storenow bool) error {
219
	// Remove attributes
220
	list, exists := client.attributes[id]
221
222
223
224
	if !exists || index >= len(list) {
		return errors.Errorf("Can't remove credential %s-%d: no such credential", id.String(), index)
	}
	attrs := list[index]
225
	client.attributes[id] = append(list[:index], list[index+1:]...)
226
	if storenow {
227
		if err := client.storage.StoreAttributes(client.attributes); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
228
229
			return err
		}
230
231
232
	}

	// Remove credential
233
	if creds, exists := client.credentials[id]; exists {
234
		if _, exists := creds[index]; exists {
Sietse Ringers's avatar
Sietse Ringers committed
235
			delete(creds, index)
236
			client.credentials[id] = creds
237
238
239
240
		}
	}

	// Remove signature from storage
241
	if err := client.storage.DeleteSignature(attrs); err != nil {
242
243
244
		return err
	}

245
	removed := map[irmago.CredentialTypeIdentifier][]irmago.TranslatedString{}
246
247
248
	removed[id] = attrs.Strings()

	if storenow {
249
		return client.addLogEntry(&LogEntry{
250
			Type:    actionRemoval,
251
			Time:    irmago.Timestamp(time.Now()),
252
253
254
255
			Removed: removed,
		})
	}
	return nil
256
257
}

258
func (client *Client) RemoveCredential(id irmago.CredentialTypeIdentifier, index int) error {
259
	return client.remove(id, index, true)
260
261
}

262
263
func (client *Client) RemoveCredentialByHash(hash string) error {
	cred, index, err := client.credentialByHash(hash)
264
265
266
	if err != nil {
		return err
	}
267
	return client.RemoveCredential(cred.CredentialType().Identifier(), index)
268
269
}

270
func (client *Client) RemoveAllCredentials() error {
271
	removed := map[irmago.CredentialTypeIdentifier][]irmago.TranslatedString{}
272
	for _, attrlistlist := range client.attributes {
Sietse Ringers's avatar
Sietse Ringers committed
273
274
275
276
		for _, attrs := range attrlistlist {
			if attrs.CredentialType() != nil {
				removed[attrs.CredentialType().Identifier()] = attrs.Strings()
			}
277
			client.storage.DeleteSignature(attrs)
278
279
		}
	}
280
	client.attributes = map[irmago.CredentialTypeIdentifier][]*irmago.AttributeList{}
281
	if err := client.storage.StoreAttributes(client.attributes); err != nil {
282
283
		return err
	}
284
285
286

	logentry := &LogEntry{
		Type:    actionRemoval,
287
		Time:    irmago.Timestamp(time.Now()),
288
289
		Removed: removed,
	}
290
	if err := client.addLogEntry(logentry); err != nil {
291
292
		return err
	}
293
	return client.storage.StoreLogs(client.logs)
294
295
}

296
// Attribute and credential getter methods
Sietse Ringers's avatar
Sietse Ringers committed
297

Sietse Ringers's avatar
Sietse Ringers committed
298
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
299
func (client *Client) attrs(id irmago.CredentialTypeIdentifier) []*irmago.AttributeList {
300
	list, exists := client.attributes[id]
Sietse Ringers's avatar
Sietse Ringers committed
301
	if !exists {
302
		list = make([]*irmago.AttributeList, 0, 1)
303
		client.attributes[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
304
305
306
307
308
	}
	return list
}

// creds returns cm.credentials[id], initializing it to an empty map if neccesary
309
func (client *Client) creds(id irmago.CredentialTypeIdentifier) map[int]*credential {
310
	list, exists := client.credentials[id]
Sietse Ringers's avatar
Sietse Ringers committed
311
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
312
		list = make(map[int]*credential)
313
		client.credentials[id] = list
Sietse Ringers's avatar
Sietse Ringers committed
314
315
316
317
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
318
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
319
func (client *Client) Attributes(id irmago.CredentialTypeIdentifier, counter int) (attributes *irmago.AttributeList) {
320
	list := client.attrs(id)
Sietse Ringers's avatar
Sietse Ringers committed
321
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
322
323
324
325
326
		return
	}
	return list[counter]
}

327
328
func (client *Client) credentialByHash(hash string) (*credential, int, error) {
	for _, attrlistlist := range client.attributes {
329
		for index, attrs := range attrlistlist {
330
			if attrs.Hash() == hash {
331
				cred, err := client.credential(attrs.CredentialType().Identifier(), index)
332
333
334
335
336
337
338
				return cred, index, err
			}
		}
	}
	return nil, 0, nil
}

339
func (client *Client) credentialByID(id irmago.CredentialIdentifier) (*credential, error) {
340
	if _, exists := client.attributes[id.Type]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
341
342
		return nil, nil
	}
343
	for index, attrs := range client.attributes[id.Type] {
344
		if attrs.Hash() == id.Hash {
345
			return client.credential(attrs.CredentialType().Identifier(), index)
Sietse Ringers's avatar
Sietse Ringers committed
346
347
348
		}
	}
	return nil, nil
349
350
}

Sietse Ringers's avatar
Sietse Ringers committed
351
// credential returns the requested credential, or nil if we do not have it.
352
func (client *Client) credential(id irmago.CredentialTypeIdentifier, counter int) (cred *credential, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
353
	// If the requested credential is not in credential map, we check if its attributes were
354
	// deserialized during New(). If so, there should be a corresponding signature file,
Sietse Ringers's avatar
Sietse Ringers committed
355
	// so we read that, construct the credential, and add it to the credential map
356
357
	if _, exists := client.creds(id)[counter]; !exists {
		attrs := client.Attributes(id, counter)
Sietse Ringers's avatar
Sietse Ringers committed
358
359
360
		if attrs == nil { // We do not have the requested cred
			return
		}
361
		sig, err := client.storage.LoadSignature(attrs)
Sietse Ringers's avatar
Sietse Ringers committed
362
363
364
365
366
367
368
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
369
		pk, err := attrs.PublicKey()
370
371
372
		if err != nil {
			return nil, err
		}
373
374
375
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
376
		cred, err := newCredential(&gabi.Credential{
377
			Attributes: append([]*big.Int{client.secretkey.Key}, attrs.Ints...),
378
			Signature:  sig,
379
			Pk:         pk,
380
		}, client.ConfigurationStore)
381
382
383
		if err != nil {
			return nil, err
		}
384
		client.credentials[id][counter] = cred
Sietse Ringers's avatar
Sietse Ringers committed
385
386
	}

387
	return client.credentials[id][counter], nil
388
389
}

Sietse Ringers's avatar
Sietse Ringers committed
390
// Methods used in the IRMA protocol
391

392
// Candidates returns a list of attributes present in this client
Sietse Ringers's avatar
Sietse Ringers committed
393
// that satisfy the specified attribute disjunction.
394
395
func (client *Client) Candidates(disjunction *irmago.AttributeDisjunction) []*irmago.AttributeIdentifier {
	candidates := make([]*irmago.AttributeIdentifier, 0, 10)
396
397

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
398
		credID := attribute.CredentialTypeIdentifier()
399
		if !client.ConfigurationStore.Contains(credID) {
400
401
			continue
		}
402
		creds := client.attributes[credID]
403
404
405
406
		count := len(creds)
		if count == 0 {
			continue
		}
407
		for _, attrs := range creds {
408
			id := &irmago.AttributeIdentifier{Type: attribute, Hash: attrs.Hash()}
409
410
411
			if attribute.IsCredential() {
				candidates = append(candidates, id)
			} else {
412
				val := attrs.UntranslatedAttribute(attribute)
413
				if val == "" { // This won't handle empty attributes correctly
414
415
416
417
418
419
420
421
422
423
424
425
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

426
// CheckSatisfiability checks if this client has the required attributes
Sietse Ringers's avatar
Sietse Ringers committed
427
428
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
429
func (client *Client) CheckSatisfiability(
430
431
432
433
	disjunctions irmago.AttributeDisjunctionList,
) ([][]*irmago.AttributeIdentifier, irmago.AttributeDisjunctionList) {
	candidates := [][]*irmago.AttributeIdentifier{}
	missing := irmago.AttributeDisjunctionList{}
434
	for i, disjunction := range disjunctions {
435
		candidates = append(candidates, []*irmago.AttributeIdentifier{})
436
		candidates[i] = client.Candidates(disjunction)
437
		if len(candidates[i]) == 0 {
438
439
440
			missing = append(missing, disjunction)
		}
	}
441
	return candidates, missing
442
}
443

444
445
func (client *Client) groupCredentials(choice *irmago.DisclosureChoice) (map[irmago.CredentialIdentifier][]int, error) {
	grouped := make(map[irmago.CredentialIdentifier][]int)
446
447
448
	if choice == nil || choice.Attributes == nil {
		return grouped, nil
	}
449
450
451
452
453
454
455
456

	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 {
457
458
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
459
460
461
462
463
464
			grouped[ici] = indices
		}

		if identifier.IsCredential() {
			continue // In this case we only disclose the metadata attribute, which is already handled
		}
465
		index, err := client.ConfigurationStore.CredentialTypes[identifier.CredentialTypeIdentifier()].IndexOf(identifier)
466
467
468
469
		if err != nil {
			return nil, err
		}

Sietse Ringers's avatar
Sietse Ringers committed
470
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
471
		// which doesn't know about the secret key and metadata attribute, so +2
472
		grouped[ici] = append(grouped[ici], index+2)
473
474
475
476
477
	}

	return grouped, nil
}

478
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
479
func (client *Client) ProofBuilders(choice *irmago.DisclosureChoice) (gabi.ProofBuilderList, error) {
480
	todisclose, err := client.groupCredentials(choice)
481
482
483
484
	if err != nil {
		return nil, err
	}

485
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
486
	for id, list := range todisclose {
487
		cred, err := client.credentialByID(id)
488
489
490
491
492
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
493
	return builders, nil
494
}
Sietse Ringers's avatar
Sietse Ringers committed
495

Sietse Ringers's avatar
Sietse Ringers committed
496
// Proofs computes disclosure proofs containing the attributes specified by choice.
497
func (client *Client) Proofs(choice *irmago.DisclosureChoice, request irmago.IrmaSession, issig bool) (gabi.ProofList, error) {
498
	builders, err := client.ProofBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
499
500
501
	if err != nil {
		return nil, err
	}
502
	return builders.BuildProofList(request.GetContext(), request.GetNonce(), issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
503
504
}

505
506
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
// for the future credentials as well as possibly any disclosed attributes.
507
func (client *Client) IssuanceProofBuilders(request *irmago.IssuanceRequest) (gabi.ProofBuilderList, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
508
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
509
510
511
	if err != nil {
		return nil, err
	}
512
	client.state = state
Sietse Ringers's avatar
Sietse Ringers committed
513

514
	proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
515
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
516
		var pk *gabi.PublicKey
517
		pk, err = client.ConfigurationStore.PublicKey(futurecred.CredentialTypeID.IssuerIdentifier(), futurecred.KeyCounter)
518
519
520
		if err != nil {
			return nil, err
		}
521
		credBuilder := gabi.NewCredentialBuilder(
522
			pk, request.GetContext(), client.secretkey.Key, state.nonce2)
523
		state.builders = append(state.builders, credBuilder)
Sietse Ringers's avatar
Sietse Ringers committed
524
525
		proofBuilders = append(proofBuilders, credBuilder)
	}
Sietse Ringers's avatar
Sietse Ringers committed
526

527
	disclosures, err := client.ProofBuilders(request.Choice)
Sietse Ringers's avatar
Sietse Ringers committed
528
529
530
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
531
	proofBuilders = append(disclosures, proofBuilders...)
Sietse Ringers's avatar
Sietse Ringers committed
532
533
	return proofBuilders, nil
}
Sietse Ringers's avatar
Sietse Ringers committed
534

Sietse Ringers's avatar
Sietse Ringers committed
535
536
// IssueCommitments computes issuance commitments, along with disclosure proofs
// specified by choice.
537
func (client *Client) IssueCommitments(request *irmago.IssuanceRequest) (*gabi.IssueCommitmentMessage, error) {
538
	proofBuilders, err := client.IssuanceProofBuilders(request)
Sietse Ringers's avatar
Sietse Ringers committed
539
540
541
	if err != nil {
		return nil, err
	}
542
	list := proofBuilders.BuildProofList(request.GetContext(), request.GetNonce(), false)
543
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: client.state.nonce2}, nil
Sietse Ringers's avatar
Sietse Ringers committed
544
545
}

Sietse Ringers's avatar
Sietse Ringers committed
546
547
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
548
549
func (client *Client) ConstructCredentials(msg []*gabi.IssueSignatureMessage, request *irmago.IssuanceRequest) error {
	if len(msg) != len(client.state.builders) {
Sietse Ringers's avatar
Sietse Ringers committed
550
551
552
		return errors.New("Received unexpected amount of signatures")
	}

553
554
	// 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
555
	gabicreds := []*gabi.Credential{}
Sietse Ringers's avatar
Sietse Ringers committed
556
	for i, sig := range msg {
557
		attrs, err := request.Credentials[i].AttributeList(client.ConfigurationStore)
Sietse Ringers's avatar
Sietse Ringers committed
558
559
560
		if err != nil {
			return err
		}
561
		cred, err := client.state.builders[i].ConstructCredential(sig, attrs.Ints)
Sietse Ringers's avatar
Sietse Ringers committed
562
563
564
		if err != nil {
			return err
		}
565
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
566
567
	}

568
	for _, gabicred := range gabicreds {
569
		newcred, err := newCredential(gabicred, client.ConfigurationStore)
570
571
572
		if err != nil {
			return err
		}
573
		if err = client.addCredential(newcred, true); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
574
575
			return err
		}
Sietse Ringers's avatar
Sietse Ringers committed
576
	}
577

Sietse Ringers's avatar
Sietse Ringers committed
578
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
579
}
580

Sietse Ringers's avatar
Sietse Ringers committed
581
582
// Keyshare server handling

583
// PaillierKey returns a new Paillier key (and generates a new one in a goroutine).
584
585
func (client *Client) paillierKey(wait bool) *paillierPrivateKey {
	cached := client.paillierKeyCache
Sietse Ringers's avatar
Sietse Ringers committed
586
	ch := make(chan bool)
587

588
589
	// 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
590
	// the if-clause below match.
591
	go client.paillierKeyWorker(cached == nil && wait, ch)
592
	if cached == nil && wait {
Sietse Ringers's avatar
Sietse Ringers committed
593
		<-ch
594
		// generate yet another one for future calls, but no need to wait now
595
		go client.paillierKeyWorker(false, ch)
Sietse Ringers's avatar
Sietse Ringers committed
596
	}
597
	return client.paillierKeyCache
598
}
Sietse Ringers's avatar
Sietse Ringers committed
599

600
func (client *Client) paillierKeyWorker(wait bool, ch chan bool) {
601
	newkey, _ := paillier.GenerateKey(rand.Reader, 2048)
602
603
	client.paillierKeyCache = (*paillierPrivateKey)(newkey)
	client.storage.StorePaillierKeys(client.paillierKeyCache)
604
605
606
607
608
	if wait {
		ch <- true
	}
}

609
610
func (client *Client) unenrolledSchemeManagers() []irmago.SchemeManagerIdentifier {
	list := []irmago.SchemeManagerIdentifier{}
611
612
	for name, manager := range client.ConfigurationStore.SchemeManagers {
		if _, contains := client.keyshareServers[name]; manager.Distributed() && !contains {
Sietse Ringers's avatar
Sietse Ringers committed
613
			list = append(list, manager.Identifier())
Sietse Ringers's avatar
Sietse Ringers committed
614
615
616
617
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
618

619
// KeyshareEnroll attempts to enroll at the keyshare server of the specified scheme manager.
620
func (client *Client) KeyshareEnroll(manager irmago.SchemeManagerIdentifier, email, pin string) {
Sietse Ringers's avatar
Sietse Ringers committed
621
	go func() {
622
		defer func() {
623
			handlePanic(func(err *irmago.SessionError) {
624
625
				if client.handler != nil {
					client.handler.EnrollmentError(manager, err)
626
627
628
629
				}
			})
		}()

630
631
		err := client.keyshareEnrollWorker(manager, email, pin)
		client.UnenrolledSchemeManagers = client.unenrolledSchemeManagers()
Sietse Ringers's avatar
Sietse Ringers committed
632
		if err != nil {
633
			client.handler.EnrollmentError(manager, err)
Sietse Ringers's avatar
Sietse Ringers committed
634
		} else {
635
			client.handler.EnrollmentSuccess(manager)
Sietse Ringers's avatar
Sietse Ringers committed
636
637
		}
	}()
638

639
640
}

641
func (client *Client) keyshareEnrollWorker(managerID irmago.SchemeManagerIdentifier, email, pin string) error {
642
	manager, ok := client.ConfigurationStore.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
643
644
645
646
647
648
649
650
651
652
	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")
	}

653
	transport := irmago.NewHTTPTransport(manager.KeyshareServer)
654
	kss, err := newKeyshareServer(client.paillierKey(true), manager.KeyshareServer, email)
Sietse Ringers's avatar
Sietse Ringers committed
655
656
657
	if err != nil {
		return err
	}
658
	message := keyshareEnrollment{
Sietse Ringers's avatar
Sietse Ringers committed
659
660
661
662
663
664
665
666
667
668
669
		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
	}

670
671
	client.keyshareServers[managerID] = kss
	return client.storage.StoreKeyshareServers(client.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
672
673
}

674
// KeyshareRemove unenrolls the keyshare server of the specified scheme manager.
675
func (client *Client) KeyshareRemove(manager irmago.SchemeManagerIdentifier) error {
676
	if _, contains := client.keyshareServers[manager]; !contains {
Sietse Ringers's avatar
Sietse Ringers committed
677
678
		return errors.New("Can't uninstall unknown keyshare server")
	}
679
680
	delete(client.keyshareServers, manager)
	return client.storage.StoreKeyshareServers(client.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
681
}
Sietse Ringers's avatar
Sietse Ringers committed
682

683
func (client *Client) KeyshareRemoveAll() error {
684
	client.keyshareServers = map[irmago.SchemeManagerIdentifier]*keyshareServer{}
685
686
	client.UnenrolledSchemeManagers = client.unenrolledSchemeManagers()
	return client.storage.StoreKeyshareServers(client.keyshareServers)
687
688
}

Sietse Ringers's avatar
Sietse Ringers committed
689
690
// Add, load and store log entries

691
692
693
func (client *Client) addLogEntry(entry *LogEntry) error {
	client.logs = append(client.logs, entry)
	return client.storage.StoreLogs(client.logs)
694
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
695
696
}

697
698
func (client *Client) Logs() ([]*LogEntry, error) {
	if client.logs == nil || len(client.logs) == 0 {
Sietse Ringers's avatar
Sietse Ringers committed
699
		var err error
700
		client.logs, err = client.storage.LoadLogs()
Sietse Ringers's avatar
Sietse Ringers committed
701
702
703
704
		if err != nil {
			return nil, err
		}
	}
705
	return client.logs, nil
Sietse Ringers's avatar
Sietse Ringers committed
706
}