manager.go 10.9 KB
Newer Older
1
2
3
package irmago

import (
4
5
	"encoding/json"
	"encoding/xml"
6
	"errors"
7
	"html"
8
9
10
11
12
13
	"io/ioutil"
	"math/big"

	"github.com/mhe/gabi"
)

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

// CredentialManager manages credentials.
type CredentialManager struct {
	secretkey   *big.Int
	storagePath string
21
	attributes  map[CredentialTypeIdentifier][]*AttributeList
Sietse Ringers's avatar
Sietse Ringers committed
22
	credentials map[CredentialTypeIdentifier]map[int]*credential
23
24
}

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

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

35
36
37
// Init deserializes the credentials from storage.
func (cm *CredentialManager) Init(path string) (err error) {
	cm.storagePath = path
38

Sietse Ringers's avatar
Sietse Ringers committed
39
40
41
42
43
	err = cm.ensureStorageExists()
	if err != nil {
		return err
	}
	cm.secretkey, err = cm.loadSecretKey()
44
45
46
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
47
	cm.attributes, err = cm.loadAttributes()
48
	return
Sietse Ringers's avatar
Sietse Ringers committed
49
50
51

}

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

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

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

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

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

// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
Sietse Ringers's avatar
Sietse Ringers committed
121
122
123
// from the old Android IRMA app, parsing its credentials into the current instance,
// and saving them to storage.
// CAREFUL: this method overwrites any existing secret keys and attributes on storage.
124
func (cm *CredentialManager) ParseAndroidStorage() (err error) {
125
	exists, err := PathExists(cm.path(cardemuXML))
126
	if err != nil || !exists {
127
128
		return
	}
129
130

	bytes, err := ioutil.ReadFile(cm.path(cardemuXML))
Sietse Ringers's avatar
Sietse Ringers committed
131
	parsedxml := struct {
132
133
134
135
136
		Strings []struct {
			Name    string `xml:"name,attr"`
			Content string `xml:",chardata"`
		} `xml:"string"`
	}{}
Sietse Ringers's avatar
Sietse Ringers committed
137
	xml.Unmarshal(bytes, &parsedxml)
138

Sietse Ringers's avatar
Sietse Ringers committed
139
140
	parsedjson := make(map[string][]*gabi.Credential)
	for _, xmltag := range parsedxml.Strings {
141
142
		if xmltag.Name == "credentials" {
			jsontag := html.UnescapeString(xmltag.Content)
Sietse Ringers's avatar
Sietse Ringers committed
143
			if err = json.Unmarshal([]byte(jsontag), &parsedjson); err != nil {
144
145
				return
			}
146
147
148
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
149
150
	for _, list := range parsedjson {
		cm.secretkey = list[0].Attributes[0]
151
152
		for i, gabicred := range list {
			cred := newCredential(gabicred)
153
154
155
156
157
158
159
160
161
162
			if cred.CredentialType() == nil {
				return errors.New("cannot add unknown credential type")
			}

			cm.addCredential(cred)
			err = cm.storeSignature(cred, i)
			if err != nil {
				return err
			}
		}
163
	}
164
165
166

	if len(cm.credentials) > 0 {
		err = cm.storeAttributes()
167
		if err != nil {
168
			return err
169
		}
Sietse Ringers's avatar
Sietse Ringers committed
170
		err = cm.storeSecretKey(cm.secretkey)
171
		if err != nil {
172
			return err
173
174
175
		}
	}

176
	return
177
178
}

Sietse Ringers's avatar
Sietse Ringers committed
179
func (cm *CredentialManager) addCredential(cred *credential) {
180
	id := cred.CredentialType().Identifier()
Sietse Ringers's avatar
Sietse Ringers committed
181
	cm.attributes[id] = append(cm.attrs(id), NewAttributeListFromInts(cred.Attributes[1:]))
182

183
	if _, exists := cm.credentials[id]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
184
		cm.credentials[id] = make(map[int]*credential)
185
	}
Sietse Ringers's avatar
Sietse Ringers committed
186
187
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
188
}
189

Sietse Ringers's avatar
Sietse Ringers committed
190
191
// add adds the specified credential to the CredentialManager.
func (cm *CredentialManager) add(cred *credential) (err error) {
192
193
194
195
196
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

	cm.addCredential(cred)
Sietse Ringers's avatar
Sietse Ringers committed
197
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
198
199

	err = cm.storeSignature(cred, counter)
200
201
202
	if err != nil {
		return
	}
203
	err = cm.storeAttributes()
204
205
	return
}
206

Sietse Ringers's avatar
Sietse Ringers committed
207
208
// Candidates returns a list of attributes present in this credential manager
// that satisfy the specified attribute disjunction.
209
func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
210
	candidates := make([]*AttributeIdentifier, 0, 10)
211
212

	for _, attribute := range disjunction.Attributes {
Sietse Ringers's avatar
Sietse Ringers committed
213
214
		credID := attribute.CredentialTypeIdentifier()
		if !MetaStore.Contains(credID) {
215
216
			continue
		}
Sietse Ringers's avatar
Sietse Ringers committed
217
		creds := cm.credentials[credID]
218
219
220
221
222
223
224
225
226
227
228
		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)
229
				if val == "" { // This won't handle empty attributes correctly
230
231
232
233
234
235
236
237
238
239
240
241
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

Sietse Ringers's avatar
Sietse Ringers committed
242
243
244
// 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
245
func (cm *CredentialManager) CheckSatisfiability(disjunctions AttributeDisjunctionList) AttributeDisjunctionList {
246
	missing := make(AttributeDisjunctionList, 0, 5)
Sietse Ringers's avatar
Sietse Ringers committed
247
	for _, disjunction := range disjunctions {
248
249
250
251
252
253
254
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
255

256
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
257
258
259
260
261
262
263
264
265
	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 {
266
267
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
268
269
270
271
272
273
274
275
276
277
278
			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
279
		// These indices will be used in the []*big.Int at gabi.credential.Attributes,
280
		// which doesn't know about the secret key and metadata attribute, so +2
281
		grouped[ici] = append(grouped[ici], index+2)
282
283
284
285
286
	}

	return grouped, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
287
// Session is an IRMA session.
288
type Session interface {
289
	GetNonce() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
290
	SetNonce(*big.Int)
291
	GetContext() *big.Int
Sietse Ringers's avatar
Sietse Ringers committed
292
293
	SetContext(*big.Int)
	DisjunctionList() AttributeDisjunctionList
294
295
}

296
func (cm *CredentialManager) proofsBuilders(choice *DisclosureChoice) ([]gabi.ProofBuilder, error) {
297
298
299
300
301
302
303
	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
304
		cred, err := cm.credentialByID(id)
305
306
307
308
309
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}
Sietse Ringers's avatar
Sietse Ringers committed
310
	return builders, nil
311
}
Sietse Ringers's avatar
Sietse Ringers committed
312

Sietse Ringers's avatar
Sietse Ringers committed
313
// Proofs computes disclosure proofs containing the attributes specified by choice.
314
315
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
316
317
318
	if err != nil {
		return nil, err
	}
319
	return gabi.BuildProofList(request.GetContext(), request.GetNonce(), builders, issig), nil
Sietse Ringers's avatar
Sietse Ringers committed
320
321
}

Sietse Ringers's avatar
Sietse Ringers committed
322
323
// IssueCommitments computes issuance commitments, along with disclosure proofs
// specified by choice.
Sietse Ringers's avatar
Sietse Ringers committed
324
325
326
327
328
329
330
331
332
333
334
335
336
337
func (cm *CredentialManager) IssueCommitments(choice *DisclosureChoice, request *IssuanceRequest) (*gabi.IssueCommitmentMessage, error) {
	state, err := newIssuanceState(request)
	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
338

339
	disclosures, err := cm.proofsBuilders(choice)
Sietse Ringers's avatar
Sietse Ringers committed
340
341
342
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
343
344
345
346
347
348
	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
349
350
// ConstructCredentials constructs and saves new credentials
// using the specified issuance signature messages.
Sietse Ringers's avatar
Sietse Ringers committed
351
352
353
354
355
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")
	}

356
357
	// 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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
	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
372
		cm.add(newCredential(cred))
Sietse Ringers's avatar
Sietse Ringers committed
373
	}
374

Sietse Ringers's avatar
Sietse Ringers committed
375
	return nil
Sietse Ringers's avatar
Sietse Ringers committed
376
}