storage.go 10.4 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// 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
185
	saveFile(cm.path(updatesFile), bytes)
186
187
188
189

	return nil
}

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

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

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

217
218
219
220
221
222
223
224
225
226
227
228
	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
		}
	}
229
	return ensureDirectoryExists(cm.path(signaturesDir))
230
231
}

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

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

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

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

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

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

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

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

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

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

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

	// 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 {
352
353
		return nil, err
	}
354
355
356
357
358
359
360
361
362
	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{}
363
		}
364
		list[ct] = append(list[ct], attrlist)
365
366
	}

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

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