storage.go 10.4 KB
Newer Older
1
2
3
4
5
6
7
8
package irmago

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"

Sietse Ringers's avatar
Sietse Ringers committed
9
10
11
	"crypto/rand"
	"encoding/hex"
	"path"
12

13
14
	"time"

15
	"github.com/mhe/gabi"
16
17
18
19
20
21
)

// Filenames in which we store stuff
const (
	skFile         = "sk"
	attributesFile = "attrs"
22
23
	kssFile        = "kss"
	paillierFile   = "paillier"
24
	updatesFile    = "updates"
25
26
27
	signaturesDir  = "sigs"
)

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

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

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

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

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

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

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

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

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

145
	return cm, nil
146
147
}

148
149
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
175
176
177
178
179
180
181
182
183
184
185
186
// 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++ {
		if err := credentialManagerUpdates[i](cm); err != nil {
			return err
		}
		cm.updates = append(cm.updates,
			update{
				When:   Timestamp(time.Now()),
				Number: i,
			},
		)
	}

	// 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
}