storage.go 9.37 KB
Newer Older
1
2
3
4
package irmago

import (
	"encoding/json"
5
	"encoding/xml"
6
	"errors"
7
	"html"
8
9
10
11
	"io/ioutil"
	"os"
	"strconv"

Sietse Ringers's avatar
Sietse Ringers committed
12
13
14
15
	"crypto/rand"
	"encoding/hex"
	"math/big"
	"path"
16
17

	"github.com/mhe/gabi"
18
19
20
21
22
23
)

// Filenames in which we store stuff
const (
	skFile         = "sk"
	attributesFile = "attrs"
24
25
	kssFile        = "kss"
	paillierFile   = "paillier"
26
	signaturesDir  = "sigs"
Sietse Ringers's avatar
Sietse Ringers committed
27
	cardemuXML     = "../cardemu.xml"
28
29
)

Sietse Ringers's avatar
Sietse Ringers committed
30
// PathExists checks if the specified path exists.
31
func PathExists(path string) (bool, error) {
32
33
34
35
36
37
38
39
40
41
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

42
43
// NewCredentialManager deserializes the credentials from storage.
func NewCredentialManager(
44
45
46
	storagePath string,
	irmaConfigurationPath string,
	keyshareHandler KeyshareHandler,
47
48
49
50
51
52
53
) (*CredentialManager, error) {
	var err error
	cm := &CredentialManager{
		credentials:     make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers: make(map[SchemeManagerIdentifier]*keyshareServer),
	}

54
	if err = MetaStore.ParseFolder(irmaConfigurationPath); err != nil {
55
		return nil, err
56
	}
57

58
59
	cm.storagePath = storagePath
	if err = cm.ensureStorageExists(); err != nil {
60
		return nil, err
61
	}
62
	if cm.secretkey, err = cm.loadSecretKey(); err != nil {
63
		return nil, err
64
	}
65
	if cm.attributes, err = cm.loadAttributes(); err != nil {
66
		return nil, err
67
	}
68
	if cm.paillierKeyCache, err = cm.loadPaillierKeys(); err != nil {
69
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
70
	}
71
	if cm.keyshareServers, err = cm.loadKeyshareServers(); err != nil {
72
		return nil, err
73
	}
Sietse Ringers's avatar
Sietse Ringers committed
74
75
76

	unenrolled := cm.unenrolledKeyshareServers()
	switch len(unenrolled) {
77
	case 0: // nop
Sietse Ringers's avatar
Sietse Ringers committed
78
	case 1:
Sietse Ringers's avatar
Sietse Ringers committed
79
		if keyshareHandler == nil {
80
			return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
Sietse Ringers's avatar
Sietse Ringers committed
81
		}
82
		keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) {
Sietse Ringers's avatar
Sietse Ringers committed
83
84
			cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
		})
Sietse Ringers's avatar
Sietse Ringers committed
85
	default:
86
		return nil, errors.New("Too many keyshare servers")
Sietse Ringers's avatar
Sietse Ringers committed
87
88
	}

89
	return cm, nil
90
91
92
93
94
95
96
97
}

// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
// 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.
func (cm *CredentialManager) ParseAndroidStorage() (err error) {
	exists, err := PathExists(cm.path(cardemuXML))
98
	if err != nil {
99
100
		return
	}
101
102
103
	if !exists {
		return errors.New("cardemu.xml not found at " + cardemuXML)
	}
104
105

	bytes, err := ioutil.ReadFile(cm.path(cardemuXML))
106
107
108
	if err != nil {
		return
	}
109
110
111
112
113
114
115
116
117
	parsedxml := struct {
		Strings []struct {
			Name    string `xml:"name,attr"`
			Content string `xml:",chardata"`
		} `xml:"string"`
	}{}
	xml.Unmarshal(bytes, &parsedxml)

	parsedjson := make(map[string][]*gabi.Credential)
118
	cm.keyshareServers = make(map[SchemeManagerIdentifier]*keyshareServer)
119
120
121
122
123
124
125
126
127
	for _, xmltag := range parsedxml.Strings {
		if xmltag.Name == "credentials" {
			jsontag := html.UnescapeString(xmltag.Content)
			if err = json.Unmarshal([]byte(jsontag), &parsedjson); err != nil {
				return
			}
		}
		if xmltag.Name == "keyshare" {
			jsontag := html.UnescapeString(xmltag.Content)
128
			if err = json.Unmarshal([]byte(jsontag), &cm.keyshareServers); err != nil {
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
				return
			}
		}
		if xmltag.Name == "KeyshareKeypairs" {
			jsontag := html.UnescapeString(xmltag.Content)
			keys := make([]*paillierPrivateKey, 0, 3)
			if err = json.Unmarshal([]byte(jsontag), &keys); err != nil {
				return
			}
			cm.paillierKeyCache = keys[0]
		}
	}

	for _, list := range parsedjson {
		cm.secretkey = list[0].Attributes[0]
144
		for _, gabicred := range list {
145
146
147
148
149
			cred := newCredential(gabicred)
			if cred.CredentialType() == nil {
				return errors.New("cannot add unknown credential type")
			}

150
			err = cm.addCredential(cred, false)
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
			if err != nil {
				return err
			}
		}
	}

	if len(cm.credentials) > 0 {
		err = cm.storeAttributes()
		if err != nil {
			return err
		}
		err = cm.storeSecretKey(cm.secretkey)
		if err != nil {
			return err
		}
	}

	if len(cm.keyshareServers) > 0 {
		err = cm.storeKeyshareServers()
		if err != nil {
			return err
		}
	}

	err = cm.storePaillierKeys()
	if err != nil {
		return err
	}
	if cm.paillierKeyCache == nil {
Sietse Ringers's avatar
Sietse Ringers committed
180
		cm.paillierKey(false) // trigger calculating a new one
181
182
183
184
185
	}

	return
}

186
187
188
189
func (cm *CredentialManager) path(file string) string {
	return cm.storagePath + "/" + file
}

Sietse Ringers's avatar
Sietse Ringers committed
190
191
192
193
194
195
196
197
198
func (cm *CredentialManager) signatureFilename(id string, counter int) string {
	return cm.path(signaturesDir) + "/" + id + "-" + strconv.Itoa(counter)
}

// ensureStorageExists initializes the credential storage folder,
// ensuring that it is in a usable state.
// NOTE: we do not create the folder if it does not exist!
// Setting it up in a properly protected location (e.g., with automatic
// backups to iCloud/Google disabled) is the responsibility of the user.
199
func (cm *CredentialManager) ensureStorageExists() (err error) {
200
	exist, err := PathExists(cm.storagePath)
201
202
203
204
205
206
207
	if err != nil {
		return
	}
	if !exist {
		return errors.New("credential storage path does not exist")
	}

208
	exist, err = PathExists(cm.path(signaturesDir))
209
210
211
212
213
214
215
216
217
218
	if err != nil {
		return err
	}
	if !exist {
		err = os.Mkdir(cm.path(signaturesDir), 0700)
	}

	return
}

Sietse Ringers's avatar
Sietse Ringers committed
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
func (cm *CredentialManager) storeSecretKey(sk *big.Int) error {
	return ioutil.WriteFile(cm.path(skFile), sk.Bytes(), 0600)
}

// Save the filecontents at the specified path atomically:
// - first save the content in a temp file with a random filename in the same dir
// - then rename the temp file to the specified filepath, overwriting the old file
func (cm *CredentialManager) saveFile(filepath string, content []byte) (err error) {
	dir := path.Dir(filepath)

	// Read random data for filename and convert to hex
	randBytes := make([]byte, 16)
	_, err = rand.Read(randBytes)
	if err != nil {
		return
	}
	tempfilename := hex.EncodeToString(randBytes)

	// Create temp file
	err = ioutil.WriteFile(dir+"/"+tempfilename, content, 0600)
	if err != nil {
		return
	}

	// Rename, overwriting old file
	return os.Rename(dir+"/"+tempfilename, filepath)
245
246
}

Sietse Ringers's avatar
Sietse Ringers committed
247
func (cm *CredentialManager) storeSignature(cred *credential, counter int) (err error) {
248
249
250
251
252
253
254
255
256
257
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

	credbytes, err := json.Marshal(cred.Signature)
	if err != nil {
		return err
	}

	// TODO existence check
258
	filename := cm.signatureFilename(cred.CredentialType().Identifier().String(), counter)
259
260
261
262
263
	err = ioutil.WriteFile(filename, credbytes, 0600)
	return
}

