manager.go 19.2 KB
Newer Older
1
2
3
package irmago

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

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

// CredentialManager (de)serializes credentials and keyshare server information
// from storage; as well as logs of earlier IRMA sessions; it provides access
// to the attributes and all related information of its credentials;
// it is the starting point for new IRMA sessions; and it computes some
// of the messages in the client side of the IRMA protocol.
35
type CredentialManager struct {
36
	secretkey        *secretKey
37
38
39
	attributes       map[CredentialTypeIdentifier][]*AttributeList
	credentials      map[CredentialTypeIdentifier]map[int]*credential
	keyshareServers  map[SchemeManagerIdentifier]*keyshareServer
40
	paillierKeyCache *paillierPrivateKey
Sietse Ringers's avatar
Sietse Ringers committed
41
	logs             []*LogEntry
42

43
	storage               storage
44
	irmaConfigurationPath string
45
	androidStoragePath    string
46
	ConfigurationStore    *ConfigurationStore
47
	updates               []update
Sietse Ringers's avatar
Sietse Ringers committed
48
49
}

50
51
52
53
type secretKey struct {
	Key *big.Int
}

Sietse Ringers's avatar
Sietse Ringers committed
54
55
56
57
58
59
60
61
62
63
64
65
66
// IrmaSession is an IRMA session.
type IrmaSession interface {
	GetNonce() *big.Int
	SetNonce(*big.Int)
	GetContext() *big.Int
	SetContext(*big.Int)
	DisjunctionList() AttributeDisjunctionList
	DisclosureChoice() *DisclosureChoice
	SetDisclosureChoice(choice *DisclosureChoice)
	Distributed(store *ConfigurationStore) bool
	SchemeManagers() []SchemeManagerIdentifier
}

