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

import (
	"encoding/json"
	"errors"
6
	"io"
7
8
9
	"io/ioutil"
	"os"

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

14
15
	"time"

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

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

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

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
func ensureDirectoryExists(path string) error {
	exists, err := PathExists(path)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}
	return os.Mkdir(path, 0700)
}

// writeFile writes the contents of reader to a new or truncated file at dest.
func writeFile(reader io.Reader, dest string) error {
	destfile, err := os.Create(dest)
	if err != nil {
		return err
	}
	if _, err := io.Copy(destfile, reader); err != nil {
		destfile.Close()
		return err
	}
	return destfile.Close()
}

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

93
	// Ensure storage path exists, and populate it with necessary files
94
95
	cm.storagePath = storagePath
	if err = cm.ensureStorageExists(); err != nil {
96
		return nil, err
97
	}
98
	if err = cm.Store.ParseFolder(); err != nil {
99
		return nil, err
100
	}
101

102
103
104
105
106
107
	// Perform new update functions from credentialManagerUpdates, if any
	if err = cm.update(); err != nil {
		return nil, err
	}

	// Load our stuff
108
	if cm.secretkey, err = cm.loadSecretKey(); err != nil {
109
		return nil, err
110
	}
111
	if cm.attributes, err = cm.loadAttributes(); err != nil {
112
		return nil, err
113
	}
114
	if cm.paillierKeyCache, err = cm.loadPaillierKeys(); err != nil {
115
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
116
	}
117
	if cm.keyshareServers, err = cm.loadKeyshareServers(); err != nil {
118
		return nil, err
119
	}
Sietse Ringers's avatar
Sietse Ringers committed
120
121
122

	unenrolled := cm.unenrolledKeyshareServers()
	switch len(unenrolled) {
123
	case 0: // nop
Sietse Ringers's avatar
Sietse Ringers committed
124
	case 1:
Sietse Ringers's avatar
Sietse Ringers committed
125
		if keyshareHandler == nil {
126
			return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
Sietse Ringers's avatar
Sietse Ringers committed
127
		}
128
		keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) {
Sietse Ringers's avatar
Sietse Ringers committed
129
130
			cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
		})
Sietse Ringers's avatar
Sietse Ringers committed
131
	default:
132
		return nil, errors.New("Too many keyshare servers")
Sietse Ringers's avatar
Sietse Ringers committed
133
134
	}

135
	return cm, nil
136
137
}

138
139
140
141
142
143
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
169
170
171
172
173
174
175
176
177
178
179
180
181
// 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
	}
	cm.saveFile(cm.path(updatesFile), bytes)

	return nil
}

182
183
184
185
func (cm *CredentialManager) path(file string) string {
	return cm.storagePath + "/" + file
}

186
187
func (cm *CredentialManager) signatureFilename(attrs *AttributeList) string {
	return cm.path(signaturesDir) + "/" + attrs.hash()
Sietse Ringers's avatar
Sietse Ringers committed
188
189
190
191
192
193
194
}

// 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.
195
func (cm *CredentialManager) ensureStorageExists() error {
196
	exist, err := PathExists(cm.storagePath)
197
	if err != nil {
198
		return err
199
200
201
202
203
	}
	if !exist {
		return errors.New("credential storage path does not exist")
	}

204
205
206
207
208
209
210
211
212
213
214
215
	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
		}
	}
216
	return ensureDirectoryExists(cm.path(signaturesDir))
217
218
}

219
220
221
222
223
224
func (cm *CredentialManager) storeSecretKey(sk *secretKey) error {
	bytes, err := json.Marshal(sk)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(cm.path(skFile), bytes, 0600)
Sietse Ringers's avatar
Sietse Ringers committed
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
}

// 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)
249
250
}

Sietse Ringers's avatar
Sietse Ringers committed
251
func (cm *CredentialManager) storeSignature(cred *credential, counter int) (err error) {
252
253
254
255
256
257
258
259
260
261
	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
262
	filename := cm.signatureFilename(cred.AttributeList())
263
264
265
266
267
	err = ioutil.WriteFile(filename, credbytes, 0600)
	return
}

func (cm *CredentialManager) storeAttributes() (err error) {
268
	attrbytes, err := json.Marshal(cm.attributes)
269
	if err != nil {
270
		return err
271
272
273
274
275
276
	}

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

278
func (cm *CredentialManager) storeKeyshareServers() (err error) {
279
	bts, err := json.Marshal(cm.keyshareServers)
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
	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
}

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

334
	sk, err = cm.generateSecretKey()
335
336
337
338
339
340
341
342
	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
343
344
}

345
346
func (cm *CredentialManager) loadAttributes() (list map[CredentialTypeIdentifier][]*AttributeList, err error) {
	list = make(map[CredentialTypeIdentifier][]*AttributeList)
347
	exists, err := PathExists(cm.path(attributesFile))
Sietse Ringers's avatar
Sietse Ringers committed
348
349
350
351
352
353
354
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(cm.path(attributesFile))
	if err != nil {
		return nil, err
	}
355
	err = json.Unmarshal(bytes, &list)
356
357
358
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
359

360
361
	for _, attrlistlist := range list {
		for _, attrlist := range attrlistlist {
362
			attrlist.MetadataAttribute = MetadataFromInt(attrlist.Ints[0], cm.Store)
363
364
365
		}
	}

366
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
367
}
368
369
370
371
372
373
374
375
376
377
378

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