manager.go 11.5 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
234
235
}

236
func (cm *CredentialManager) proofsBuilders(choice *DisclosureChoice) ([]gabi.ProofBuilder, error) {
237
238
239
240
241
242
243
	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}

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

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

Sietse Ringers's avatar
Sietse Ringers committed
262
263
// IssueCommitments computes issuance commitments, along with disclosure proofs
// specified by choice.
Sietse Ringers's avatar
Sietse Ringers committed
264
func (cm *CredentialManager) IssueCommitments(choice *DisclosureChoice, request *IssuanceRequest) (*gabi.IssueCommitmentMessage, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
265
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
266
267
268
269
270
271
272
273
274
275
276
277
	if err != nil {
		return nil, err
	}
	request.state = state

	proofBuilders := []gabi.ProofBuilder{}
	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
278

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

	list := gabi.BuildProofList(request.GetContext(), request.GetNonce(), proofBuilders, false)
	return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: state.nonce2}, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
289
290
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
291
292
293
294
295
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")
	}

296
297
	// 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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
	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
312
		cm.add(newCredential(cred))
Sietse Ringers's avatar
Sietse Ringers committed
313
	}
314

Sietse Ringers's avatar
Sietse Ringers committed
315
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
316
}
317
318
319
320
321
322
323
324
325
326
327

// PaillierKey returns a new Paillier key (and generates a new one in a goroutine).
func (cm *CredentialManager) paillierKey() *paillierPrivateKey {
	retval := cm.paillierKeyCache
	go func() {
		newkey, _ := paillier.GenerateKey(rand.Reader, 2048)
		converted := paillierPrivateKey(*newkey)
		cm.paillierKeyCache = &converted
	}()
	return retval
}
Sietse Ringers's avatar
Sietse Ringers committed
328
329
330
331
332
333
334
335
336
337

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
338

Sietse Ringers's avatar
Sietse Ringers committed
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
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)
	kss, err := newKeyshareServer(Manager.paillierKey(), manager.URL, email)
	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
378
379
	return cm.storeKeyshareServers()
}