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

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

Sietse Ringers's avatar
Sietse Ringers committed
11
12
13
14
	"crypto/rand"
	"encoding/hex"
	"math/big"
	"path"
15
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
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
51
52
53
54
55
56
57
58
59
60
61
62
63
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()
}

64
65
// NewCredentialManager deserializes the credentials from storage.
func NewCredentialManager(
66
67
68
	storagePath string,
	irmaConfigurationPath string,
	keyshareHandler KeyshareHandler,
69
70
71
) (*CredentialManager, error) {
	var err error
	cm := &CredentialManager{
72
73
74
75
		credentials:           make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers:       make(map[SchemeManagerIdentifier]*keyshareServer),
		irmaConfigurationPath: irmaConfigurationPath,
		Store: NewConfigurationStore(storagePath + "/irma_configuration"),
76
77
	}

78
79
	cm.storagePath = storagePath
	if err = cm.ensureStorageExists(); err != nil {
80
		return nil, err
81
	}
82

83
	if err = cm.Store.ParseFolder(); err != nil {
84
		return nil, err
85
	}
86

87
	if cm.secretkey, err = cm.loadSecretKey(); err != nil {
88
		return nil, err
89
	}
90
	if cm.attributes, err = cm.loadAttributes(); err != nil {
91
		return nil, err
92
	}
93
	if cm.paillierKeyCache, err = cm.loadPaillierKeys(); err != nil {
94
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
95
	}
96
	if cm.keyshareServers, err = cm.loadKeyshareServers(); err != nil {
97
		return nil, err
98
	}
Sietse Ringers's avatar
Sietse Ringers committed
99
100
101

	unenrolled := cm.unenrolledKeyshareServers()
	switch len(unenrolled) {
102
	case 0: // nop
Sietse Ringers's avatar
Sietse Ringers committed
103
	case 1:
Sietse Ringers's avatar
Sietse Ringers committed
104
		if keyshareHandler == nil {
105
			return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
Sietse Ringers's avatar
Sietse Ringers committed
106
		}
107
		keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) {
Sietse Ringers's avatar
Sietse Ringers committed
108
109
			cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
		})
Sietse Ringers's avatar
Sietse Ringers committed
110
	default:
111
		return nil, errors.New("Too many keyshare servers")
Sietse Ringers's avatar
Sietse Ringers committed
112
113
	}

114
	return cm, nil
115
116
}

117
118
119
120
func (cm *CredentialManager) path(file string) string {
	return cm.storagePath + "/" + file
}

Sietse Ringers's avatar
Sietse Ringers committed
121
122
123
124
125
126
127
128
129
func (cm *CredentialManager) signatureFilename(id string, counter int) string {
	return cm.path(signaturesDir) + "/" + id + "-" + strconv.Itoa(counter)
}

// 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.
130
func (cm *CredentialManager) ensureStorageExists() error {
131
	exist, err := PathExists(cm.storagePath)
132
	if err != nil {
133
		return err
134
135
136
137
138
	}
	if !exist {
		return errors.New("credential storage path does not exist")
	}

139
140
141
142
143
144
145
146
147
148
149
150
	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
		}
	}
151
	return ensureDirectoryExists(cm.path(signaturesDir))
152
153
}

Sietse Ringers's avatar
Sietse Ringers committed
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
func (cm *CredentialManager) storeSecretKey(sk *big.Int) error {
	return ioutil.WriteFile(cm.path(skFile), sk.Bytes(), 0600)
}

// 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)
180
181
}

Sietse Ringers's avatar
Sietse Ringers committed
182
func (cm *CredentialManager) storeSignature(cred *credential, counter int) (err error) {
183
184
185
186
187
188
189
190
191
192
	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
193
	filename := cm.signatureFilename(cred.CredentialType().Identifier().String(), counter)
194
195
196
197
198
	err = ioutil.WriteFile(filename, credbytes, 0600)
	return
}

func (cm *CredentialManager) storeAttributes() (err error) {
199
	attrbytes, err := json.Marshal(cm.attributes)
200
	if err != nil {
201
		return err
202
203
204
205
206
207
	}

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

209
func (cm *CredentialManager) storeKeyshareServers() (err error) {
210
	bts, err := json.Marshal(cm.keyshareServers)
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
	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
}

227
func (cm *CredentialManager) loadSignature(id CredentialTypeIdentifier, counter int) (signature *gabi.CLSignature, err error) {
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
228
229
	sigpath := cm.signatureFilename(id.String(), counter)
	exists, err := PathExists(sigpath)
230
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
231
232
		return
	}
233
234
235
	if !exists {
		return nil, errors.New("Signature file not found")
	}
Sietse Ringers's avatar
Cleanup    
Sietse Ringers committed
236
	bytes, err := ioutil.ReadFile(sigpath)
237
238
239
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
240
241
242
243
244
245
246
247
	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.
func (cm *CredentialManager) loadSecretKey() (*big.Int, error) {
248
	exists, err := PathExists(cm.path(skFile))
Sietse Ringers's avatar
Sietse Ringers committed
249
250
251
252
	if err != nil {
		return nil, err
	}
	if exists {
253
254
255
		var bytes []byte
		if bytes, err = ioutil.ReadFile(cm.path(skFile)); err == nil {
			return new(big.Int).SetBytes(bytes), nil
Sietse Ringers's avatar
Sietse Ringers committed
256
		}
257
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
258
	}
259
260
261
262
263
264
265
266
267
268

	sk, err := cm.generateSecretKey()
	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
269
270
}

271
272
func (cm *CredentialManager) loadAttributes() (list map[CredentialTypeIdentifier][]*AttributeList, err error) {
	list = make(map[CredentialTypeIdentifier][]*AttributeList)
273
	exists, err := PathExists(cm.path(attributesFile))
Sietse Ringers's avatar
Sietse Ringers committed
274
275
276
277
278
279
280
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(cm.path(attributesFile))
	if err != nil {
		return nil, err
	}
281
	err = json.Unmarshal(bytes, &list)
282
283
284
	if err != nil {
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
285

286
287
	for _, attrlistlist := range list {
		for _, attrlist := range attrlistlist {
288
			attrlist.MetadataAttribute = MetadataFromInt(attrlist.Ints[0], cm.Store)
289
290
291
		}
	}

292
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
293
}
294
295
296
297
298
299
300
301
302
303
304

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
	}
305
	err = json.Unmarshal(bytes, &ksses)
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
	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
}