storage.go 11.1 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
103
104
105
106
	exists, err := PathExists(cm.irmaConfigurationPath)
	if !exists {
		return nil, errors.New("irmaConfigurationPath does not exist")
	}

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

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

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

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

149
	return cm, nil
150
151
}

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// 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
	exists, err := PathExists(cm.path(updatesFile))
	if err != nil {
		return err
	}
	if !exists {
		cm.updates = []update{}
	} else {
		bytes, err := ioutil.ReadFile(cm.path(updatesFile))
		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++ {
175
176
177
178
179
180
181
182
183
		err = credentialManagerUpdates[i](cm)
		update := update{
			When:    Timestamp(time.Now()),
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
			update.Error = &str
184
		}
185
		cm.updates = append(cm.updates, update)
186
187
188
189
190
191
192
	}

	// Save updates file
	bytes, err := json.Marshal(cm.updates)
	if err != nil {
		return err
	}
Sietse Ringers's avatar
Sietse Ringers committed
193
	saveFile(cm.path(updatesFile), bytes)
194
195
196
197

	return nil
}

198
199
200
201
func (cm *CredentialManager) path(file string) string {
	return cm.storagePath + "/" + file
}

202
func (cm *CredentialManager) signatureFilename(attrs *AttributeList) string {
Sietse Ringers's avatar
Sietse Ringers committed
203
204
205
206
207
	// 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.
208
	return cm.path(signaturesDir) + "/" + attrs.hash()
Sietse Ringers's avatar
Sietse Ringers committed
209
210
211
212
213
214
215
}

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

225
	if exist, err = PathExists(cm.ConfigurationStore.path); err != nil {
226
227
228
		return err
	}
	if !exist {
229
		if err = ensureDirectoryExists(cm.ConfigurationStore.path); err != nil {
230
231
			return err
		}
232
		cm.ConfigurationStore.Copy(cm.irmaConfigurationPath, false)
233
	}
234
	return ensureDirectoryExists(cm.path(signaturesDir))
235
236
}

237
238
239
240
241
func (cm *CredentialManager) storeSecretKey(sk *secretKey) error {
	bytes, err := json.Marshal(sk)
	if err != nil {
		return err
	}
Sietse Ringers's avatar
Sietse Ringers committed
242
	return saveFile(cm.path(skFile), bytes)
243
244
}

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

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

255
	filename := cm.signatureFilename(cred.AttributeList())
Sietse Ringers's avatar
Sietse Ringers committed
256
	err = saveFile(filename, credbytes)
257
258
259
	return
}

260
261
262
263
264
265
func (cm *CredentialManager) storeAttributes() error {
	temp := []*AttributeList{}
	for _, attrlistlist := range cm.attributes {
		for _, attrlist := range attrlistlist {
			temp = append(temp, attrlist)
		}
266
267
	}

268
	if attrbytes, err := json.Marshal(temp); err == nil {
Sietse Ringers's avatar
Sietse Ringers committed
269
		err = saveFile(cm.path(attributesFile), attrbytes)
270
271
272
273
		return nil
	} else {
		return err
	}
274
}
Sietse Ringers's avatar
Sietse Ringers committed
275

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

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

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

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

341
	sk, err = cm.generateSecretKey()
342
343
344
345
346
347
348
349
	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
350
351
}

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

	// 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 {
366
367
		return nil, err
	}
368
	for _, attrlist := range temp {
369
		attrlist.MetadataAttribute = MetadataFromInt(attrlist.Ints[0], cm.ConfigurationStore)
370
371
372
373
374
375
376
		id := attrlist.CredentialType()
		var ct CredentialTypeIdentifier
		if id != nil {
			ct = id.Identifier()
		}
		if _, contains := list[ct]; !contains {
			list[ct] = []*AttributeList{}
377
		}
378
		list[ct] = append(list[ct], attrlist)
379
380
	}

381
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
382
}
383
384
385
386
387
388
389
390
391
392
393

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

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
}