func (cm *CredentialManager) storeAttributes() (err error) {
264
	attrbytes, err := json.Marshal(cm.attributes)
265
	if err != nil {
266
		return err
267
268
269
270
271
272
	}

	// TODO existence check
	err = ioutil.WriteFile(cm.path(attributesFile), attrbytes, 0600)
	return
}
Sietse Ringers's avatar
Sietse Ringers committed
273

274
func (cm *CredentialManager) storeKeyshareServers() (err error) {
275
	bts, err := json.Marshal(cm.keyshareServers)
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
	if err != nil {
		return
	}
	err = ioutil.WriteFile(cm.path(kssFile), bts, 0600)
	return
}

func (cm *CredentialManager) storePaillierKeys() (err error) {
	bts, err := json.Marshal(cm.paillierKeyCache)
	if err != nil {
		return
	}
	err = ioutil.WriteFile(cm.path(paillierFile), bts, 0600)
	return
}

292
func (cm *CredentialManager) loadSignature(id CredentialTypeIdentifier, counter int) (signature *gabi.CLSignature, err error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
293
294
	sigpath := cm.signatureFilename(id.String(), counter)
	exists, err := PathExists(sigpath)
295
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
296
297
		return
	}
298
299
300
	if !exists {
		return nil, errors.New("Signature file not found")
	}
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
301
	bytes, err := ioutil.ReadFile(sigpath)
302
303
304
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
305
306
307
308
309
310
311
312
	signature = new(gabi.CLSignature)
	err = json.Unmarshal(bytes, signature)
	return
}

// loadSecretKey retrieves and returns the secret key from storage, or if no secret key
// was found in storage, it generates, saves, and returns a new secret key.
func (cm *CredentialManager) loadSecretKey() (*big.Int, error) {
313
	exists, err := PathExists(cm.path(skFile))
Sietse Ringers's avatar
Sietse Ringers committed
314
315
316
317
	if err != nil {
		return nil, err
	}
	if exists {
318
319
320
		var bytes []byte
		if bytes, err = ioutil.ReadFile(cm.path(skFile)); err == nil {
			return new(big.Int).SetBytes(bytes), nil
Sietse Ringers's avatar
Sietse Ringers committed
321
		}
322
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
323
	}
324
325
326
327
328
329
330
331
332
333

	sk, err := cm.generateSecretKey()
	if err != nil {
		return nil, err
	}
	err = cm.storeSecretKey(sk)
	if err != nil {
		return nil, err
	}
	return sk, nil
Sietse Ringers's avatar
Sietse Ringers committed
334
335
}

336
337
func (cm *CredentialManager) loadAttributes() (list map[CredentialTypeIdentifier][]*AttributeList, err error) {
	list = make(map[CredentialTypeIdentifier][]*AttributeList)
338
	exists, err := PathExists(cm.path(attributesFile))
Sietse Ringers's avatar
Sietse Ringers committed
339
340
341
342
343
344
345
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(cm.path(attributesFile))
	if err != nil {
		return nil, err
	}
346
	err = json.Unmarshal(bytes, &list)
347
348
349
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
350

351
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
352
}
353
354
355
356
357
358
359
360
361
362
363

func (cm *CredentialManager) loadKeyshareServers() (ksses map[SchemeManagerIdentifier]*keyshareServer, err error) {
	ksses = make(map[SchemeManagerIdentifier]*keyshareServer)
	exists, err := PathExists(cm.path(kssFile))
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(cm.path(kssFile))
	if err != nil {
		return nil, err
	}
364
	err = json.Unmarshal(bytes, &ksses)
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
	if err != nil {
		return nil, err
	}
	return
}

func (cm *CredentialManager) loadPaillierKeys() (key *paillierPrivateKey, err error) {
	exists, err := PathExists(cm.path(paillierFile))
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(cm.path(paillierFile))
	if err != nil {
		return nil, err
	}
	key = new(paillierPrivateKey)
	err = json.Unmarshal(bytes, key)
	if err != nil {
		return nil, err
	}
	return
}