storage.go 11.2 KB
Newer Older
1
2
3
package irmago

import (
Sietse Ringers's avatar
Sietse Ringers committed
4
5
	"crypto/rand"
	"encoding/hex"
6
7
8
	"encoding/json"
	"io/ioutil"
	"os"
Sietse Ringers's avatar
Sietse Ringers committed
9
	"path"
10
11
	"time"

Sietse Ringers's avatar
Sietse Ringers committed
12
	"github.com/go-errors/errors"
13
	"github.com/mhe/gabi"
14
15
16
17
18
19
)

// Filenames in which we store stuff
const (
	skFile         = "sk"
	attributesFile = "attrs"
20
21
	kssFile        = "kss"
	paillierFile   = "paillier"
22
	updatesFile    = "updates"
Sietse Ringers's avatar
Sietse Ringers committed
23
	logsFile       = "logs"
24
25
26
	signaturesDir  = "sigs"
)

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

39
40
41
42
43
44
45
46
47
48
49
func ensureDirectoryExists(path string) error {
	exists, err := PathExists(path)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}
	return os.Mkdir(path, 0700)
}

Sietse Ringers's avatar
Sietse Ringers committed
50
51
52
53
54
55
56
57
58
// 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 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)
59
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
60
		return
61
	}
Sietse Ringers's avatar
Sietse Ringers committed
62
63
64
65
66
67
	tempfilename := hex.EncodeToString(randBytes)

	// Create temp file
	err = ioutil.WriteFile(dir+"/"+tempfilename, content, 0600)
	if err != nil {
		return
68
	}
Sietse Ringers's avatar
Sietse Ringers committed
69
70
71

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

74
75
76
77
78
79
80
81
82
83
84
85
// NewCredentialManager creates a new CredentialManager that uses the directory
// specified by storagePath for (de)serializing itself. irmaConfigurationPath
// is the path to a (possibly readonly) folder containing irma_configuration;
// androidStoragePath is an optional path to the files of the old android app
// (specify "" if you do not want to parse the old android app files),
// and keyshareHandler is used for when a registration to a keyshare server needs
// to happen.
// The credential manager returned by this function has been fully deserialized
// and is ready for use.
//
// NOTE: It is the responsibility of the caller that there exists a directory
// at storagePath!
86
func NewCredentialManager(
87
88
	storagePath string,
	irmaConfigurationPath string,
89
	androidStoragePath string,
90
	keyshareHandler KeyshareHandler,
91
92
93
) (*CredentialManager, error) {
	var err error
	cm := &CredentialManager{
94
95
		credentials:           make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers:       make(map[SchemeManagerIdentifier]*keyshareServer),
96
		attributes:            make(map[CredentialTypeIdentifier][]*AttributeList),
97
		irmaConfigurationPath: irmaConfigurationPath,
98
		androidStoragePath:    androidStoragePath,
99
		ConfigurationStore:    NewConfigurationStore(storagePath + "/irma_configuration"),
100
101
	}

102
	exists, err := PathExists(cm.irmaConfigurationPath)
Sietse Ringers's avatar
Sietse Ringers committed
103
104
105
	if err != nil {
		return nil, err
	}
106
107
108
109
	if !exists {
		return nil, errors.New("irmaConfigurationPath does not exist")
	}

110
	// Ensure storage path exists, and populate it with necessary files
111
112
	cm.storagePath = storagePath
	if err = cm.ensureStorageExists(); err != nil {
113
		return nil, err
114
	}
115
	if err = cm.ConfigurationStore.ParseFolder(); err != nil {
116
		return nil, err
117
	}
118

119
120
121
122
123
124
	// Perform new update functions from credentialManagerUpdates, if any
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
125
	if cm.secretkey, err = cm.loadSecretKey(); err != nil {
126
		return nil, err
127
	}
128
	if cm.attributes, err = cm.loadAttributes(); err != nil {
129
		return nil, err
130
	}
131
	if cm.paillierKeyCache, err = cm.loadPaillierKeys(); err != nil {
132
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
133
	}
134
	if cm.keyshareServers, err = cm.loadKeyshareServers(); err != nil {
135
		return nil, err
136
	}
Sietse Ringers's avatar
Sietse Ringers committed
137
138
139

	unenrolled := cm.unenrolledKeyshareServers()
	switch len(unenrolled) {
140
	case 0: // nop
Sietse Ringers's avatar
Sietse Ringers committed
141
	case 1:
Sietse Ringers's avatar
Sietse Ringers committed
142
		if keyshareHandler == nil {
143
			return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
Sietse Ringers's avatar
Sietse Ringers committed
144
		}
145
		keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) {
Sietse Ringers's avatar
Sietse Ringers committed
146
147
			cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
		})
