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

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

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

// CredentialManager manages credentials.
type CredentialManager struct {
17
	secretkey        *secretKey
18
19
20
	attributes       map[CredentialTypeIdentifier][]*AttributeList
	credentials      map[CredentialTypeIdentifier]map[int]*credential
	keyshareServers  map[SchemeManagerIdentifier]*keyshareServer
21
	paillierKeyCache *paillierPrivateKey
Sietse Ringers's avatar
Sietse Ringers committed
22
	logs             []*LogEntry
23

24
	storage               storage
25
	irmaConfigurationPath string
26
	androidStoragePath    string
27
	ConfigurationStore    *ConfigurationStore
28
	updates               []update
Sietse Ringers's avatar
Sietse Ringers committed
29
30
}

31
32
33
34
type secretKey struct {
	Key *big.Int
}

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
112
113
114
115
116
117
118
119
// 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.
//
// NOTE: It is the responsibility of the caller that there exists a directory
// at storagePath!
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
	if err = cm.storage.ensureStorageExists(); err != nil {
		return nil, err
	}

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

	// Load our stuff
	if cm.secretkey, err = cm.storage.loadSecretKey(); err != nil {
		return nil, err
	}
	if cm.attributes, err = cm.storage.loadAttributes(); err != nil {
		return nil, err
	}
	if cm.paillierKeyCache, err = cm.storage.loadPaillierKeys(); err != nil {
		return nil, err
	}
	if cm.keyshareServers, err = cm.storage.loadKeyshareServers(); err != nil {
		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
}

120
121
122
// CredentialInfoList returns a list of information of all contained credentials.
func (cm *CredentialManager) CredentialInfoList() CredentialInfoList {
	list := CredentialInfoList([]*CredentialInfo{})
123
124

	for _, attrlistlist := range cm.attributes {
125
126
127
		for index, attrlist := range attrlistlist {
			info := attrlist.Info()
			info.Index = index
128
			list = append(list, attrlist.Info())
Sietse Ringers's avatar
Sietse Ringers committed
129
130
		}
	}
131

Sietse Ringers's avatar
Sietse Ringers committed
132
133
134
135
	sort.Sort(list)
	return list
}

136
137
138
139
140
141
142
143
144
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 {
145
		cm.storage.storeAttributes(cm.attributes)
146
147
148
149
150
151
152
153
154
155
156
	}

	// 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
157
	if err := os.Remove(cm.storage.signatureFilename(attrs)); err != nil {
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
		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
		}
	}
187
	if err := cm.storage.storeAttributes(cm.attributes); err != nil {
188
189
		return err
	}
190
	return cm.storage.storeLogs(cm.logs)
191
192
}

Sietse Ringers's avatar
Sietse Ringers committed
193
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
194
func (cm *CredentialManager) attrs(id CredentialTypeIdentifier) []*AttributeList {
Sietse Ringers's avatar
Sietse Ringers committed
195
196
197
198
199
200
201
202
203
	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
204
func (cm *CredentialManager) creds(id CredentialTypeIdentifier) map[int]*credential {
Sietse Ringers's avatar
Sietse Ringers committed
205
206
	list, exists := cm.credentials[id]
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
207
		list = make(map[int]*credential)
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
211
212
		cm.credentials[id] = list
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
213
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
214
func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int) (attributes *AttributeList) {
Sietse Ringers's avatar
Sietse Ringers committed
215
216
	list := cm.attrs(id)
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
217
218
219
220
221
		return
	}
	return list[counter]
}

222
223
224
225
226
227
228
229
230
231
232
233
234
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
235
	return cm.credential(id.Type, id.Index)
236
237
}

Sietse Ringers's avatar
Sietse Ringers committed
238
239
// 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
240
	// If the requested credential is not in credential map, we check if its attributes were
241
	// deserialized during NewCredentialManager(). If so, there should be a corresponding signature file,
Sietse Ringers's avatar
Sietse Ringers committed
242
	// so we read that, construct the credential, and add it to the credential map
Sietse Ringers's avatar
Sietse Ringers committed
243
	if _, exists := cm.creds(id)[counter]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
244
245
246
247
		attrs := cm.Attributes(id, counter)
		if attrs == nil { // We do not have the requested cred
			return
		}
248
		sig, err := cm.storage.loadSignature(attrs)
Sietse Ringers's avatar
Sietse Ringers committed
249
250
251
252
253
254
255
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
256
		pk, err := attrs.PublicKey()
257
258
259
		if err != nil {
			return nil, err
		}
260
261
262
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
263
		cred, err := newCredential(&gabi.Credential{
264
			Attributes: append([]*big.Int{cm.secretkey.Key}, attrs.Ints...),
265
			Signature:  sig,
266
			Pk:         pk,
267
		}, cm.ConfigurationStore)
268
269
270
		if err != nil {
			return nil, err
		}
Sietse Ringers's avatar
Sietse Ringers committed
271
272
273
274
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
275
276
}

277
278
279
// 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) {
280
	id := cred.CredentialType().Identifier()
281
	cm.attributes[id] = append(cm.attrs(id), cred.AttributeList())
