storage.go 7.66 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
)

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
// NewCredentialManager deserializes the credentials from storage.
func NewCredentialManager(
67
68
69
	storagePath string,
	irmaConfigurationPath string,
	keyshareHandler KeyshareHandler,
70
71
72
73
74
75
76
) (*CredentialManager, error) {
	var err error
	cm := &CredentialManager{
		credentials:     make(map[CredentialTypeIdentifier]map[int]*credential),
		keyshareServers: make(map[SchemeManagerIdentifier]*keyshareServer),
	}

77
78
79
	cm.Store = NewConfigurationStore(storagePath)
	cm.Store.Copy(irmaConfigurationPath)
	if err = cm.Store.ParseFolder(); err != nil {
80
		return nil, err
81
	}
82

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

139
	return ensureDirectoryExists(cm.path(signaturesDir))
140
141
}

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

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

func (cm *CredentialManager) storeAttributes() (err error) {
187
	attrbytes, err := json.Marshal(cm.attributes)
188
	if err != nil {
189
		return err
190
191
192
193
194
195
	}

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

197
func (cm *CredentialManager) storeKeyshareServers() (err error) {
198
	bts, err := json.Marshal(cm.keyshareServers)
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
	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
}

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

	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
257
258
}

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

274
275
276
277
278
279
	for _, attrlistlist := range list {
		for _, attrlist := range attrlistlist {
			attrlist.MetadataAttribute.store = cm.Store
		}
	}

280
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
281
}
282
283
284
285
286
287
288
289
290
291
292

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
	}
293
	err = json.Unmarshal(bytes, &ksses)
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
	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
}