storage.go 7.75 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
	signaturesDir  = "sigs"
Sietse Ringers's avatar
Sietse Ringers committed
26
	cardemuXML     = "../cardemu.xml"
27
28
)

29
30
31
32
33
34
type update struct {
	when   Timestamp
	number int
	info   string
}

Sietse Ringers's avatar
Sietse Ringers committed
35
// PathExists checks if the specified path exists.
36
func PathExists(path string) (bool, error) {
37
38
39
40
41
42
43
44
45
46
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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()
}

71
72
// NewCredentialManager deserializes the credentials from storage.
func NewCredentialManager(
73
74
75
	storagePath string,
	irmaConfigurationPath string,
	keyshareHandler KeyshareHandler,
76
77
78
79
80
81
82
) (*CredentialManager, error) {
	var err error
	cm := &CredentialManager{
		credentials:     make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers: make(map[SchemeManagerIdentifier]*keyshareServer),
	}

83
84
85
	cm.Store = NewConfigurationStore(storagePath)
	cm.Store.Copy(irmaConfigurationPath)
	if err = cm.Store.ParseFolder(); err != nil {
86
		return nil, err
87
	}
88

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

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

120
	return cm, nil
121
122
}

123
124
125
126
func (cm *CredentialManager) path(file string) string {
	return cm.storagePath + "/" + file
}

Sietse Ringers's avatar
Sietse Ringers committed
127
128
129
130
131
132
133
134
135
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.
136
func (cm *CredentialManager) ensureStorageExists() (err error) {
137
	exist, err := PathExists(cm.storagePath)
138
139
140
141
142
143
144
	if err != nil {
		return
	}
	if !exist {
		return errors.New("credential storage path does not exist")
	}

145
	return ensureDirectoryExists(cm.path(signaturesDir))
146
147
}

Sietse Ringers's avatar
Sietse Ringers committed
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
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)
174
175
}

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

func (cm *CredentialManager) storeAttributes() (err error) {
193
	attrbytes, err := json.Marshal(cm.attributes)
194
	if err != nil {
195
		return err
196
197
198
199
200
201
	}

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

203
func (cm *CredentialManager) storeKeyshareServers() (err error) {
204
	bts, err := json.Marshal(cm.keyshareServers)
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
	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
}

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

	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
263
264
}

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

280
281
	for _, attrlistlist := range list {
		for _, attrlist := range attrlistlist {
282
			attrlist.MetadataAttribute = MetadataFromInt(attrlist.Ints[0], cm.Store)
283
284
285
		}
	}

286
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
287
}
288
289
290
291
292
293
294
295
296
297
298

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
	}
299
	err = json.Unmarshal(bytes, &ksses)
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
	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
}