282

283
	if _, exists := cm.credentials[id]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
284
		cm.credentials[id] = make(map[int]*credential)
285
	}
Sietse Ringers's avatar
Sietse Ringers committed
286
287
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
288

289
	if err = cm.storage.storeSignature(cred); err != nil {
290
291
		return
	}
292
	if storeAttributes {
293
		err = cm.storage.storeAttributes(cm.attributes)
294
	}
295
296
	return
}
297

Sietse Ringers's avatar
Sietse Ringers committed
298
299
// Candidates returns a list of attributes present in this credential manager
// that satisfy the specified attribute disjunction.
300
func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
301
	candidates := make([]*AttributeIdentifier, 0, 10)
302
303

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
304
		credID := attribute.CredentialTypeIdentifier()
305
		if !cm.ConfigurationStore.Contains(credID) {
306
307
			continue
		}
Sietse Ringers's avatar
Sietse Ringers committed
308
		creds := cm.credentials[credID]
309
310
311
312
313
314
315
316
317
		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 {
318
				attrs := cred.AttributeList()
Sietse Ringers's avatar
Sietse Ringers committed
319
				val := attrs.untranslatedAttribute(attribute)
320
				if val == "" { // This won't handle empty attributes correctly
321
322
323
324
325
326
327
328
329
330
331
332
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

Sietse Ringers's avatar
Sietse Ringers committed
333
334
335
// 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
336
func (cm *CredentialManager) CheckSatisfiability(disjunctions AttributeDisjunctionList) AttributeDisjunctionList {
337
	missing := make(AttributeDisjunctionList, 0, 5)
Sietse Ringers's avatar
Sietse Ringers committed
338
	for _, disjunction := range disjunctions {
339
340
341
342
343
344
345
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
346

347
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
348
349
350
351
352
353
354
355
356
	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 {
357
358
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
359
360
361
362
363
364
			grouped[ici] = indices
		}

		if identifier.IsCredential() {
			continue // In this case we only disclose the metadata attribute, which is already handled
		}
365
		index, err := cm.ConfigurationStore.Credentials[identifier.CredentialTypeIdentifier()].IndexOf(identifier)
366
367
368
369
		if err != nil {
			return nil, err
		}

Sietse Ringers's avatar
Sietse Ringers committed
370
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
371
		// which doesn't know about the secret key and metadata attribute, so +2
372
		grouped[ici] = append(grouped[ici], index+2)
373
374
375
376
377
	}

	return grouped, nil
}

378
379
// IrmaSession is an IRMA session.
type IrmaSession interface {
380
	GetNonce() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
381
	SetNonce(*big.Int)
382
	GetContext() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
383
384
	SetContext(*big.Int)
	DisjunctionList() AttributeDisjunctionList
Sietse Ringers's avatar
Sietse Ringers committed
385
386
	DisclosureChoice() *DisclosureChoice
	SetDisclosureChoice(choice *DisclosureChoice)
387
	Distributed(store *ConfigurationStore) bool
Sietse Ringers's avatar
Sietse Ringers committed
388
	SchemeManagers() []SchemeManagerIdentifier
389
390
}

391
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
392
func (cm *CredentialManager) ProofBuilders(choice *DisclosureChoice) (gabi.ProofBuilderList, error) {
393
394
395
396
397
	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}

398
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
399
	for id, list := range todisclose {
Sietse Ringers's avatar
Sietse Ringers committed
400
		cred, err := cm.credentialByID(id)
401
402
403
404
405
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
406
	return builders, nil
407
}
Sietse Ringers's avatar
Sietse Ringers committed
408

Sietse Ringers's avatar
Sietse Ringers committed
409
// Proofs computes disclosure proofs containing the attributes specified by choice.
410
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request IrmaSession, issig bool) (gabi.ProofList, error) {
Sietse Ringers's avatar
Sietse Ringers committed
411
	builders, err := cm.ProofBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
412
413
414
	if err != nil {
		return nil, err
	}
415
	return builders.BuildProofList(request.GetContext(), request.GetNonce(), issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
416
417
}

418
419
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
// for the future credentials as well as possibly any disclosed attributes.
420
func (cm *CredentialManager) IssuanceProofBuilders(request *IssuanceRequest) (gabi.ProofBuilderList, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
421
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
422
423
424
425
426
	if err != nil {
		return nil, err
	}
	request.state = state

427
	proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
428
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
429
430
		var pk *gabi.PublicKey
		pk, err = cm.ConfigurationStore.PublicKey(futurecred.Credential.IssuerIdentifier(), futurecred.KeyCounter)
431
432
433
		if err != nil {
			return nil, err
		}
434
435
		credBuilder := gabi.NewCredentialBuilder(
			pk, request.GetContext(), cm.secretkey.Key, state.nonce2)
Sietse Ringers's avatar
Sietse Ringers committed
436
437
438
		request.state.builders = append(request.state.builders, credBuilder)
		proofBuilders = append(proofBuilders, credBuilder)
	}
Sietse Ringers's avatar
Sietse Ringers committed
439

Sietse Ringers's avatar
Sietse Ringers committed
440
	disclosures, err := cm.ProofBuilders(request.choice)
Sietse Ringers's avatar
Sietse Ringers committed
441
442
443
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
444
	proofBuilders = append(disclosures, proofBuilders...)
Sietse Ringers's avatar
Sietse Ringers committed
445
446
	return proofBuilders, nil
}
Sietse Ringers's avatar
Sietse Ringers committed
447

Sietse Ringers's avatar
Sietse Ringers committed
448
449
450
451
452
453
454
// 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
	}
