manager.go 8.27 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
23
24
}

Sietse Ringers's avatar
Sietse Ringers committed
25
26
func newCredentialManager() *CredentialManager {
	return &CredentialManager{
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
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 {
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]
}

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

Sietse Ringers's avatar
Sietse Ringers committed
85
// Credential returns the requested credential, or nil if we do not have it.
86
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
}

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 {
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

190
// Add adds the specified credential to the CredentialManager.
191
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
207

func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
208
	candidates := make([]*AttributeIdentifier, 0, 10)
209
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

	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 {
241
	missing := make(AttributeDisjunctionList, 0, 5)
242
243
244
245
246
247
248
249
	for _, disjunction := range disjunctions.DisjunctionList() {
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}
250

251
func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[CredentialIdentifier][]int, error) {
252
253
254
255
256
257
258
259
260
	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 {
261
262
			indices := make([]int, 1, 1)
			indices[0] = 1 // Always include metadata
263
264
265
266
267
268
269
270
271
272
273
274
275
			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
276
		grouped[ici] = append(grouped[ici], index+2)
277
278
279
280
281
282
283
284
285
286
	}

	return grouped, nil
}

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

287
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request SessionRequest) (gabi.ProofList, error) {
288
289
290
291
292
293
294
295
296
297
298
299
300
301
	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))
	}

302
	return gabi.BuildProofList(request.GetContext(), request.GetNonce(), builders), nil
303
}