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

import (
	"errors"
	"math/big"

7
8
	"crypto/rand"

9
	"github.com/Roasbeef/go-go-gadget-paillier"
10
11
12
	"github.com/mhe/gabi"
)

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

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

	paillierKeyCache *paillierPrivateKey
25
26
}

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

func (cm *CredentialManager) generateSecretKey() (sk *big.Int, err error) {
	return gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
36
37
}

Sietse Ringers's avatar
Sietse Ringers committed
38
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
39
func (cm *CredentialManager) attrs(id CredentialTypeIdentifier) []*AttributeList {
Sietse Ringers's avatar
Sietse Ringers committed
40
41
42
43
44
45
46
47
48
	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
49
func (cm *CredentialManager) creds(id CredentialTypeIdentifier) map[int]*credential {
Sietse Ringers's avatar
Sietse Ringers committed
50
51
	list, exists := cm.credentials[id]
	if !exists {
Sietse Ringers's avatar
Sietse Ringers committed
52
		list = make(map[int]*credential)
Sietse Ringers's avatar
Sietse Ringers committed
53
54
55
56
57
		cm.credentials[id] = list
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
58
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
59
func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int) (attributes *AttributeList) {
Sietse Ringers's avatar
Sietse Ringers committed
60
61
	list := cm.attrs(id)
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
62
63
64
65
66
		return
	}
	return list[counter]
}

Sietse Ringers's avatar
Sietse Ringers committed
67
68
func (cm *CredentialManager) credentialByID(id CredentialIdentifier) (cred *credential, err error) {
	return cm.credential(id.Type, id.Index)
69
70
}

Sietse Ringers's avatar
Sietse Ringers committed
71
72
// 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
73
74
75
	// 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
76
	if _, exists := cm.creds(id)[counter]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
77
78
79
80
81
82
83
84
85
86
87
88
89
		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
		}
90
91
92
93
94
		meta := MetadataFromInt(ints[1])
		pk := meta.PublicKey()
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
95
96
97
		cred := newCredential(&gabi.Credential{
			Attributes: ints,
			Signature:  sig,
98
			Pk:         pk,
99
		})
Sietse Ringers's avatar
Sietse Ringers committed
100
101
102
103
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
104
105
}

Sietse Ringers's avatar
Sietse Ringers committed
106
func (cm *CredentialManager) addCredential(cred *credential) {
107
	id := cred.CredentialType().Identifier()
Sietse Ringers's avatar
Sietse Ringers committed
108
	cm.attributes[id] = append(cm.attrs(id), NewAttributeListFromInts(cred.Attributes[1:]))
109

110
	if _, exists := cm.credentials[id]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
111
		cm.credentials[id] = make(map[int]*credential)
112
	}
Sietse Ringers's avatar
Sietse Ringers committed
113
114
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
115
}
116

Sietse Ringers's avatar
Sietse Ringers committed
117
118
// add adds the specified credential to the CredentialManager.
func (cm *CredentialManager) add(cred *credential) (err error) {
119
120
121
122
123
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

	cm.addCredential(cred)
Sietse Ringers's avatar
Sietse Ringers committed
124
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
125
126

	err = cm.storeSignature(cred, counter)
127
128
129
	if err != nil {
		return
	}
130
	err = cm.storeAttributes()
131
132
	return
}
133

Sietse Ringers's avatar
Sietse Ringers committed
134
135
// Candidates returns a list of attributes present in this credential manager
// that satisfy the specified attribute disjunction.
136
func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
137
	candidates := make([]*AttributeIdentifier, 0, 10)
138
139

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
140
141
		credID := attribute.CredentialTypeIdentifier()
		if !MetaStore.Contains(credID) {
142
143
			continue
		}
Sietse Ringers's avatar
Sietse Ringers committed
144
		creds := cm.credentials[credID]
145
146
147
148
149
150
151
152
153
154
155
		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)
156
				if val == "" { // This won't handle empty attributes correctly
157
158
159
160
161
162
163
164
165
166
167
168
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

Sietse Ringers's avatar
Sietse Ringers committed
169
170
171
// 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
172
func (cm *CredentialManager) CheckSatisfiability(disjunctions AttributeDisjunctionList) AttributeDisjunctionList {
173
	missing := make(AttributeDisjunctionList, 0, 5)
Sietse Ringers's avatar
Sietse Ringers committed
174
	for _, disjunction := range disjunctions {
175
176
177
178
179
180
181
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
182

183
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
184
185
186
187
188
189
190
191
192
	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 {
193
194
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
195
196
197
198
199
200
201
202
203
204
205
			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
206
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
207
		// which doesn't know about the secret key and metadata attribute, so +2
208
		grouped[ici] = append(grouped[ici], index+2)
209
210
211
212
213
	}

	return grouped, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
214
// Session is an IRMA session.
215
type Session interface {
216
	GetNonce() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
217
	SetNonce(*big.Int)
218
	GetContext() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
219
220
	SetContext(*big.Int)
	DisjunctionList() AttributeDisjunctionList
221
222
}

223
func (cm *CredentialManager) proofsBuilders(choice *DisclosureChoice) ([]gabi.ProofBuilder, error) {
224
225
226
227
228
229
230
	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
231
		cred, err := cm.credentialByID(id)
232
233
234
235
236
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
237
	return builders, nil
238
}
Sietse Ringers's avatar
Sietse Ringers committed
239

Sietse Ringers's avatar
Sietse Ringers committed
240
// Proofs computes disclosure proofs containing the attributes specified by choice.
241
242
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
243
244
245
	if err != nil {
		return nil, err
	}
246
	return gabi.BuildProofList(request.GetContext(), request.GetNonce(), builders, issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
247
248
}

Sietse Ringers's avatar
Sietse Ringers committed
249
250
// IssueCommitments computes issuance commitments, along with disclosure proofs
// specified by choice.
Sietse Ringers's avatar
Sietse Ringers committed
251
func (cm *CredentialManager) IssueCommitments(choice *DisclosureChoice, request *IssuanceRequest) (*gabi.IssueCommitmentMessage, error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
252
	state, err := newIssuanceState()
Sietse Ringers's avatar
Sietse Ringers committed
253
254
255
256
257
258
259
260
261
262
263
264
	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
265

266
	disclosures, err := cm.proofsBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
267
268
269
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
270
271
272
273
274
275
	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
276
277
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
278
279
280
281
282
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")
	}

283
284
	// 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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
	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
299
		cm.add(newCredential(cred))
Sietse Ringers's avatar
Sietse Ringers committed
300
	}
301

Sietse Ringers's avatar
Sietse Ringers committed
302
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
303
}
304
305
306
307
308
309
310
311
312
313
314

// 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
315
316
317
318
319
320
321
322
323
324

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
325

Sietse Ringers's avatar
Sietse Ringers committed
326
327
328
329
330
331
332
333
334
335
336
337
338
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
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
365
366
	return cm.storeKeyshareServers()
}