455
	list := proofBuilders.BuildProofList(request.GetContext(), request.GetNonce(), false)
Sietse Ringers's avatar
Sietse Ringers committed
456
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: request.state.nonce2}, nil
Sietse Ringers's avatar
Sietse Ringers committed
457
458
}

Sietse Ringers's avatar
Sietse Ringers committed
459
460
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
461
462
463
464
465
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")
	}

466
467
	// 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
468
	gabicreds := []*gabi.Credential{}
Sietse Ringers's avatar
Sietse Ringers committed
469
	for i, sig := range msg {
470
		attrs, err := request.Credentials[i].AttributeList(cm.ConfigurationStore)
Sietse Ringers's avatar
Sietse Ringers committed
471
472
473
474
475
476
477
		if err != nil {
			return err
		}
		cred, err := request.state.builders[i].ConstructCredential(sig, attrs.Ints)
		if err != nil {
			return err
		}
478
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
479
480
	}

481
	for _, gabicred := range gabicreds {
482
		newcred, err := newCredential(gabicred, cm.ConfigurationStore)
483
484
485
486
		if err != nil {
			return err
		}
		cm.addCredential(newcred, true)
Sietse Ringers's avatar
Sietse Ringers committed
487
	}
488

Sietse Ringers's avatar
Sietse Ringers committed
489
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
490
}
491
492

// PaillierKey returns a new Paillier key (and generates a new one in a goroutine).
Sietse Ringers's avatar
Sietse Ringers committed
493
func (cm *CredentialManager) paillierKey(wait bool) *paillierPrivateKey {
494
	retval := cm.paillierKeyCache
Sietse Ringers's avatar
Sietse Ringers committed
495
	ch := make(chan bool)
496
497
498
499
	go func() {
		newkey, _ := paillier.GenerateKey(rand.Reader, 2048)
		converted := paillierPrivateKey(*newkey)
		cm.paillierKeyCache = &converted
Sietse Ringers's avatar
Sietse Ringers committed
500
501
502
		if wait && retval == nil {
			ch <- true
		}
503
	}()
Sietse Ringers's avatar
Sietse Ringers committed
504
505
506
507
	if wait && retval == nil {
		<-ch
		return cm.paillierKeyCache
	}
508
509
	return retval
}
Sietse Ringers's avatar
Sietse Ringers committed
510
511
512

func (cm *CredentialManager) unenrolledKeyshareServers() []*SchemeManager {
	list := []*SchemeManager{}
513
	for name, manager := range cm.ConfigurationStore.SchemeManagers {
Sietse Ringers's avatar
Sietse Ringers committed
514
515
516
517
518
519
		if _, contains := cm.keyshareServers[name]; len(manager.KeyshareServer) > 0 && !contains {
			list = append(list, manager)
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
520

521
522
// KeyshareEnroll attempts to register at the keyshare server of the specified scheme manager.
func (cm *CredentialManager) KeyshareEnroll(managerID SchemeManagerIdentifier, email, pin string) error {
523
	manager, ok := cm.ConfigurationStore.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
524
525
526
527
528
529
530
531
532
533
534
	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)
535
	kss, err := newKeyshareServer(cm.paillierKey(true), manager.KeyshareServer, email)
Sietse Ringers's avatar
Sietse Ringers committed
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
	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
	}

552
	cm.keyshareServers[managerID] = kss
553
	return cm.storage.storeKeyshareServers(cm.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
554
555
}

556
// KeyshareRemove unregisters the keyshare server of the specified scheme manager.
Sietse Ringers's avatar
Sietse Ringers committed
557
558
559
560
561
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)
562
	return cm.storage.storeKeyshareServers(cm.keyshareServers)
Sietse Ringers's avatar
Sietse Ringers committed
563
}
Sietse Ringers's avatar
Sietse Ringers committed
564

565
func (cm *CredentialManager) addLogEntry(entry *LogEntry, storenow bool) error {
Sietse Ringers's avatar
Sietse Ringers committed
566
	cm.logs = append(cm.logs, entry)
567
	if storenow {
568
		return cm.storage.storeLogs(cm.logs)
569
570
	}
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
571
572
573
574
575
}

func (cm *CredentialManager) Logs() ([]*LogEntry, error) {
	if cm.logs == nil || len(cm.logs) == 0 {
		var err error
576
		cm.logs, err = cm.storage.loadLogs()
Sietse Ringers's avatar
Sietse Ringers committed
577
578
579
580
581
582
		if err != nil {
			return nil, err
		}
	}
	return cm.logs, nil
}