manager.go 12.6 KB
Newer Older
1
2
3
4
5
6
package irmago

import (
	"errors"
	"math/big"

7
8
	"crypto/rand"

Sietse Ringers's avatar
Sietse Ringers committed
9
10
	"sort"

11
	"github.com/credentials/go-go-gadget-paillier"
12
13
14
	"github.com/mhe/gabi"
)

15
// Manager is the global instance of CredentialManager.
Sietse Ringers's avatar
Sietse Ringers committed
16
var Manager = newCredentialManager()
17
18
19

// CredentialManager manages credentials.
type CredentialManager struct {
20
21
22
23
24
25
26
	secretkey       *big.Int
	storagePath     string
	attributes      map[CredentialTypeIdentifier][]*AttributeList
	credentials     map[CredentialTypeIdentifier]map[int]*credential
	keyshareServers map[SchemeManagerIdentifier]*keyshareServer

	paillierKeyCache *paillierPrivateKey
27
28
}

Sietse Ringers's avatar
Sietse Ringers committed
29
30
func newCredentialManager() *CredentialManager {
	return &CredentialManager{
31
32
		credentials:     make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers: make(map[SchemeManagerIdentifier]*keyshareServer),
Sietse Ringers's avatar
Sietse Ringers committed
33
34
35
	}
}

36
// CredentialList returns a list of information of all contained credentials.
Sietse Ringers's avatar
Sietse Ringers committed
37
38
func (cm *CredentialManager) CredentialList() CredentialList {
	list := CredentialList([]*Credential{})
39
40
41
42

	for _, attrlistlist := range cm.attributes {
		for _, attrlist := range attrlistlist {
			list = append(list, attrlist.info)
Sietse Ringers's avatar
Sietse Ringers committed
43
44
		}
	}
45

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

Sietse Ringers's avatar
Sietse Ringers committed
50
51
func (cm *CredentialManager) generateSecretKey() (sk *big.Int, err error) {
	return gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
52
53
}

Sietse Ringers's avatar
Sietse Ringers committed
54
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
55
func (cm *CredentialManager) attrs(id CredentialTypeIdentifier) []*AttributeList {
Sietse Ringers's avatar
Sietse Ringers committed
56
57
58
59
60
61
62
63
64
	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
65
func (cm *CredentialManager) creds(id CredentialTypeIdentifier) map[int]*credential {
Sietse Ringers's avatar
Sietse Ringers committed
66
67
	list, exists := cm.credentials[id]
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
68
		list = make(map[int]*credential)
Sietse Ringers's avatar
Sietse Ringers committed
69
70
71
72
73
		cm.credentials[id] = list
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
74
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
75
func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int) (attributes *AttributeList) {
Sietse Ringers's avatar
Sietse Ringers committed
76
77
	list := cm.attrs(id)
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
78
79
80
81
82
		return
	}
	return list[counter]
}

Sietse Ringers's avatar
Sietse Ringers committed
83
84
func (cm *CredentialManager) credentialByID(id CredentialIdentifier) (cred *credential, err error) {
	return cm.credential(id.Type, id.Index)
85
86
}

Sietse Ringers's avatar
Sietse Ringers committed
87
88
// 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
89
90
91
	// If the requested credential is not in credential map, we check if its attributes were
	// deserialized during Init(). If so, there should be a corresponding signature file,
	// so we read that, construct the credential, and add it to the credential map
Sietse Ringers's avatar
Sietse Ringers committed
92
	if _, exists := cm.creds(id)[counter]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
93
94
95
96
97
98
99
100
101
102
103
104
105
		attrs := cm.Attributes(id, counter)
		if attrs == nil { // We do not have the requested cred
			return
		}
		ints := append([]*big.Int{cm.secretkey}, attrs.Ints...)
		sig, err := cm.loadSignature(id, counter)
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
106
107
108
109
110
		meta := MetadataFromInt(ints[1])
		pk := meta.PublicKey()
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
111
112
113
		cred := newCredential(&gabi.Credential{
			Attributes: ints,
			Signature:  sig,
114
			Pk:         pk,
115
		})
Sietse Ringers's avatar
Sietse Ringers committed
116
117
118
119
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
120
121
}

Sietse Ringers's avatar
Sietse Ringers committed
122
func (cm *CredentialManager) addCredential(cred *credential) {
123
	id := cred.CredentialType().Identifier()
Sietse Ringers's avatar
Sietse Ringers committed
124
	cm.attributes[id] = append(cm.attrs(id), NewAttributeListFromInts(cred.Attributes[1:]))
125

126
	if _, exists := cm.credentials[id]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
127
		cm.credentials[id] = make(map[int]*credential)
128
	}
