manager.go 15.1 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
21
	storagePath      string
	attributes       map[CredentialTypeIdentifier][]*AttributeList
	credentials      map[CredentialTypeIdentifier]map[int]*credential
	keyshareServers  map[SchemeManagerIdentifier]*keyshareServer
22
	paillierKeyCache *paillierPrivateKey
Sietse Ringers's avatar
Sietse Ringers committed
23
	logs             []*LogEntry
24

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
// CredentialInfoList returns a list of information of all contained credentials.
func (cm *CredentialManager) CredentialInfoList() CredentialInfoList {
	list := CredentialInfoList([]*CredentialInfo{})
38
39

	for _, attrlistlist := range cm.attributes {
40
41
42
		for index, attrlist := range attrlistlist {
			info := attrlist.Info()
			info.Index = index
43
			list = append(list, attrlist.Info())
Sietse Ringers's avatar
Sietse Ringers committed
44
45
		}
	}
46

Sietse Ringers's avatar
Sietse Ringers committed
47
48
49
50
	sort.Sort(list)
	return list
}

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
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 {
		cm.storeAttributes()
	}

	// 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
	if err := os.Remove(cm.signatureFilename(attrs)); err != nil {
		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
		}
	}
	if err := cm.storeAttributes(); err != nil {
		return err
	}
	return cm.storeLogs()
}

108
109
110
111
112
113
func (cm *CredentialManager) generateSecretKey() (*secretKey, error) {
	key, err := gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
	if err != nil {
		return nil, err
	}
	return &secretKey{Key: key}, nil
114
115
}

Sietse Ringers's avatar
Sietse Ringers committed
116
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
117
func (cm *CredentialManager) attrs(id CredentialTypeIdentifier) []*AttributeList {
Sietse Ringers's avatar
Sietse Ringers committed
118
119
120
121
122
123
124
125
126
	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
127
func (cm *CredentialManager) creds(id CredentialTypeIdentifier) map[int]*credential {
Sietse Ringers's avatar
Sietse Ringers committed
128
129
	list, exists := cm.credentials[id]
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
130
		list = make(map[int]*credential)
Sietse Ringers's avatar
Sietse Ringers committed
131
132
133
134
135
		cm.credentials[id] = list
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
136
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
137
func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int) (attributes *AttributeList) {
Sietse Ringers's avatar
Sietse Ringers committed
138
139
	list := cm.attrs(id)
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
140
141
142
143
144
		return
	}
	return list[counter]
}

145
146
147
148
149
150
151
152
153
154
155
156
157
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
158
	return cm.credential(id.Type, id.Index)
159
160
}

Sietse Ringers's avatar
Sietse Ringers committed
161
162
// 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
163
	// If the requested credential is not in credential map, we check if its attributes were
164
	// deserialized during NewCredentialManager(). If so, there should be a corresponding signature file,
Sietse Ringers's avatar
Sietse Ringers committed
165
	// so we read that, construct the credential, and add it to the credential map
Sietse Ringers's avatar
Sietse Ringers committed
166
	if _, exists := cm.creds(id)[counter]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
167
168
169
170
		attrs := cm.Attributes(id, counter)
		if attrs == nil { // We do not have the requested cred
			return
		}
171
		sig, err := cm.loadSignature(attrs)
Sietse Ringers's avatar
Sietse Ringers committed
172
173
174
175
176
177
178
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
179
		pk, err := attrs.PublicKey()
180
181
182
		if err != nil {
			return nil, err
		}
183
184
185
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
186
		cred, err := newCredential(&gabi.Credential{
187
			Attributes: append([]*big.Int{cm.secretkey.Key}, attrs.Ints...),
188
			Signature:  sig,
189
			Pk:         pk,
190
		}, cm.ConfigurationStore)
191
192
193
		if err != nil {
			return nil, err
		}
Sietse Ringers's avatar
Sietse Ringers committed
194
195
196
197
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
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) {
203
	id := cred.CredentialType().Identifier()
204
	cm.attributes[id] = append(cm.attrs(id), cred.AttributeList())
205

206
	if _, exists := cm.credentials[id]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
207
		cm.credentials[id] = make(map[int]*credential)
208
	}
Sietse Ringers's avatar
Sietse Ringers committed
209
210
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
211

212
	if err = cm.storeSignature(cred, counter); err != nil {
213
214
		return
	}
215
216
217
	if storeAttributes {
		err = cm.storeAttributes()
	}
218
219
	return
}
220

Sietse Ringers's avatar
Sietse Ringers committed
221
222
// Candidates returns a list of attributes present in this credential manager
// that satisfy the specified attribute disjunction.
223
func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
224
	candidates := make([]*AttributeIdentifier, 0, 10)