Sietse Ringers's avatar
Sietse Ringers committed
148
	default:
149
		return nil, errors.New("Too many keyshare servers")
Sietse Ringers's avatar
Sietse Ringers committed
150
151
	}

152
	return cm, nil
153
154
}

155
156
157
158
159
// update performs any function from credentialManagerUpdates that has not
// already been executed in the past, keeping track of previously executed updates
// in the file at updatesFile.
func (cm *CredentialManager) update() error {
	// Load and parse file containing info about already performed updates
Sietse Ringers's avatar
Sietse Ringers committed
160
	var err error
161
162
163
164
165
166
167
	exists, err := PathExists(cm.path(updatesFile))
	if err != nil {
		return err
	}
	if !exists {
		cm.updates = []update{}
	} else {
Sietse Ringers's avatar
Sietse Ringers committed
168
169
		var bytes []byte
		bytes, err = ioutil.ReadFile(cm.path(updatesFile))
170
171
172
173
174
175
176
177
178
179
		if err != nil {
			return err
		}
		if err = json.Unmarshal(bytes, &cm.updates); err != nil {
			return err
		}
	}

	// Perform all new updates
	for i := len(cm.updates); i < len(credentialManagerUpdates); i++ {
180
181
182
183
184
185
186
187
188
		err = credentialManagerUpdates[i](cm)
		update := update{
			When:    Timestamp(time.Now()),
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
			update.Error = &str
189
		}
190
		cm.updates = append(cm.updates, update)
191
192
193
194
195
196
197
	}

	// Save updates file
	bytes, err := json.Marshal(cm.updates)
	if err != nil {
		return err
	}
Sietse Ringers's avatar
Sietse Ringers committed
198
	saveFile(cm.path(updatesFile), bytes)
199
200
201
202

	return nil
}

203
204
205
206
func (cm *CredentialManager) path(file string) string {
	return cm.storagePath + "/" + file
}

207
func (cm *CredentialManager) signatureFilename(attrs *AttributeList) string {
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
211
212
	// We take the SHA256 hash over all attributes as the filename for the signature.
	// This means that the signatures of two credentials that have identical attributes
	// will be written to the same file, one overwriting the other - but that doesn't
	// matter, because either one of the signatures is valid over both attribute lists,
	// so keeping one of them suffices.
213
	return cm.path(signaturesDir) + "/" + attrs.hash()
Sietse Ringers's avatar
Sietse Ringers committed
214
215
216
217
218
219
220
}

// 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.
221
func (cm *CredentialManager) ensureStorageExists() error {
222
	exist, err := PathExists(cm.storagePath)
223
	if err != nil {
224
		return err
225
226
227
228
229
	}
	if !exist {
		return errors.New("credential storage path does not exist")
	}

230
	if exist, err = PathExists(cm.ConfigurationStore.path); err != nil {
231
232
233
		return err
	}
	if !exist {
234
		if err = ensureDirectoryExists(cm.ConfigurationStore.path); err != nil {
235
236
			return err
		}
237
		cm.ConfigurationStore.Copy(cm.irmaConfigurationPath, false)
238
	}
239
	return ensureDirectoryExists(cm.path(signaturesDir))
240
241
}

242
243
244
245
246
func (cm *CredentialManager) storeSecretKey(sk *secretKey) error {
	bytes, err := json.Marshal(sk)
	if err != nil {
		return err
	}
Sietse Ringers's avatar
Sietse Ringers committed
247
	return saveFile(cm.path(skFile), bytes)
248
249
}

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

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

260
	filename := cm.signatureFilename(cred.AttributeList())
Sietse Ringers's avatar
Sietse Ringers committed
261
	err = saveFile(filename, credbytes)
262
263
264
	return
}

