storage.go 5.88 KB
Newer Older
1
package irmaclient
2
3
4
5
6

import (
	"encoding/json"
	"io/ioutil"
	"os"
7

8
9
	"github.com/credentials/irmago"
	"github.com/credentials/irmago/internal/fs"
10
	"github.com/mhe/gabi"
11
12
)

Sietse Ringers's avatar
Sietse Ringers committed
13
14
15
// This file contains the storage struct and its methods,
// and some general filesystem functions.

16
// Storage provider for a Client
17
type storage struct {
18
19
	storagePath   string
	Configuration *irma.Configuration
20
21
}

22
23
24
25
// Filenames in which we store stuff
const (
	skFile         = "sk"
	attributesFile = "attrs"
26
27
	kssFile        = "kss"
	paillierFile   = "paillier"
28
	updatesFile    = "updates"
Sietse Ringers's avatar
Sietse Ringers committed
29
	logsFile       = "logs"
30
	configFile     = "config"
31
32
33
	signaturesDir  = "sigs"
)

34
35
func (s *storage) path(p string) string {
	return s.storagePath + "/" + p
36
37
}

Sietse Ringers's avatar
Sietse Ringers committed
38
// EnsureStorageExists initializes the credential storage folder,
Sietse Ringers's avatar
Sietse Ringers committed
39
40
41
42
// 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.
Sietse Ringers's avatar
Sietse Ringers committed
43
func (s *storage) EnsureStorageExists() error {
44
	if err := fs.AssertPathExists(s.storagePath); err != nil {
45
46
		return err
	}
47
	return fs.EnsureDirectoryExists(s.path(signaturesDir))
48
49
}

Sietse Ringers's avatar
Sietse Ringers committed
50
func (s *storage) load(dest interface{}, path string) (err error) {
51
	exists, err := fs.PathExists(s.path(path))
Sietse Ringers's avatar
Sietse Ringers committed
52
53
54
55
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(s.path(path))
56
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
57
		return
58
	}
Sietse Ringers's avatar
Sietse Ringers committed
59
	return json.Unmarshal(bytes, dest)
60
61
}

Sietse Ringers's avatar
Sietse Ringers committed
62
63
func (s *storage) store(contents interface{}, file string) error {
	bts, err := json.Marshal(contents)
64
65
66
	if err != nil {
		return err
	}
67
	return fs.SaveFile(s.path(file), bts)
Sietse Ringers's avatar
Sietse Ringers committed
68
69
}

70
func (s *storage) signatureFilename(attrs *irma.AttributeList) string {
Sietse Ringers's avatar
Sietse Ringers committed
71
72
73
74
75
	// 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.
76
	return signaturesDir + "/" + attrs.Hash()
Sietse Ringers's avatar
Sietse Ringers committed
77
78
}

79
func (s *storage) DeleteSignature(attrs *irma.AttributeList) error {
Sietse Ringers's avatar
Sietse Ringers committed
80
81
	return os.Remove(s.path(s.signatureFilename(attrs)))
}
82

Sietse Ringers's avatar
Sietse Ringers committed
83
84
func (s *storage) StoreSignature(cred *credential) error {
	return s.store(cred.Signature, s.signatureFilename(cred.AttributeList()))
85
86
}

Sietse Ringers's avatar
Sietse Ringers committed
87
88
89
90
func (s *storage) StoreSecretKey(sk *secretKey) error {
	return s.store(sk, skFile)
}

91
92
func (s *storage) StoreAttributes(attributes map[irma.CredentialTypeIdentifier][]*irma.AttributeList) error {
	temp := []*irma.AttributeList{}
93
	for _, attrlistlist := range attributes {
94
95
96
		for _, attrlist := range attrlistlist {
			temp = append(temp, attrlist)
		}
97
98
	}

Sietse Ringers's avatar
Sietse Ringers committed
99
	return s.store(temp, attributesFile)
100
}
Sietse Ringers's avatar
Sietse Ringers committed
101

102
func (s *storage) StoreKeyshareServers(keyshareServers map[irma.SchemeManagerIdentifier]*keyshareServer) error {
Sietse Ringers's avatar
Sietse Ringers committed
103
	return s.store(keyshareServers, kssFile)
104
105
}

106
func (s *storage) StorePaillierKeys(key *paillierPrivateKey) error {
Sietse Ringers's avatar
Sietse Ringers committed
107
	return s.store(key, paillierFile)
108
109
}

110
func (s *storage) StoreLogs(logs []*LogEntry) error {
Sietse Ringers's avatar
Sietse Ringers committed
111
	return s.store(logs, logsFile)
Sietse Ringers's avatar
Sietse Ringers committed
112
113
}

114
115
116
117
func (s *storage) StoreClientConfig(config clientConfiguration) error {
	return s.store(config, configFile)
}