225
226

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
227
		credID := attribute.CredentialTypeIdentifier()
228
		if !cm.ConfigurationStore.Contains(credID) {
229
230
			continue
		}
Sietse Ringers's avatar
Sietse Ringers committed
231
		creds := cm.credentials[credID]
232
233
234
235
236
237
238
239
240
		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 {
241
				attrs := cred.AttributeList()
Sietse Ringers's avatar
Sietse Ringers committed
242
				val := attrs.untranslatedAttribute(attribute)
243
				if val == "" { // This won't handle empty attributes correctly
244
245
246
247
248
249
250
251
252
253
254
255
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

Sietse Ringers's avatar
Sietse Ringers committed
256
257
258
// 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
259
func (cm *CredentialManager) CheckSatisfiability(disjunctions AttributeDisjunctionList) AttributeDisjunctionList {
260
	missing := make(AttributeDisjunctionList, 0, 5)
Sietse Ringers's avatar
Sietse Ringers committed
261
	for _, disjunction := range disjunctions {
262
263
264
265
266
267
268
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
269

270
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
271
272
273
274
275
276
277
278
279
	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 {
280
281
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
282
283
284
285
286
287
			grouped[ici] = indices
		}

		if identifier.IsCredential() {
			continue // In this case we only disclose the metadata attribute, which is already handled
		}
288
		index, err := cm.ConfigurationStore.Credentials[identifier.CredentialTypeIdentifier()].IndexOf(identifier)
289
290
291
292
		if err != nil {
			return nil, err
		}

Sietse Ringers's avatar
Sietse Ringers committed
293
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
294
		// which doesn't know about the secret key and metadata attribute, so +2
295
		grouped[ici] = append(grouped[ici], index+2)
296
297
298
299
300
	}

	return grouped, nil
}

301
302
// IrmaSession is an IRMA session.
type IrmaSession interface {
303
	GetNonce() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
304
	SetNonce(*big.Int)
305
	GetContext() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
306
307
	SetContext(*big.Int)
	DisjunctionList() AttributeDisjunctionList
Sietse Ringers's avatar
Sietse Ringers committed
308
309
	DisclosureChoice() *DisclosureChoice
	SetDisclosureChoice(choice *DisclosureChoice)
310
	Distributed(store *ConfigurationStore) bool
Sietse Ringers's avatar
Sietse Ringers committed
311
	SchemeManagers() []SchemeManagerIdentifier
312
313
}

314
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
315
func (cm *CredentialManager) ProofBuilders(choice *DisclosureChoice) (gabi.ProofBuilderList, error) {
316
317
318
319
320
	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}

321
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
322
	for id, list := range todisclose {
Sietse Ringers's avatar
Sietse Ringers committed
323
		cred, err := cm.credentialByID(id)
324
325
326
327
328
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
329
	return builders, nil
330
}
Sietse Ringers's avatar
Sietse Ringers committed
331

Sietse Ringers's avatar
Sietse Ringers committed
332
// Proofs computes disclosure proofs containing the attributes specified by choice.
333
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request IrmaSession, issig bool) (gabi.ProofList, error) {
Sietse Ringers's avatar
Sietse Ringers committed
334
	builders, err := cm.ProofBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
335
336
337
	if err != nil {
		return nil, err
	}
338
	return builders.BuildProofList(request.GetContext(), request.GetNonce(), issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
339
340
}

341
342
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
// for the future credentials as well as possibly any disclosed attributes.
343
func (cm *CredentialManager) IssuanceProofBuilders(request *IssuanceRequest) (gabi.ProofBuilderList, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
344
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
345
346
347
348
349
	if err != nil {
		return nil, err
	}
	request.state = state

350
	proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
351
	for _, futurecred := range request.Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
352
353
		var pk *gabi.PublicKey
		pk, err = cm.ConfigurationStore.PublicKey(futurecred.Credential.IssuerIdentifier(), futurecred.KeyCounter)
354
355
356
		if err != nil {
			return nil, err
		}
357
358
		credBuilder := gabi.NewCredentialBuilder(
			pk, request.GetContext(), cm.secretkey.Key, state.nonce2)
Sietse Ringers's avatar
Sietse Ringers committed
359
360
361
		request.state.builders = append(request.state.builders, credBuilder)
		proofBuilders = append(proofBuilders, credBuilder)
	}
Sietse Ringers's avatar
Sietse Ringers committed
362

Sietse Ringers's avatar
Sietse Ringers committed
363
	disclosures, err := cm.ProofBuilders(request.choice)
Sietse Ringers's avatar
Sietse Ringers committed
364
365
366
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
367
	proofBuilders = append(disclosures, proofBuilders...)
Sietse Ringers's avatar
Sietse Ringers committed
368
369
	return proofBuilders, nil
}
Sietse Ringers's avatar
Sietse Ringers committed
370

Sietse Ringers's avatar
Sietse Ringers committed
371
372
373
374
375
376
377
// 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
	}
