manager.go 6.47 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
81
		return
	}
	return list[counter]
}

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

	return cm.credentials[id][counter], nil
114
115
116
}

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

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

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

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

	if len(cm.credentials) > 0 {
		err = cm.storeAttributes()
163
		if err != nil {
164
			return err
165
		}
Sietse Ringers's avatar
Sietse Ringers committed
166
		err = cm.storeSecretKey(cm.secretkey)
167
		if err != nil {
168
			return err
169
170
171
		}
	}

172
	return
173
174
}

175
func (cm *CredentialManager) addCredential(cred *Credential) {
176
	id := cred.CredentialType().Identifier()
Sietse Ringers's avatar
Sietse Ringers committed
177
	cm.attributes[id] = append(cm.attrs(id), NewAttributeListFromInts(cred.Attributes[1:]))
178

179
	if _, exists := cm.credentials[id]; !exists {
180
		cm.credentials[id] = make(map[int]*Credential)
181
	}
Sietse Ringers's avatar
Sietse Ringers committed
182
183
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
184
}
185

186
// Add adds the specified credential to the CredentialManager.
187
func (cm *CredentialManager) Add(cred *Credential) (err error) {
188
189
190
191
192
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

	cm.addCredential(cred)
Sietse Ringers's avatar
Sietse Ringers committed
193
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
194
195

	err = cm.storeSignature(cred, counter)
196
197
198
	if err != nil {
		return
	}
199
	err = cm.storeAttributes()
200
201
	return
}
202
203
204
205
206
207
208
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
241
242
243
244
245

func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*AttributeIdentifier {
	candidates := make([]*AttributeIdentifier, 10)

	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 {
	missing := make(AttributeDisjunctionList, 5)
	for _, disjunction := range disjunctions.DisjunctionList() {
		if len(cm.Candidates(disjunction)) == 0 {
			missing = append(missing, disjunction)
		}
	}

	return missing
}