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

import (
	"encoding/json"
	"io/ioutil"
	"os"
7
	"path/filepath"
8

9
	"github.com/privacybydesign/gabi"
10
11
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
12
13
)

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

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

23
24
// Filenames in which we store stuff
const (
25
26
27
28
29
30
31
	skFile          = "sk"
	attributesFile  = "attrs"
	kssFile         = "kss"
	updatesFile     = "updates"
	logsFile        = "logs"
	preferencesFile = "preferences"
	signaturesDir   = "sigs"
32
33
)

34
func (s *storage) path(p string) string {
35
	return filepath.Join(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 filepath.Join(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) StoreLogs(logs []*LogEntry) error {
Sietse Ringers's avatar
Sietse Ringers committed
107
	return s.store(logs, logsFile)
Sietse Ringers's avatar
Sietse Ringers committed
108
109
}

110
111
func (s *storage) StorePreferences(prefs Preferences) error {
	return s.store(prefs, preferencesFile)
112
113
}

Sietse Ringers's avatar
Sietse Ringers committed
114
115
func (s *storage) StoreUpdates(updates []update) (err error) {
	return s.store(updates, updatesFile)
116
117
}

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

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

Sietse Ringers's avatar
Sietse Ringers committed
142
	if sk, err = generateSecretKey(); err != nil {
143
144
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
145
	if err = s.StoreSecretKey(sk); err != nil {
146
147
148
		return nil, err
	}
	return sk, nil
Sietse Ringers's avatar
Sietse Ringers committed
149
150
}

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

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

172
	return list, nil
Sietse Ringers's avatar
Sietse Ringers committed
173
}
174

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

Sietse Ringers's avatar
Sietse Ringers committed
183
func (s *storage) LoadLogs() (logs []*LogEntry, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
184
	logs = []*LogEntry{}
Sietse Ringers's avatar
Sietse Ringers committed
185
	if err := s.load(&logs, logsFile); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
186
187
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
188
	return logs, nil
Sietse Ringers's avatar
Sietse Ringers committed
189
}
190

Sietse Ringers's avatar
Sietse Ringers committed
191
func (s *storage) LoadUpdates() (updates []update, err error) {
192
	updates = []update{}
Sietse Ringers's avatar
Sietse Ringers committed
193
	if err := s.load(&updates, updatesFile); err != nil {
194
195
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
196
	return updates, nil
197
}
198

199
200
func (s *storage) LoadPreferences() (Preferences, error) {
	config := defaultPreferences
201
	return config, s.load(&config, preferencesFile)
202
}