manager.go 12.1 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
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
36
37
38
39
40
41
42
43
44
45
46
func (cm *CredentialManager) CredentialList() CredentialList {
	list := CredentialList([]*Credential{})
	for _, credlist := range cm.credentials {
		for _, cred := range credlist {
			list = append(list, cred.info)
		}
	}
	sort.Sort(list)
	return list
}

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
84
85
// 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
86
87
88
	// 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
89
	if _, exists := cm.creds(id)[counter]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
90
91
92
93
94
95
96
97
98
99
100
101
102
		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
		}
103
104
105
106
107
		meta := MetadataFromInt(ints[1])
		pk := meta.PublicKey()
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
108
109
110
		cred := newCredential(&gabi.Credential{
			Attributes: ints,
			Signature:  sig,
111
			Pk:         pk,
112
		})
Sietse Ringers's avatar
Sietse Ringers committed
113
114
115
116
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
117
118
}

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

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

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

	cm.addCredential(cred)
Sietse Ringers's avatar
Sietse Ringers committed
137
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
138
139

	err = cm.storeSignature(cred, counter)
140
141
142
	if err != nil {
		return
	}
143
	err = cm.storeAttributes()
144
145
	return
}
146

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

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

	return candidates
}

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

	return missing
}
195

196
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
197
198
199
200
201
202
203
204
205
	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 {
206
207
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
208
209
210
211
212
213
214
215
216
217
218
			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
219
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
220
		// which doesn't know about the secret key and metadata attribute, so +2
221
		grouped[ici] = append(grouped[ici], index+2)
222
223
224
225
226
	}

	return grouped, nil
}

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

240
func (cm *CredentialManager) ProofBuilders(choice *DisclosureChoice) (gabi.ProofBuilderList, error) {
241
242
243
244
245
	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}

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

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

266
func (cm *CredentialManager) IssuanceProofBuilders(request *IssuanceRequest) (gabi.ProofBuilderList, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
267
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
268
269
270
271
272
	if err != nil {
		return nil, err
	}
	request.state = state

273
	proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
Sietse Ringers's avatar
Sietse Ringers committed
274
275
276
277
278
279
	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
280

Sietse Ringers's avatar
Sietse Ringers committed
281
	disclosures, err := cm.ProofBuilders(request.choice)
Sietse Ringers's avatar
Sietse Ringers committed
282
283
284
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
285
	proofBuilders = append(disclosures, proofBuilders...)
Sietse Ringers's avatar
Sietse Ringers committed
286
287
	return proofBuilders, nil
}
Sietse Ringers's avatar
Sietse Ringers committed
288

Sietse Ringers's avatar
Sietse Ringers committed
289
290
291
292
293
294
295
// 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
	}
296
	list := proofBuilders.BuildProofList(request.GetContext(), request.GetNonce(), false)
Sietse Ringers's avatar
Sietse Ringers committed
297
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: request.state.nonce2}, nil
Sietse Ringers's avatar
Sietse Ringers committed
298
299
}

Sietse Ringers's avatar
Sietse Ringers committed
300
301
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
302
303
304
305
306
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")
	}

307
308
	// 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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
	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
323
		cm.add(newCredential(cred))
Sietse Ringers's avatar
Sietse Ringers committed
324
	}
325

Sietse Ringers's avatar
Sietse Ringers committed
326
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
327
}
328
329

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

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
357

Sietse Ringers's avatar
Sietse Ringers committed
358
359
360
361
362
363
364
365
366
367
368
369
370
func (cm *CredentialManager) KeyshareEnroll(managerId SchemeManagerIdentifier, email, pin string) error {
	manager, ok := MetaStore.SchemeManagers[managerId]
	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
371
	kss, err := newKeyshareServer(Manager.paillierKey(true), manager.KeyshareServer, email)
Sietse Ringers's avatar
Sietse Ringers committed
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
	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
	}

	cm.keyshareServers[managerId] = kss
	return cm.storeKeyshareServers()
}

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
397
398
	return cm.storeKeyshareServers()
}