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

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

8
	"path/filepath"
9
	"time"
10

11
	"github.com/privacybydesign/gabi"
12
13
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
14
15
	"github.com/timshannon/bolthold"
	"go.etcd.io/bbolt"
16
17
)

Sietse Ringers's avatar
Sietse Ringers committed
18
19
20
// This file contains the storage struct and its methods,
// and some general filesystem functions.

21
// Storage provider for a Client
22
type storage struct {
23
	storagePath   string
24
	db            *bolthold.Store
25
	Configuration *irma.Configuration
26
27
}

28
29
// Filenames in which we store stuff
const (
30
31
32
33
34
35
36
	skFile          = "sk"
	attributesFile  = "attrs"
	kssFile         = "kss"
	updatesFile     = "updates"
	logsFile        = "logs"
	preferencesFile = "preferences"
	signaturesDir   = "sigs"
37
38

	databaseFile = "db"
39
40
)

41
func (s *storage) path(p string) string {
42
	return filepath.Join(s.storagePath, p)
43
44
}

Sietse Ringers's avatar
Sietse Ringers committed
45
// EnsureStorageExists initializes the credential storage folder,
Sietse Ringers's avatar
Sietse Ringers committed
46
47
48
49
// 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
50
func (s *storage) EnsureStorageExists() error {
51
52
	var err error
	if err = fs.AssertPathExists(s.storagePath); err != nil {
53
54
		return err
	}
55
56
57
58
59
60
61
62
63
	if err = fs.EnsureDirectoryExists(s.path(signaturesDir)); err != nil {
		return err
	}
	s.db, err = bolthold.Open(s.path(databaseFile), 0600, &bolthold.Options{
		Options: &bbolt.Options{Timeout: 1 * time.Second},
		Encoder: json.Marshal,
		Decoder: json.Unmarshal,
	})
	return err
64
65
}

Sietse Ringers's avatar
Sietse Ringers committed
66
func (s *storage) load(dest interface{}, path string) (err error) {
67
	exists, err := fs.PathExists(s.path(path))
Sietse Ringers's avatar
Sietse Ringers committed
68
69
70
71
	if err != nil || !exists {
		return
	}
	bytes, err := ioutil.ReadFile(s.path(path))
72
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
73
		return
74
	}
Sietse Ringers's avatar
Sietse Ringers committed
75
	return json.Unmarshal(bytes, dest)
76
77
}

Sietse Ringers's avatar
Sietse Ringers committed
78
79
func (s *storage) store(contents interface{}, file string) error {
	bts, err := json.Marshal(contents)
80
81
82
	if err != nil {
		return err
	}
83
	return fs.SaveFile(s.path(file), bts)
Sietse Ringers's avatar
Sietse Ringers committed
84
85
}

86
func (s *storage) signatureFilename(attrs *irma.AttributeList) string {
Sietse Ringers's avatar
Sietse Ringers committed
87
88
89
90
91
	// 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.
92
	return filepath.Join(signaturesDir, attrs.Hash())
Sietse Ringers's avatar
Sietse Ringers committed
93
94
}

95
func (s *storage) DeleteSignature(attrs *irma.AttributeList) error {
Sietse Ringers's avatar
Sietse Ringers committed
96
97
	return os.Remove(s.path(s.signatureFilename(attrs)))
}
98

Sietse Ringers's avatar
Sietse Ringers committed
99
100
func (s *storage) StoreSignature(cred *credential) error {
	return s.store(cred.Signature, s.signatureFilename(cred.AttributeList()))
101
102
}

Sietse Ringers's avatar
Sietse Ringers committed
103
104
105
106
func (s *storage) StoreSecretKey(sk *secretKey) error {
	return s.store(sk, skFile)
}

107
108
func (s *storage) StoreAttributes(attributes map[irma.CredentialTypeIdentifier][]*irma.AttributeList) error {
	temp := []*irma.AttributeList{}
109
	for _, attrlistlist := range attributes {
110
111
112
		for _, attrlist := range attrlistlist {
			temp = append(temp, attrlist)
		}
113
114
	}

Sietse Ringers's avatar
Sietse Ringers committed
115
	return s.store(temp, attributesFile)
116
}
Sietse Ringers's avatar
Sietse Ringers committed
117

118
func (s *storage) StoreKeyshareServers(keyshareServers map[irma.SchemeManagerIdentifier]*keyshareServer) error {
Sietse Ringers's avatar
Sietse Ringers committed
119
	return s.store(keyshareServers, kssFile)
120
121
}

122
func (s *storage) StoreLogs(logs []*LogEntry) error {
Sietse Ringers's avatar
Sietse Ringers committed
123
	return s.store(logs, logsFile)
Sietse Ringers's avatar
Sietse Ringers committed
124
125
}