378
	list := proofBuilders.BuildProofList(request.GetContext(), request.GetNonce(), false)
Sietse Ringers's avatar
Sietse Ringers committed
379
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: request.state.nonce2}, nil
Sietse Ringers's avatar
Sietse Ringers committed
380
381
}

Sietse Ringers's avatar
Sietse Ringers committed
382
383
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
384
385
386
387
388
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")
	}

389
390
	// 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
391
	gabicreds := []*gabi.Credential{}
Sietse Ringers's avatar
Sietse Ringers committed
392
	for i, sig := range msg {
393
		attrs, err := request.Credentials[i].AttributeList(cm.ConfigurationStore)
Sietse Ringers's avatar
Sietse Ringers committed
394
395
396
397
398
399
400
		if err != nil {
			return err
		}
		cred, err := request.state.builders[i].ConstructCredential(sig, attrs.Ints)
		if err != nil {
			return err
		}
401
		gabicreds = append(gabicreds, cred)
Sietse Ringers's avatar
Sietse Ringers committed
402
403
	}

404
	for _, gabicred := range gabicreds {
405
		newcred, err := newCredential(gabicred, cm.ConfigurationStore)
406
407
408
409
		if err != nil {
			return err
		}
		cm.addCredential(newcred, true)
Sietse Ringers's avatar
Sietse Ringers committed
410
	}
411

Sietse Ringers's avatar
Sietse Ringers committed
412
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
413
}
414
415

// PaillierKey returns a new Paillier key (and generates a new one in a goroutine).
Sietse Ringers's avatar
Sietse Ringers committed
416
func (cm *CredentialManager) paillierKey(wait bool) *paillierPrivateKey {
417
	retval := cm.paillierKeyCache
Sietse Ringers's avatar
Sietse Ringers committed
418
	ch := make(chan bool)
419
420
421
422
	go func() {
		newkey, _ := paillier.GenerateKey(rand.Reader, 2048)
		converted := paillierPrivateKey(*newkey)
		cm.paillierKeyCache = &converted
Sietse Ringers's avatar
Sietse Ringers committed
423
424
425
		if wait && retval == nil {
			ch <- true
		}
426
	}()
Sietse Ringers's avatar
Sietse Ringers committed
427
428
429
430
	if wait && retval == nil {
		<-ch
		return cm.paillierKeyCache
	}
431
432
	return retval
}
Sietse Ringers's avatar
Sietse Ringers committed
433
434
435

func (cm *CredentialManager) unenrolledKeyshareServers() []*SchemeManager {
	list := []*SchemeManager{}
436
	for name, manager := range cm.ConfigurationStore.SchemeManagers {
Sietse Ringers's avatar
Sietse Ringers committed
437
438
439
440
441
442
		if _, contains := cm.keyshareServers[name]; len(manager.KeyshareServer) > 0 && !contains {
			list = append(list, manager)
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
443

444
445
// KeyshareEnroll attempts to register at the keyshare server of the specified scheme manager.
func (cm *CredentialManager) KeyshareEnroll(managerID SchemeManagerIdentifier, email, pin string) error {
446
	manager, ok := cm.ConfigurationStore.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
447
448
449
450
451
452
453
454
455
456
457
	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)
458
	kss, err := newKeyshareServer(cm.paillierKey(true), manager.KeyshareServer, email)
Sietse Ringers's avatar
Sietse Ringers committed
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
	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
	}

475
	cm.keyshareServers[managerID] = kss
Sietse Ringers's avatar
Sietse Ringers committed
476
477
478
	return cm.storeKeyshareServers()
}

479
// KeyshareRemove unregisters the keyshare server of the specified scheme manager.
Sietse Ringers's avatar
Sietse Ringers committed
480
481
482
483
484
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
485
486
	return cm.storeKeyshareServers()
}
Sietse Ringers's avatar
Sietse Ringers committed
487

488
func (cm *CredentialManager) addLogEntry(entry *LogEntry, storenow bool) error {
Sietse Ringers's avatar
Sietse Ringers committed
489
	cm.logs = append(cm.logs, entry)
490
491
492
493
	if storenow {
		return cm.storeLogs()
	}
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
494
495
496
497
498
499
500
501
502
503
504
505
}

func (cm *CredentialManager) Logs() ([]*LogEntry, error) {
	if cm.logs == nil || len(cm.logs) == 0 {
		var err error
		cm.logs, err = cm.loadLogs()
		if err != nil {
			return nil, err
		}
	}
	return cm.logs, nil
}