67
68
69
70
71
72
73
74
75
76
// NewCredentialManager creates a new CredentialManager that uses the directory
// 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),
// and keyshareHandler is used for when a registration to a keyshare server needs
// to happen.
// The credential manager returned by this function has been fully deserialized
// and is ready for use.
//
Sietse Ringers's avatar
Sietse Ringers committed
77
78
// NOTE: It is the responsibility of the caller that there exists a (properly
// protected) directory at storagePath!
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
func NewCredentialManager(
	storagePath string,
	irmaConfigurationPath string,
	androidStoragePath string,
	keyshareHandler KeyshareHandler,
) (*CredentialManager, error) {
	var err error
	if err = AssertPathExists(storagePath); err != nil {
		return nil, err
	}
	if err = AssertPathExists(irmaConfigurationPath); err != nil {
		return nil, err
	}

	var store *ConfigurationStore
	if store, err = NewConfigurationStore(storagePath+"/irma_configuration", irmaConfigurationPath); err != nil {
		return nil, err
	}
	if err = store.ParseFolder(); err != nil {
		return nil, err
	}

	cm := &CredentialManager{
		credentials:           make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers:       make(map[SchemeManagerIdentifier]*keyshareServer),
		attributes:            make(map[CredentialTypeIdentifier][]*AttributeList),
		irmaConfigurationPath: irmaConfigurationPath,
		androidStoragePath:    androidStoragePath,
		ConfigurationStore:    store,
		storage:               storage{storagePath: storagePath, ConfigurationStore: store},
	}

	// Ensure storage path exists, and populate it with necessary files
Sietse Ringers's avatar
Sietse Ringers committed
112
	if err = cm.storage.EnsureStorageExists(); err != nil {
113
114
115
116
117
118
119
120
121
		return nil, err
	}

	// Perform new update functions from credentialManagerUpdates, if any
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
Sietse Ringers's avatar
Sietse Ringers committed
122
	if cm.secretkey, err = cm.storage.LoadSecretKey(); err != nil {
123
124
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
125
	if cm.attributes, err = cm.storage.LoadAttributes(); err != nil {
126
127
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
128
	if cm.paillierKeyCache, err = cm.storage.LoadPaillierKeys(); err != nil {
129
130
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
131
	if cm.keyshareServers, err = cm.storage.LoadKeyshareServers(); err != nil {
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
		return nil, err
	}

	unenrolled := cm.unenrolledKeyshareServers()
	switch len(unenrolled) {
	case 0: // nop
	case 1:
		if keyshareHandler == nil {
			return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
		}
		keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) {
			cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
		})
	default:
		return nil, errors.New("Too many keyshare servers")
	}

	return cm, nil
}

152
153
154
// CredentialInfoList returns a list of information of all contained credentials.
func (cm *CredentialManager) CredentialInfoList() CredentialInfoList {
	list := CredentialInfoList([]*CredentialInfo{})
155
156

	for _, attrlistlist := range cm.attributes {
157
158
159
		for index, attrlist := range attrlistlist {
			info := attrlist.Info()
			info.Index = index
160
			list = append(list, attrlist.Info())
Sietse Ringers's avatar
Sietse Ringers committed
161
162
		}
	}
163

Sietse Ringers's avatar
Sietse Ringers committed
164
165
166
167
	sort.Sort(list)
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// addCredential adds the specified credential to the CredentialManager, saving its signature
// imediately, and optionally cm.attributes as well.
func (cm *CredentialManager) addCredential(cred *credential, storeAttributes bool) (err error) {
	id := cred.CredentialType().Identifier()
	cm.attributes[id] = append(cm.attrs(id), cred.AttributeList())

	if _, exists := cm.credentials[id]; !exists {
		cm.credentials[id] = make(map[int]*credential)
	}
	if cred.CredentialType().IsSingleton {
		cm.credentials[id][0] = cred
	} else {
		counter := len(cm.attributes[id]) - 1
		cm.credentials[id][counter] = cred
	}

	if err = cm.storage.StoreSignature(cred); err != nil {
		return
	}
	if storeAttributes {
		err = cm.storage.StoreAttributes(cm.attributes)
	}
	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

203
204
205
206
207
208
209
210
211
func (cm *CredentialManager) remove(id CredentialTypeIdentifier, index int, storenow bool) error {
	// Remove attributes
	list, exists := cm.attributes[id]
	if !exists || index >= len(list) {
		return errors.Errorf("Can't remove credential %s-%d: no such credential", id.String(), index)
	}
	attrs := list[index]
	cm.attributes[id] = append(list[:index], list[index+1:]...)
	if storenow {
Sietse Ringers's avatar
Sietse Ringers committed
212
		cm.storage.StoreAttributes(cm.attributes)
213
214
215
216
217
218
219
220
221
222
223
	}

	// Remove credential
	if creds, exists := cm.credentials[id]; exists {
		if _, exists := creds[index]; exists {
			creds[index] = nil
			cm.credentials[id] = creds
		}
	}

	// Remove signature from storage
Sietse Ringers's avatar
Sietse Ringers committed
224
	if err := cm.storage.DeleteSignature(attrs); err != nil {
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
		return err
	}

	return cm.addLogEntry(&LogEntry{
		Type:              actionRemoval,
		Time:              Timestamp(time.Now()),
		RemovedCredential: id,
	}, storenow)
}

func (cm *CredentialManager) RemoveCredential(id CredentialTypeIdentifier, index int) error {
	return cm.remove(id, index, true)
}

func (cm *CredentialManager) RemoveCredentialByHash(hash string) error {
	cred, index, err := cm.credentialByHash(hash)
	if err != nil {
		return err
	}
	return cm.RemoveCredential(cred.CredentialType().Identifier(), index)
}

func (cm *CredentialManager) RemoveAllCredentials() error {
	list := cm.CredentialInfoList()
	for _, cred := range list {
		if err := cm.remove(NewCredentialTypeIdentifier(cred.ID), cred.Index, false); err != nil {
			return err
		}
	}
Sietse Ringers's avatar
Sietse Ringers committed
254
	if err := cm.storage.StoreAttributes(cm.attributes); err != nil {
255
256
		return err
	}
Sietse Ringers's avatar
Sietse Ringers committed
257
	return cm.storage.StoreLogs(cm.logs)
258
259
}

Sietse Ringers's avatar
Sietse Ringers committed
260
261
// Getter methods

Sietse Ringers's avatar
Sietse Ringers committed
262
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
263
func (cm *CredentialManager) attrs(id CredentialTypeIdentifier) []*AttributeList {
Sietse Ringers's avatar
Sietse Ringers committed
264
265
266
267
268
269
270
271
272
	list, exists := cm.attributes[id]
	if !exists {
		list = make([]*AttributeList, 0, 1)
		cm.attributes[id] = list
	}
	return list
}

// creds returns cm.credentials[id], initializing it to an empty map if neccesary
Sietse Ringers's avatar
Sietse Ringers committed
273
func (cm *CredentialManager) creds(id CredentialTypeIdentifier) map[int]*credential {
Sietse Ringers's avatar
Sietse Ringers committed
274
275
	list, exists := cm.credentials[id]
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
276
		list = make(map[int]*credential)
Sietse Ringers's avatar
Sietse Ringers committed
277
278
279
280
281
		cm.credentials[id] = list
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
282
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
283
func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int) (attributes *AttributeList) {
Sietse Ringers's avatar
Sietse Ringers committed
284
285
	list := cm.attrs(id)
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
286
287
288
289
290
		return
	}
	return list[counter]
}

291
292
293
294
295
296
297
298
299
300
301
302
303
func (cm *CredentialManager) credentialByHash(hash string) (*credential, int, error) {
	for _, attrlistlist := range cm.attributes {
		for index, attrs := range attrlistlist {
			if attrs.hash() == hash {
				cred, err := cm.credential(attrs.CredentialType().Identifier(), index)
				return cred, index, err
			}
		}
	}
	return nil, 0, nil
}

func (cm *CredentialManager) credentialByID(id CredentialIdentifier) (*credential, error) {
Sietse Ringers's avatar
Sietse Ringers committed
304
	return cm.credential(id.Type, id.Index)
305
306
}

Sietse Ringers's avatar
Sietse Ringers committed
307
308
// credential returns the requested credential, or nil if we do not have it.
func (cm *CredentialManager) credential(id CredentialTypeIdentifier, counter int) (cred *credential, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
309
	// If the requested credential is not in credential map, we check if its attributes were
310
	// deserialized during NewCredentialManager(). If so, there should be a corresponding signature file,
Sietse Ringers's avatar
Sietse Ringers committed
311
	// so we read that, construct the credential, and add it to the credential map
Sietse Ringers's avatar
Sietse Ringers committed
312
	if _, exists := cm.creds(id)[counter]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
313
314
315
316
		attrs := cm.Attributes(id, counter)
		if attrs == nil { // We do not have the requested cred
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
317
		sig, err := cm.storage.LoadSignature(attrs)
Sietse Ringers's avatar
Sietse Ringers committed
318
319
320
321
322
323
324
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
325
		pk, err := attrs.PublicKey()
326
327
328
		if err != nil {
			return nil, err
		}
329
330
331
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
332
		cred, err := newCredential(&gabi.Credential{
333
			Attributes: append([]*big.Int{cm.secretkey.Key}, attrs.Ints...),
334
			Signature:  sig,
335
			Pk:         pk,
336
		}, cm.ConfigurationStore)
337
338
339
		if err != nil {
			return nil, err
		}
Sietse Ringers's avatar
Sietse Ringers committed
340
341
342
343
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
344
345
}

Sietse Ringers's avatar
Sietse Ringers committed
346
// Methods used in the IRMA protocol
347

Sietse Ringers's avatar
Sietse Ringers committed
348
349
// Candidates returns a list of attributes present in this credential manager
// that satisfy the specified attribute disjunction.
350
func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
351
	candidates := make([]*AttributeIdentifier, 0, 10)
352
353

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
354
		credID := attribute.CredentialTypeIdentifier()
355
		if !cm.ConfigurationStore.Contains(credID) {
356
357
			continue
		}
Sietse Ringers's avatar
Sietse Ringers committed
358
		creds := cm.credentials[credID]
359
360
361
362
363
364
365
366
367
		count := len(creds)
		if count == 0 {
			continue
		}
		for i, cred := range creds {
			id := &AttributeIdentifier{Type: attribute, Index: i, Count: count}
			if attribute.IsCredential() {
				candidates = append(candidates, id)
			} else {
368
				attrs := cred.AttributeList()
Sietse Ringers's avatar
Sietse Ringers committed
369
				val := attrs.untranslatedAttribute(attribute)
370
				if val == "" { // This won't handle empty attributes correctly
371
372
373
374
375
376
377
378
379
380
381
382
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

Sietse Ringers's avatar
Sietse Ringers committed
383
384
385
// CheckSatisfiability checks if this credential manager has the required attributes
// to satisfy the specifed disjunction list. If not, the unsatisfiable disjunctions
// are returned.
Sietse Ringers's avatar
Sietse Ringers committed
386
func (cm *CredentialManager) CheckSatisfiability(disjunctions AttributeDisjunctionList) AttributeDisjunctionList {
387
	missing := make(AttributeDisjunctionList, 0, 5)
Sietse Ringers's avatar
Sietse Ringers committed
388
	for _, disjunction := range disjunctions {
389
390
391
392
393
394
395
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
396

397
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
398
399
400
401
402
403
404
405
406
	grouped := make(map[CredentialIdentifier][]int)

	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 {
407
408
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
409
410
411
412
413
414
			grouped[ici] = indices
		}

		if identifier.IsCredential() {
			continue // In this case we only disclose the metadata attribute, which is already handled
		}
415
		index, err := cm.ConfigurationStore.Credentials[identifier.CredentialTypeIdentifier()].IndexOf(identifier)
416
417
418
419
		if err != nil {
			return nil, err
		}

Sietse Ringers's avatar
Sietse Ringers committed
420
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
421
		// which doesn't know about the secret key and metadata attribute, so +2
422
		grouped[ici] = append(grouped[ici], index+2)
423
424
425
426
427
	}

	return grouped, nil
}

428
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
429
func (cm *CredentialManager) ProofBuilders(choice *DisclosureChoice) (gabi.ProofBuilderList, error) {
430
431
432
433
434
	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}

435
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
436
	for id, list := range todisclose {
Sietse Ringers's avatar
Sietse Ringers committed
437
		cred, err := cm.credentialByID(id)
438
439
440
441
442
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
443
	return builders, nil
444
}
Sietse Ringers's avatar
Sietse Ringers committed
445

Sietse Ringers's avatar
Sietse Ringers committed
446
// Proofs computes disclosure proofs containing the attributes specified by choice.
447
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request IrmaSession, issig bool) (gabi.ProofList, error) {
Sietse Ringers's avatar
Sietse Ringers committed
448
	builders, err := cm.ProofBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
449
450
451
	if err != nil {
		return nil, err
	}
452
	return builders.BuildProofList(request.GetContext(), request.GetNonce(), issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
453
454
}

455
456
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
// for the future credentials as well as possibly any disclosed attributes.
457
func (cm *CredentialManager) IssuanceProofBuilders(request *IssuanceRequest) (gabi.ProofBuilderList, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
458
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
459
460
461
462
463
	if err != nil {
		return nil, err
	}
	request.state = state

464
	proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
465
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
466
467
		var pk *gabi.PublicKey
		pk, err = cm.ConfigurationStore.PublicKey(futurecred.Credential.IssuerIdentifier(), futurecred.KeyCounter)
468
469
470
		if err != nil {
			return nil, err
		}
471
472
		credBuilder := gabi.NewCredentialBuilder(
			pk, request.GetContext(), cm.secretkey.Key, state.nonce2)
Sietse Ringers's avatar
Sietse Ringers committed
473
474
475
		request.state.builders = append(request.state.builders, credBuilder)
		proofBuilders = append(proofBuilders, credBuilder)
	}
Sietse Ringers's avatar
Sietse Ringers committed
476

Sietse Ringers's avatar
Sietse Ringers committed
477
	disclosures, err := cm.ProofBuilders(request.choice)
Sietse Ringers's avatar
Sietse Ringers committed
478
479
480
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
481
	proofBuilders = append(disclosures, proofBuilders...)
Sietse Ringers's avatar
Sietse Ringers committed
482
483
	return proofBuilders, nil
}
Sietse Ringers's avatar
Sietse Ringers committed
484

Sietse Ringers's avatar
Sietse Ringers committed
485
486
487
488
489
490
491
// IssueCommitments computes issuance commitments, along with disclosure proofs
// specified by choice.
func (cm *CredentialManager) IssueCommitments(request *IssuanceRequest) (*gabi.IssueCommitmentMessage, error) {
	proofBuilders, err := cm.IssuanceProofBuilders(request)
	if err != nil {
		return nil, err
	}
492
	list := proofBuilders.BuildProofList(request.GetContext(), request.GetNonce(), false)
Sietse Ringers's avatar
Sietse Ringers committed
493
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: request.state.nonce2}, nil
Sietse Ringers's avatar
Sietse Ringers committed
494
495
}

Sietse Ringers's avatar
Sietse Ringers committed
496
497
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
498
499
500
501
502
func (cm *CredentialManager) ConstructCredentials(msg []*gabi.IssueSignatureMessage, request *IssuanceRequest) error {
	if len(msg) != len(request.state.builders) {
		return errors.New("Received unexpected amount of signatures")
	}

503
504
	// 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
505
	gabicreds := []*gabi.Credential{}
Sietse Ringers's avatar
Sietse Ringers committed
506
	for i, sig := range msg {
507
		attrs, err := request.Credentials[i].AttributeList(cm.ConfigurationStore)
Sietse Ringers's avatar
Sietse Ringers committed
508
509
510
511
512
513
514
		if err != nil {
			return err
		}
		cred, err := request.state.builders[i].ConstructCredential(sig, attrs.Ints)
		if err != nil {
			return err
		}
515
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
516
517
	}

518
	for _, gabicred := range gabicreds {
519
		newcred, err := newCredential(gabicred, cm.ConfigurationStore)
520
521
522
523
		if err != nil {
			return err
		}
		cm.addCredential(newcred, true)
Sietse Ringers's avatar
Sietse Ringers committed
524
	}
525

Sietse Ringers's avatar
Sietse Ringers committed
526
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
527
}
528

Sietse Ringers's avatar
Sietse Ringers committed
529
530
// Keyshare server handling

531
// PaillierKey returns a new Paillier key (and generates a new one in a goroutine).
Sietse Ringers's avatar
Sietse Ringers committed
532
func (cm *CredentialManager) paillierKey(wait bool) *paillierPrivateKey {
533
	retval := cm.paillierKeyCache
Sietse Ringers's avatar
Sietse Ringers committed
534
	ch := make(chan bool)
535
536
537
538
	go func() {
		newkey, _ := paillier.GenerateKey(rand.Reader, 2048)
		converted := paillierPrivateKey(*newkey)
		cm.paillierKeyCache = &converted
Sietse Ringers's avatar
Sietse Ringers committed
539
540
541
		if wait && retval == nil {
			ch <- true
		}
542
	}()
Sietse Ringers's avatar
Sietse Ringers committed
543
544
545
546
	if wait && retval == nil {
		<-ch
		return cm.paillierKeyCache
	}
547
548
	return retval
}
Sietse Ringers's avatar
Sietse Ringers committed
549
550
551

func (cm *CredentialManager) unenrolledKeyshareServers() []*SchemeManager {
	list := []*SchemeManager{}
552
	for name, manager := range cm.ConfigurationStore.SchemeManagers {
Sietse Ringers's avatar
Sietse Ringers committed
553
554
555
556
557
558
		if _, contains := cm.keyshareServers[name]; len(manager.KeyshareServer) > 0 && !contains {
			list = append(list, manager)
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
559

560
561
// KeyshareEnroll attempts to register at the keyshare server of the specified scheme manager.
func (cm *CredentialManager) KeyshareEnroll(managerID SchemeManagerIdentifier, email, pin string) error {
562
	manager, ok := cm.ConfigurationStore.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
563
564
565
566
567
568
569
570
571
572
573
	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")
	}

	transport := NewHTTPTransport(manager.KeyshareServer)
574
	kss, err := newKeyshareServer(cm.paillierKey(true), manager.KeyshareServer, email)
Sietse Ringers's avatar
Sietse Ringers committed
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
	if err != nil {
		return err
	}
	message := keyshareRegistration{
		Username:  email,
		Pin:       kss.HashedPin(pin),
		PublicKey: (*paillierPublicKey)(&kss.PrivateKey.PublicKey),
	}

	// TODO: examine error returned by Post() to see if it tells us that the email address is already in use
	result := &struct{}{}
	err = transport.Post("web/users/selfenroll", result, message)
	if err != nil {
		return err
	}

591
	cm.keyshareServers[managerID] = kss
Sietse Ringers's avatar
Sietse Ringers committed
592
	return cm.storage.StoreKeyshareServers(cm.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
593
594
}

595
// KeyshareRemove unregisters the keyshare server of the specified scheme manager.
Sietse Ringers's avatar
Sietse Ringers committed
596
597
598
599
600
func (cm *CredentialManager) KeyshareRemove(manager SchemeManagerIdentifier) error {
	if _, contains := cm.keyshareServers[manager]; !contains {
		return errors.New("Can't uninstall unknown keyshare server")
	}
	delete(cm.keyshareServers, manager)
Sietse Ringers's avatar
Sietse Ringers committed
601
	return cm.storage.StoreKeyshareServers(cm.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
602
}
Sietse Ringers's avatar
Sietse Ringers committed
603

Sietse Ringers's avatar
Sietse Ringers committed
604
605
// Add, load and store log entries

606
func (cm *CredentialManager) addLogEntry(entry *LogEntry, storenow bool) error {
Sietse Ringers's avatar
Sietse Ringers committed
607
	cm.logs = append(cm.logs, entry)
608
	if storenow {
Sietse Ringers's avatar
Sietse Ringers committed
609
		return cm.storage.StoreLogs(cm.logs)
610
611
	}
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
612
613
614
615
616
}

func (cm *CredentialManager) Logs() ([]*LogEntry, error) {
	if cm.logs == nil || len(cm.logs) == 0 {
		var err error
Sietse Ringers's avatar
Sietse Ringers committed
617
		cm.logs, err = cm.storage.LoadLogs()
Sietse Ringers's avatar
Sietse Ringers committed
618
619
620
621
622
623
		if err != nil {
			return nil, err
		}
	}
	return cm.logs, nil
}