126
127
128
129
130
131
132
133
func (s *storage) AddLogEntry(entry *LogEntry) error {
	return s.db.Upsert(s.logEntryKey(entry), entry)
}

func (s *storage) logEntryKey(entry *LogEntry) interface{} {
	return time.Time(entry.Time).UnixNano()
}

134
135
func (s *storage) StorePreferences(prefs Preferences) error {
	return s.store(prefs, preferencesFile)
136
137
}

Sietse Ringers's avatar
Sietse Ringers committed
138
139
func (s *storage) StoreUpdates(updates []update) (err error) {
	return s.store(updates, updatesFile)
140
141
}

142
func (s *storage) LoadSignature(attrs *irma.AttributeList) (signature *gabi.CLSignature, err error) {
143
	sigpath := s.signatureFilename(attrs)
144
	if err := fs.AssertPathExists(s.path(sigpath)); err != nil {
145
		return nil, err
146
	}
Sietse Ringers's avatar
Sietse Ringers committed
147
	signature = new(gabi.CLSignature)
Sietse Ringers's avatar
Sietse Ringers committed
148
149
150
151
	if err := s.load(signature, sigpath); err != nil {
		return nil, err
	}
	return signature, nil
Sietse Ringers's avatar
Sietse Ringers committed
152
153
}

Sietse Ringers's avatar
Sietse Ringers committed
154
// LoadSecretKey retrieves and returns the secret key from storage, or if no secret key
Sietse Ringers's avatar
Sietse Ringers committed
155
// was found in storage, it generates, saves, and returns a new secret key.
Sietse Ringers's avatar
Sietse Ringers committed
156
func (s *storage) LoadSecretKey() (*secretKey, error) {
157
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
158
159
	sk := &secretKey{}
	if err = s.load(sk, skFile); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
160
161
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
162
163
	if sk.Key != nil {
		return sk, nil
Sietse Ringers's avatar
Sietse Ringers committed
164
	}
165

Sietse Ringers's avatar
Sietse Ringers committed
166
	if sk, err = generateSecretKey(); err != nil {
167
168
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
169
	if err = s.StoreSecretKey(sk); err != nil {
170
171
172
		return nil, err
	}
	return sk, nil
Sietse Ringers's avatar
Sietse Ringers committed
173
174
}

175
func (s *storage) LoadAttributes() (list map[irma.CredentialTypeIdentifier][]*irma.AttributeList, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
176
	// The attributes are stored as a list of instances of AttributeList
177
	temp := []*irma.AttributeList{}
Sietse Ringers's avatar
Sietse Ringers committed
178
	if err = s.load(&temp, attributesFile); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
179
180
		return
	}
181

182
	list = make(map[irma.CredentialTypeIdentifier][]*irma.AttributeList)
183
	for _, attrlist := range temp {
184
		attrlist.MetadataAttribute = irma.MetadataFromInt(attrlist.Ints[0], s.Configuration)
185
		id := attrlist.CredentialType()
186
		var ct irma.CredentialTypeIdentifier
187
188
189
190
		if id != nil {
			ct = id.Identifier()
		}
		if _, contains := list[ct]; !contains {
191
			list[ct] = []*irma.AttributeList{}
192
		}
193
		list[ct] = append(list[ct], attrlist)
194
195
	}

196
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
197
}
198

199
200
func (s *storage) LoadKeyshareServers() (ksses map[irma.SchemeManagerIdentifier]*keyshareServer, err error) {
	ksses = make(map[irma.SchemeManagerIdentifier]*keyshareServer)
Sietse Ringers's avatar
Sietse Ringers committed
201
	if err := s.load(&ksses, kssFile); err != nil {
202
203
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
204
	return ksses, nil
205
206
}

207
208
209
210
211
func (s *storage) LoadLogs(before time.Time, max int) ([]*LogEntry, error) {
	var logs []*LogEntry
	return logs, s.db.Find(&logs,
		bolthold.Where(bolthold.Key).Lt(before.UnixNano()).Limit(max),
	)
Sietse Ringers's avatar
Sietse Ringers committed
212
}
213

Sietse Ringers's avatar
Sietse Ringers committed
214
func (s *storage) LoadUpdates() (updates []update, err error) {
215
	updates = []update{}
Sietse Ringers's avatar
Sietse Ringers committed
216
	if err := s.load(&updates, updatesFile); err != nil {
217
218
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
219
	return updates, nil
220
}
221

222
223
func (s *storage) LoadPreferences() (Preferences, error) {
	config := defaultPreferences
224
	return config, s.load(&config, preferencesFile)
225
}