265
266
267
268
269
270
func (cm *CredentialManager) storeAttributes() error {
	temp := []*AttributeList{}
	for _, attrlistlist := range cm.attributes {
		for _, attrlist := range attrlistlist {
			temp = append(temp, attrlist)
		}
271
272
	}

273
	if attrbytes, err := json.Marshal(temp); err == nil {
Sietse Ringers's avatar
Sietse Ringers committed
274
		return saveFile(cm.path(attributesFile), attrbytes)
275
276
277
	} else {
		return err
	}
278
}
Sietse Ringers's avatar
Sietse Ringers committed
279

280
func (cm *CredentialManager) storeKeyshareServers() (err error) {
281
	bts, err := json.Marshal(cm.keyshareServers)
282
283
284
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
285
	err = saveFile(cm.path(kssFile), bts)
286
287
288
289
290
291
292
293
	return
}

func (cm *CredentialManager) storePaillierKeys() (err error) {
	bts, err := json.Marshal(cm.paillierKeyCache)
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
294
	err = saveFile(cm.path(paillierFile), bts)
295
296
297
	return
}

Sietse Ringers's avatar
Sietse Ringers committed
298
299
300
301
302
303
304
305
306
func (cm *CredentialManager) storeLogs() (err error) {
	bts, err := json.Marshal(cm.logs)
	if err != nil {
		return
	}
	err = saveFile(cm.path(logsFile), bts)
	return
}

307
308
func (cm *CredentialManager) loadSignature(attrs *AttributeList) (signature *gabi.CLSignature, err error) {
	sigpath := cm.signatureFilename(attrs)
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
309
	exists, err := PathExists(sigpath)
310
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
311
312
		return
	}
313
314
315
	if !exists {
		return nil, errors.New("Signature file not found")
	}
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
316
	bytes, err := ioutil.ReadFile(sigpath)
317
318
319
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
320
321
322
323
324
325
326
	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.
327
328
329
func (cm *CredentialManager) loadSecretKey() (*secretKey, error) {
	sk := &secretKey{}
	var err error
330
	exists, err := PathExists(cm.path(skFile))
Sietse Ringers's avatar
Sietse Ringers committed
331
332
333
334
	if err != nil {
		return nil, err
	}
	if exists {
335
		var bytes []byte
336
337
		if bytes, err = ioutil.ReadFile(cm.path(skFile)); err != nil {
			return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
338
		}
339
340
341
342
		if err = json.Unmarshal(bytes, sk); err != nil {
			return nil, err
		}
		return sk, err
Sietse Ringers's avatar
Sietse Ringers committed
343
	}
344

345
	sk, err = cm.generateSecretKey()
346
347
348
349
350
351
352
353
	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
354
355
}

356
func (cm *CredentialManager) loadAttributes() (list map[CredentialTypeIdentifier][]*AttributeList, err error) {
357
	exists, err := PathExists(cm.path(attributesFile))
Sietse Ringers's avatar
Sietse Ringers committed
358
359
360
361
362
363
364
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(cm.path(attributesFile))
	if err != nil {
		return nil, err
	}
365
366
367
368
369

	// The attributes are stored as a list of instances of AttributeList
	temp := []*AttributeList{}
	list = make(map[CredentialTypeIdentifier][]*AttributeList)
	if err = json.Unmarshal(bytes, &temp); err != nil {
370
371
		return nil, err
	}
372
	for _, attrlist := range temp {
373
		attrlist.MetadataAttribute = MetadataFromInt(attrlist.Ints[0], cm.ConfigurationStore)
374
375
376
377
378
379
380
		id := attrlist.CredentialType()
		var ct CredentialTypeIdentifier
		if id != nil {
			ct = id.Identifier()
		}
		if _, contains := list[ct]; !contains {
			list[ct] = []*AttributeList{}
381
		}
382
		list[ct] = append(list[ct], attrlist)
383
384
	}

385
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
386
}
387
388
389
390
391
392
393
394
395
396
397

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
	}
398
	err = json.Unmarshal(bytes, &ksses)
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
	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
}
Sietse Ringers's avatar
Sietse Ringers committed
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437

func (cm *CredentialManager) loadLogs() (logs []*LogEntry, err error) {
	logs = []*LogEntry{}
	exists, err := PathExists(cm.path(logsFile))
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(cm.path(logsFile))
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(bytes, &logs)
	if err != nil {
		return nil, err
	}
	return
}