storage.go 10.5 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"
23
24
25
	signaturesDir  = "sigs"
)

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

38
39
40
41
42
43
44
45
46
47
48
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
49
50
51
52
53
54
55
56
57
// 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)
58
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
59
		return
60
	}
Sietse Ringers's avatar
Sietse Ringers committed
61
62
63
64
65
66
	tempfilename := hex.EncodeToString(randBytes)

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

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

73
74
75
76
77
78
79
80
81
82
83
84
// 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!
85
func NewCredentialManager(
86
87
	storagePath string,
	irmaConfigurationPath string,
88
	androidStoragePath string,
89
	keyshareHandler KeyshareHandler,
90
91
92
) (*CredentialManager, error) {
	var err error
	cm := &CredentialManager{
93
94
		credentials:           make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers:       make(map[SchemeManagerIdentifier]*keyshareServer),
95
		attributes:            make(map[CredentialTypeIdentifier][]*AttributeList),
96
		irmaConfigurationPath: irmaConfigurationPath,
97
98
		androidStoragePath:    androidStoragePath,
		Store:                 NewConfigurationStore(storagePath + "/irma_configuration"),
99
100
	}

101
	// Ensure storage path exists, and populate it with necessary files
102
103
	cm.storagePath = storagePath
	if err = cm.ensureStorageExists(); err != nil {
104
		return nil, err
105
	}
106
	if err = cm.Store.ParseFolder(); err != nil {
107
		return nil, err
108
	}
109

110
111
112
113
114
115
	// Perform new update functions from credentialManagerUpdates, if any
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
116
	if cm.secretkey, err = cm.loadSecretKey(); err != nil {
117
		return nil, err
118
	}
119
	if cm.attributes, err = cm.loadAttributes(); err != nil {
120
		return nil, err
121
	}
122
	if cm.paillierKeyCache, err = cm.loadPaillierKeys(); err != nil {
123
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
124
	}
125
	if cm.keyshareServers, err = cm.loadKeyshareServers(); err != nil {
126
		return nil, err
127
	}
Sietse Ringers's avatar
Sietse Ringers committed
128
129
130

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

143
	return cm, nil
144
145
}

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 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++ {
169
170
171
172
173
174
175
176
177
		err = credentialManagerUpdates[i](cm)
		update := update{
			When:    Timestamp(time.Now()),
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
			update.Error = &str
178
		}
179
		cm.updates = append(cm.updates, update)
180
181
182
183
184
185
186
	}

	// Save updates file
	bytes, err := json.Marshal(cm.updates)
	if err != nil {
		return err
	}
Sietse Ringers's avatar
Sietse Ringers committed
187
	saveFile(cm.path(updatesFile), bytes)
188
189
190
191

	return nil
}

192
193
194
195
func (cm *CredentialManager) path(file string) string {
	return cm.storagePath + "/" + file
}

196
func (cm *CredentialManager) signatureFilename(attrs *AttributeList) string {
Sietse Ringers's avatar
Sietse Ringers committed
197
198
199
200
201
	// 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.
202
	return cm.path(signaturesDir) + "/" + attrs.hash()
Sietse Ringers's avatar
Sietse Ringers committed
203
204
205
206
207
208
209
}

// 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.
210
func (cm *CredentialManager) ensureStorageExists() error {
211
	exist, err := PathExists(cm.storagePath)
212
	if err != nil {
213
		return err
214
215
216
217
218
	}
	if !exist {
		return errors.New("credential storage path does not exist")
	}

219
220
221
222
223
224
225
226
227
228
229
230
	if exist, err = PathExists(cm.Store.path); err != nil {
		return err
	}
	if !exist {
		if err = ensureDirectoryExists(cm.Store.path); err != nil {
			return err
		}
		cm.Store.Copy(cm.irmaConfigurationPath, false)
		if err = cm.Store.ParseFolder(); err != nil {
			return err
		}
	}
231
	return ensureDirectoryExists(cm.path(signaturesDir))
232
233
}

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

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

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

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

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

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

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

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

291
292
func (cm *CredentialManager) loadSignature(attrs *AttributeList) (signature *gabi.CLSignature, err error) {
	sigpath := cm.signatureFilename(attrs)
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
293
	exists, err := PathExists(sigpath)
294
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
295
296
		return
	}
297
298
299
	if !exists {
		return nil, errors.New("Signature file not found")
	}
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
300
	bytes, err := ioutil.ReadFile(sigpath)
301
302
303
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
304
305
306
307
308
309
310
	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.
311
312
313
func (cm *CredentialManager) loadSecretKey() (*secretKey, error) {
	sk := &secretKey{}
	var err error
314
	exists, err := PathExists(cm.path(skFile))
Sietse Ringers's avatar
Sietse Ringers committed
315
316
317
318
	if err != nil {
		return nil, err
	}
	if exists {
319
		var bytes []byte
320
321
		if bytes, err = ioutil.ReadFile(cm.path(skFile)); err != nil {
			return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
322
		}
323
324
325
326
		if err = json.Unmarshal(bytes, sk); err != nil {
			return nil, err
		}
		return sk, err
Sietse Ringers's avatar
Sietse Ringers committed
327
	}
328

329
	sk, err = cm.generateSecretKey()
330
331
332
333
334
335
336
337
	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
338
339
}

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

	// 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 {
354
355
		return nil, err
	}
356
357
358
359
360
361
362
363
364
	for _, attrlist := range temp {
		attrlist.MetadataAttribute = MetadataFromInt(attrlist.Ints[0], cm.Store)
		id := attrlist.CredentialType()
		var ct CredentialTypeIdentifier
		if id != nil {
			ct = id.Identifier()
		}
		if _, contains := list[ct]; !contains {
			list[ct] = []*AttributeList{}
365
		}
366
		list[ct] = append(list[ct], attrlist)
367
368
	}

369
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
370
}
371
372
373
374
375
376
377
378
379
380
381

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
	}
382
	err = json.Unmarshal(bytes, &ksses)
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
	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
}