manager.go 8.68 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
22
	attributes  map[CredentialTypeIdentifier][]*AttributeList
	credentials map[CredentialTypeIdentifier]map[int]*Credential
Sietse Ringers's avatar
Sietse Ringers committed
23
	issuance    issuanceState
24
25
}

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

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

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

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

}

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

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

82
func (cm *CredentialManager) CredentialByID(id CredentialIdentifier) (cred *Credential, err error) {
83
	return cm.Credential(id.Type, id.Index)
84
85
}

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

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

// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
Sietse Ringers's avatar
Sietse Ringers committed
122
123
124
// 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.
125
func (cm *CredentialManager) ParseAndroidStorage() (err error) {
126
	exists, err := PathExists(cm.path(cardemuXML))
127
	if err != nil || !exists {
128
129
		return
	}
130
131

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

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

Sietse Ringers's avatar
Sietse Ringers committed
150
151
	for _, list := range parsedjson {
		cm.secretkey = list[0].Attributes[0]
152
153
		for i, gabicred := range list {
			cred := newCredential(gabicred)
154
155
156
157
158
159
160
161
162
163
			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
			}
		}
164
	}
165
166
167

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

177
	return
178
179
}

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

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

191
// Add adds the specified credential to the CredentialManager.
192
func (cm *CredentialManager) Add(cred *Credential) (err error) {
193
194
195
196
197
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

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

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

func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
209
	candidates := make([]*AttributeIdentifier, 0, 10)
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241

	for _, attribute := range disjunction.Attributes {
		credId := attribute.CredentialTypeIdentifier()
		if !MetaStore.Contains(credId) {
			continue
		}
		creds := cm.credentials[credId]
		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)
				if val == "" {
					continue
				}
				if !disjunction.HasValues() || val == disjunction.Values[attribute] {
					candidates = append(candidates, id)
				}
			}
		}
	}

	return candidates
}

func (cm *CredentialManager) CheckSatisfiability(disjunctions DisjunctionListContainer) AttributeDisjunctionList {
242
	missing := make(AttributeDisjunctionList, 0, 5)
243
244
245
246
247
248
249
250
	for _, disjunction := range disjunctions.DisjunctionList() {
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
251

252
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
253
254
255
256
257
258
259
260
261
	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 {
262
263
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
264
265
266
267
268
269
270
271
272
273
274
275
276
			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
		}

		// These indices will be used in the []*big.Int at gabi.Credential.Attributes,
		// which doesn't know about the secret key and metadata attribute, so +2
277
		grouped[ici] = append(grouped[ici], index+2)
278
279
280
281
282
283
284
285
286
287
	}

	return grouped, nil
}

type SessionRequest interface {
	GetNonce() *big.Int
	GetContext() *big.Int
}

288
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request SessionRequest, issig bool) (gabi.ProofList, error) {
289
290
291
292
293
294
295
296
297
298
299
300
301
302
	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}

	builders := []gabi.ProofBuilder{}
	for id, list := range todisclose {
		cred, err := cm.CredentialByID(id)
		if err != nil {
			return nil, err
		}
		builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
	}

303
	return gabi.BuildProofList(request.GetContext(), request.GetNonce(), builders, issig), nil
304
}
Sietse Ringers's avatar
Sietse Ringers committed
305
306
307
308
309
310
311
312
313
314
315
316
317
318

type issuanceState struct {
	builders []*gabi.CredentialBuilder
	nonce2   *big.Int
}

func (cm *CredentialManager) IssueCommitments(choice *DisclosureChoice, request SessionRequest) (gabi.IssueCommitmentMessage, error) {
	cm.issuance = issuanceState{[]*gabi.CredentialBuilder{}, nil}

	todisclose, err := cm.groupCredentials(choice)
	if err != nil {
		return nil, err
	}
}