Sietse Ringers's avatar
Sietse Ringers committed
129
130
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
131
}
132

Sietse Ringers's avatar
Sietse Ringers committed
133
134
// add adds the specified credential to the CredentialManager.
func (cm *CredentialManager) add(cred *credential) (err error) {
135
136
137
138
139
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

	cm.addCredential(cred)
Sietse Ringers's avatar
Sietse Ringers committed
140
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
141
142

	err = cm.storeSignature(cred, counter)
143
144
145
	if err != nil {
		return
	}
146
	err = cm.storeAttributes()
147
148
	return
}
149

Sietse Ringers's avatar
Sietse Ringers committed
150
151
// Candidates returns a list of attributes present in this credential manager
// that satisfy the specified attribute disjunction.
152
func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
153
	candidates := make([]*AttributeIdentifier, 0, 10)
154
155

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
156
157
		credID := attribute.CredentialTypeIdentifier()
		if !MetaStore.Contains(credID) {
158
159
			continue
		}
Sietse Ringers's avatar
Sietse Ringers committed
160
		creds := cm.credentials[credID]
161
162
163
164
165
166
167
168
169
170
171
		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 {
				attrs := NewAttributeListFromInts(cred.Attributes[1:])
				val := attrs.Attribute(attribute)
172
				if val == "" { // This won't handle empty attributes correctly
173
174
175
176
177
178
179
180
181
182
183
184
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

Sietse Ringers's avatar
Sietse Ringers committed
185
186
187
// 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
188
func (cm *CredentialManager) CheckSatisfiability(disjunctions AttributeDisjunctionList) AttributeDisjunctionList {
189
	missing := make(AttributeDisjunctionList, 0, 5)
Sietse Ringers's avatar
Sietse Ringers committed
190
	for _, disjunction := range disjunctions {
191
192
193
194
195
196
197
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
198

199
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
200
201
202
203
204
205
206
207
208
	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 {
209
210
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
211
212
213
214
215
216
217
218
219
220
221
			grouped[ici] = indices
		}

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

Sietse Ringers's avatar
Sietse Ringers committed
222
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
223
		// which doesn't know about the secret key and metadata attribute, so +2
224
		grouped[ici] = append(grouped[ici], index+2)
225
226
227
228
229
	}

	return grouped, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
230
// Session is an IRMA session.
231
type Session interface {
232
	GetNonce() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
233
	SetNonce(*big.Int)
234
	GetContext() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
235
236
	SetContext(*big.Int)
	DisjunctionList() AttributeDisjunctionList
Sietse Ringers's avatar
Sietse Ringers committed
237
238
239
240
	DisclosureChoice() *DisclosureChoice
	SetDisclosureChoice(choice *DisclosureChoice)
	Distributed() bool
	SchemeManagers() []SchemeManagerIdentifier
241
242
}

243
// ProofBuilders constructs a list of proof builders for the specified attribute choice.
244
func (cm *CredentialManager) ProofBuilders(choice *DisclosureChoice) (gabi.ProofBuilderList, error) {
245
246
247
248
249
	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}

250
	builders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
251
	for id, list := range todisclose {
Sietse Ringers's avatar
Sietse Ringers committed
252
		cred, err := cm.credentialByID(id)
253
254
255
256
257
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
258
	return builders, nil
259
}
Sietse Ringers's avatar
Sietse Ringers committed
260

Sietse Ringers's avatar
Sietse Ringers committed
261
// Proofs computes disclosure proofs containing the attributes specified by choice.
262
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request Session, issig bool) (gabi.ProofList, error) {
Sietse Ringers's avatar
Sietse Ringers committed
263
	builders, err := cm.ProofBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
264
265
266
	if err != nil {
		return nil, err
	}
267
	return builders.BuildProofList(request.GetContext(), request.GetNonce(), issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
268
269
}

270
271
// IssuanceProofBuilders constructs a list of proof builders in the issuance protocol
// for the future credentials as well as possibly any disclosed attributes.
272
func (cm *CredentialManager) IssuanceProofBuilders(request *IssuanceRequest) (gabi.ProofBuilderList, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
273
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
274
275
276
277
278
	if err != nil {
		return nil, err
	}
	request.state = state

279
	proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
280
281
282
283
284
285
	for _, futurecred := range request.Credentials {
		pk := MetaStore.PublicKey(futurecred.Credential.IssuerIdentifier(), futurecred.KeyCounter)
		credBuilder := gabi.NewCredentialBuilder(pk, request.GetContext(), cm.secretkey, state.nonce2)
		request.state.builders = append(request.state.builders, credBuilder)
		proofBuilders = append(proofBuilders, credBuilder)
	}
Sietse Ringers's avatar
Sietse Ringers committed
286

Sietse Ringers's avatar
Sietse Ringers committed
287
	disclosures, err := cm.ProofBuilders(request.choice)
Sietse Ringers's avatar
Sietse Ringers committed
288
289
290
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
291
	proofBuilders = append(disclosures, proofBuilders...)
Sietse Ringers's avatar
Sietse Ringers committed
292
293
	return proofBuilders, nil
}
Sietse Ringers's avatar
Sietse Ringers committed
294

Sietse Ringers's avatar
Sietse Ringers committed
295
296
297
298
299
300
301
// 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
	}
302
	list := proofBuilders.BuildProofList(request.GetContext(), request.GetNonce(), false)
Sietse Ringers's avatar
Sietse Ringers committed
303
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: request.state.nonce2}, nil
Sietse Ringers's avatar
Sietse Ringers committed
304
305
}