Sietse Ringers's avatar
Sietse Ringers committed
118
119
func (s *storage) StoreUpdates(updates []update) (err error) {
	return s.store(updates, updatesFile)
120
121
}

122
func (s *storage) LoadSignature(attrs *irma.AttributeList) (signature *gabi.CLSignature, err error) {
123
	sigpath := s.signatureFilename(attrs)
124
	if err := fs.AssertPathExists(s.path(sigpath)); err != nil {
125
		return nil, err
126
	}
Sietse Ringers's avatar
Sietse Ringers committed
127
	signature = new(gabi.CLSignature)
Sietse Ringers's avatar
Sietse Ringers committed
128
129
130
131
	if err := s.load(signature, sigpath); err != nil {
		return nil, err
	}
	return signature, nil
Sietse Ringers's avatar
Sietse Ringers committed
132
133
}

Sietse Ringers's avatar
Sietse Ringers committed
134
// LoadSecretKey retrieves and returns the secret key from storage, or if no secret key
Sietse Ringers's avatar
Sietse Ringers committed
135
// was found in storage, it generates, saves, and returns a new secret key.
Sietse Ringers's avatar
Sietse Ringers committed
136
func (s *storage) LoadSecretKey() (*secretKey, error) {
137
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
138
139
	sk := &secretKey{}
	if err = s.load(sk, skFile); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
140
141
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
142
143
	if sk.Key != nil {
		return sk, nil
Sietse Ringers's avatar
Sietse Ringers committed
144
	}
145

Sietse Ringers's avatar
Sietse Ringers committed
146
	if sk, err = generateSecretKey(); err != nil {
147
148
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
149
	if err = s.StoreSecretKey(sk); err != nil {
150
151
152
		return nil, err
	}
	return sk, nil
Sietse Ringers's avatar
Sietse Ringers committed
153
154
}

155
func (s *storage) LoadAttributes() (list map[irma.CredentialTypeIdentifier][]*irma.AttributeList, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
156
	// The attributes are stored as a list of instances of AttributeList
157
	temp := []*irma.AttributeList{}
Sietse Ringers's avatar
Sietse Ringers committed
158
	if err = s.load(&temp, attributesFile); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
159
160
		return
	}
161

162
	list = make(map[irma.CredentialTypeIdentifier][]*irma.AttributeList)
163
	for _, attrlist := range temp {
164
		attrlist.MetadataAttribute = irma.MetadataFromInt(attrlist.Ints[0], s.Configuration)
165
		id := attrlist.CredentialType()
166
		var ct irma.CredentialTypeIdentifier
167
168
169
170
		if id != nil {
			ct = id.Identifier()
		}
		if _, contains := list[ct]; !contains {
171
			list[ct] = []*irma.AttributeList{}
172
		}
173
		list[ct] = append(list[ct], attrlist)
174
175
	}

176
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
177
}
178

179
180
func (s *storage) LoadKeyshareServers() (ksses map[irma.SchemeManagerIdentifier]*keyshareServer, err error) {
	ksses = make(map[irma.SchemeManagerIdentifier]*keyshareServer)
Sietse Ringers's avatar
Sietse Ringers committed
181
	if err := s.load(&ksses, kssFile); err != nil {
182
183
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
184
	return ksses, nil
185
186
}

Sietse Ringers's avatar
Sietse Ringers committed
187
func (s *storage) LoadPaillierKeys() (key *paillierPrivateKey, err error) {
188
	key = new(paillierPrivateKey)
Sietse Ringers's avatar
Sietse Ringers committed
189
	if err := s.load(key, paillierFile); err != nil {
190
191
		return nil, err
	}
192
193
194
	if key.N == nil { // TODO this is ugly
		return nil, nil
	}
Sietse Ringers's avatar
Sietse Ringers committed
195
	return key, nil
196
}
Sietse Ringers's avatar
Sietse Ringers committed
197

Sietse Ringers's avatar
Sietse Ringers committed
198
func (s *storage) LoadLogs() (logs []*LogEntry, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
199
	logs = []*LogEntry{}
Sietse Ringers's avatar
Sietse Ringers committed
200
	if err := s.load(&logs, logsFile); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
201
202
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
203
	return logs, nil
Sietse Ringers's avatar
Sietse Ringers committed
204
}
205

Sietse Ringers's avatar
Sietse Ringers committed
206
func (s *storage) LoadUpdates() (updates []update, err error) {
207
	updates = []update{}
Sietse Ringers's avatar
Sietse Ringers committed
208
	if err := s.load(&updates, updatesFile); err != nil {
209
210
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
211
	return updates, nil
212
}
213
214
215
216
217

func (s *storage) LoadClientConfig() (clientConfiguration, error) {
	config := defaultClientConfig
	return config, s.load(&config, configFile)
}