Sietse Ringers's avatar
Sietse Ringers committed
306
307
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
308
309
310
311
312
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")
	}

313
314
	// 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
Sietse Ringers's avatar
Sietse Ringers committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
	creds := []*gabi.Credential{}
	for i, sig := range msg {
		attrs, err := request.Credentials[i].AttributeList()
		if err != nil {
			return err
		}
		cred, err := request.state.builders[i].ConstructCredential(sig, attrs.Ints)
		if err != nil {
			return err
		}
		creds = append(creds, cred)
	}

	for _, cred := range creds {
Sietse Ringers's avatar
Sietse Ringers committed
329
		cm.add(newCredential(cred))
Sietse Ringers's avatar
Sietse Ringers committed
330
	}
331

Sietse Ringers's avatar
Sietse Ringers committed
332
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
333
}
334
335

// PaillierKey returns a new Paillier key (and generates a new one in a goroutine).
Sietse Ringers's avatar
Sietse Ringers committed
336
func (cm *CredentialManager) paillierKey(wait bool) *paillierPrivateKey {
337
	retval := cm.paillierKeyCache
Sietse Ringers's avatar
Sietse Ringers committed
338
	ch := make(chan bool)
339
340
341
342
	go func() {
		newkey, _ := paillier.GenerateKey(rand.Reader, 2048)
		converted := paillierPrivateKey(*newkey)
		cm.paillierKeyCache = &converted
Sietse Ringers's avatar
Sietse Ringers committed
343
344
345
		if wait && retval == nil {
			ch <- true
		}
346
	}()
Sietse Ringers's avatar
Sietse Ringers committed
347
348
349
350
	if wait && retval == nil {
		<-ch
		return cm.paillierKeyCache
	}
351
352
	return retval
}
Sietse Ringers's avatar
Sietse Ringers committed
353
354
355
356
357
358
359
360
361
362

func (cm *CredentialManager) unenrolledKeyshareServers() []*SchemeManager {
	list := []*SchemeManager{}
	for name, manager := range MetaStore.SchemeManagers {
		if _, contains := cm.keyshareServers[name]; len(manager.KeyshareServer) > 0 && !contains {
			list = append(list, manager)
		}
	}
	return list
}
Sietse Ringers's avatar
Sietse Ringers committed
363

364
365
366
// KeyshareEnroll attempts to register at the keyshare server of the specified scheme manager.
func (cm *CredentialManager) KeyshareEnroll(managerID SchemeManagerIdentifier, email, pin string) error {
	manager, ok := MetaStore.SchemeManagers[managerID]
Sietse Ringers's avatar
Sietse Ringers committed
367
368
369
370
371
372
373
374
375
376
377
	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)
Sietse Ringers's avatar
Sietse Ringers committed
378
	kss, err := newKeyshareServer(Manager.paillierKey(true), manager.KeyshareServer, email)
Sietse Ringers's avatar
Sietse Ringers committed
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
	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
	}

395
	cm.keyshareServers[managerID] = kss
Sietse Ringers's avatar
Sietse Ringers committed
396
397
398
	return cm.storeKeyshareServers()
}

399
// KeyshareRemove unregisters the keyshare server of the specified scheme manager.
Sietse Ringers's avatar
Sietse Ringers committed
400
401
402
403
404
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
405
406
	return cm.storeKeyshareServers()
}