updates.go 8.41 KB
Newer Older
1
package irmaclient
Sietse Ringers's avatar
Sietse Ringers committed
2
3
4
5
6
7
8

import (
	"encoding/json"
	"encoding/xml"
	"html"
	"io/ioutil"
	"math/big"
Tomas's avatar
Tomas committed
9
	"regexp"
10
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
11

Sietse Ringers's avatar
Sietse Ringers committed
12
	"github.com/go-errors/errors"
Sietse Ringers's avatar
Sietse Ringers committed
13
	"github.com/mhe/gabi"
14
15
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
Sietse Ringers's avatar
Sietse Ringers committed
16
17
)

18
// This file contains the update mechanism for Client
Sietse Ringers's avatar
Sietse Ringers committed
19
20
// as well as updates themselves.

21
type update struct {
22
	When    irma.Timestamp
23
24
25
	Number  int
	Success bool
	Error   *string
26
27
}

28
var clientUpdates = []func(client *Client) error{
29
	// 0: Convert old cardemu.xml Android storage to our own storage format
30
31
	func(client *Client) error {
		_, err := client.ParseAndroidStorage()
32
33
		return err
	},
34

35
	// 1: Adding scheme manager index, signature and public key
36
37
	// Check the signatures of all scheme managers, if any is not ok,
	// copy the entire irma_configuration folder from assets
38
	nil, // made irrelevant by irma_configuration-autocopying
39

40
	// 2: Rename config -> preferences
41
42
43
44
45
	func(client *Client) (err error) {
		exists, err := fs.PathExists(client.storage.path("config"))
		if !exists || err != nil {
			return
		}
46
47
48
49
		oldStruct := &struct {
			SendCrashReports bool
		}{}
		// Load old file, convert to new struct, and save
50
		err = client.storage.load(oldStruct, "config")
51
52
53
54
55
56
57
58
		if err != nil {
			return err
		}
		client.Preferences = Preferences{
			EnableCrashReporting: oldStruct.SendCrashReports,
		}
		return client.storage.StorePreferences(client.Preferences)
	},
59

60
	// 3: Copy new irma_configuration out of assets
61
	nil, // made irrelevant by irma_configuration-autocopying
62

63
	// 4: For each keyshare server, include in its struct the identifier of its scheme manager
64
	func(client *Client) (err error) {
65
66
67
68
69
		keyshareServers, err := client.storage.LoadKeyshareServers()
		if err != nil {
			return err
		}
		for smi, kss := range keyshareServers {
70
71
			kss.SchemeManagerIdentifier = smi
		}
72
		return client.storage.StoreKeyshareServers(keyshareServers)
73
	},
74

75
	// 5: Remove the test scheme manager which was erroneously included in a production build
76
	nil, // No longer necessary, also broke many unit tests
Tomas's avatar
Tomas committed
77

78
79
	// 6: Guess and include version protocol in issuance logs, and convert log entry structure
	// from Response to either IssueCommitment or ProofList
Tomas's avatar
Tomas committed
80
81
82
83
84
	func(client *Client) (err error) {
		logs, err := client.Logs()
		if err != nil {
			return
		}
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
		// The logs read above do not contain the Response field as it has been removed from the LogEntry struct.
		// So read the logs again into a slice of a temp struct that does contain this field.
		type oldLogEntry struct {
			Response json.RawMessage
		}
		var oldLogs []*oldLogEntry
		if err = client.storage.load(&oldLogs, logsFile); err != nil {
			return
		}
		// Sanity check, this should be true as both log slices were read from the same source
		if len(oldLogs) != len(logs) {
			return errors.New("Log count does not match")
		}

		for i, entry := range logs {
			oldEntry := oldLogs[i]

			if len(oldEntry.Response) == 0 {
				return errors.New("Log entry had no Response field")
			}

			switch entry.Type {
			case actionRemoval: // nop
			case irma.ActionSigning:
				fallthrough
			case irma.ActionDisclosing:
				proofs := []*gabi.ProofD{}
				if err = json.Unmarshal(oldEntry.Response, &proofs); err != nil {
					return
				}
				for _, proof := range proofs {
					entry.ProofList = append(entry.ProofList, proof)
				}
			case irma.ActionIssuing:
				entry.IssueCommitment = &gabi.IssueCommitmentMessage{}
				if err = json.Unmarshal(oldEntry.Response, entry.IssueCommitment); err != nil {
					return err
				}
			default:
				return errors.New("Invalid log type")
			}

			if entry.Type != irma.ActionIssuing {
Tomas's avatar
Tomas committed
128
129
130
131
132
133
134
135
				continue
			}
			// Ugly hack alert: unfortunately the protocol version that was used in the session is nowhere recorded.
			// This means that we cannot be sure whether or not we should byteshift the presence bit out of the attributes
			// that was introduced in version 2.3 of the protocol. The only thing that I can think of to determine this
			// is to check if the attributes are human-readable, i.e., alphanumeric: if the presence bit is present and
			// we do not shift it away, then they almost certainly will not be.
			var jwt irma.RequestorJwt
136
			jwt, err = entry.Jwt()
Tomas's avatar
Tomas committed
137
138
139
140
141
			if err != nil {
				return
			}
			for _, attr := range jwt.IrmaSession().(*irma.IssuanceRequest).Credentials[0].Attributes {
				if regexp.MustCompile("^\\w").Match([]byte(attr)) {
142
					entry.Version = irma.NewVersion(2, 2)
Tomas's avatar
Tomas committed
143
				} else {
144
					entry.Version = irma.NewVersion(2, 3)
Tomas's avatar
Tomas committed
145
146
147
148
149
150
				}
				break
			}
		}
		return client.storage.StoreLogs(logs)
	},
151
}
152

153
// update performs any function from clientUpdates that has not
154
155
// already been executed in the past, keeping track of previously executed updates
// in the file at updatesFile.
156
func (client *Client) update() error {
157
158
	// Load and parse file containing info about already performed updates
	var err error
159
	if client.updates, err = client.storage.LoadUpdates(); err != nil {
160
161
162
163
		return err
	}

	// Perform all new updates
164
	for i := len(client.updates); i < len(clientUpdates); i++ {
165
166
167
		err = nil
		if clientUpdates[i] != nil {
			err = clientUpdates[i](client)
168
		}
Sietse Ringers's avatar
Sietse Ringers committed
169
		u := update{
170
			When:    irma.Timestamp(time.Now()),
171
172
173
174
175
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
Sietse Ringers's avatar
Sietse Ringers committed
176
			u.Error = &str
177
		}
178
		client.updates = append(client.updates, u)
179
180
181
		if err != nil {
			break
		}
182
183
	}

184
185
186
187
188
	storeErr := client.storage.StoreUpdates(client.updates)
	if storeErr != nil {
		return storeErr
	}
	return err
189
190
}

Sietse Ringers's avatar
Sietse Ringers committed
191
192
193
194
// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
// from the old Android IRMA app, parsing its credentials into the current instance,
// and saving them to storage.
// CAREFUL: this method overwrites any existing secret keys and attributes on storage.
195
196
func (client *Client) ParseAndroidStorage() (present bool, err error) {
	if client.androidStoragePath == "" {
197
198
199
		return false, nil
	}

200
	cardemuXML := client.androidStoragePath + "/shared_prefs/cardemu.xml"
201
	present, err = fs.PathExists(cardemuXML)
202
	if err != nil || !present {
Sietse Ringers's avatar
Sietse Ringers committed
203
204
		return
	}
205
	present = true
Sietse Ringers's avatar
Sietse Ringers committed
206

207
	bytes, err := ioutil.ReadFile(cardemuXML)
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
211
212
213
214
215
216
	if err != nil {
		return
	}
	parsedxml := struct {
		Strings []struct {
			Name    string `xml:"name,attr"`
			Content string `xml:",chardata"`
		} `xml:"string"`
	}{}
Sietse Ringers's avatar
Sietse Ringers committed
217
218
219
	if err = xml.Unmarshal(bytes, &parsedxml); err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
220
221
222
223
224
225
226

	parsedjson := make(map[string][]*struct {
		Signature    *gabi.CLSignature `json:"signature"`
		Pk           *gabi.PublicKey   `json:"-"`
		Attributes   []*big.Int        `json:"attributes"`
		SharedPoints []*big.Int        `json:"public_sks"`
	})
227
	client.keyshareServers = make(map[irma.SchemeManagerIdentifier]*keyshareServer)
Sietse Ringers's avatar
Sietse Ringers committed
228
229
230
231
232
233
234
235
236
	for _, xmltag := range parsedxml.Strings {
		if xmltag.Name == "credentials" {
			jsontag := html.UnescapeString(xmltag.Content)
			if err = json.Unmarshal([]byte(jsontag), &parsedjson); err != nil {
				return
			}
		}
		if xmltag.Name == "keyshare" {
			jsontag := html.UnescapeString(xmltag.Content)
237
			if err = json.Unmarshal([]byte(jsontag), &client.keyshareServers); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
238
239
240
241
242
243
244
245
246
				return
			}
		}
		if xmltag.Name == "KeyshareKeypairs" {
			jsontag := html.UnescapeString(xmltag.Content)
			keys := make([]*paillierPrivateKey, 0, 3)
			if err = json.Unmarshal([]byte(jsontag), &keys); err != nil {
				return
			}
247
			client.paillierKeyCache = keys[0]
Sietse Ringers's avatar
Sietse Ringers committed
248
249
250
251
		}
	}

	for _, list := range parsedjson {
252
		client.secretkey = &secretKey{Key: list[0].Attributes[0]}
Sietse Ringers's avatar
Sietse Ringers committed
253
254
255
256
257
258
259
260
		for _, oldcred := range list {
			gabicred := &gabi.Credential{
				Attributes: oldcred.Attributes,
				Signature:  oldcred.Signature,
			}
			if oldcred.SharedPoints != nil && len(oldcred.SharedPoints) > 0 {
				gabicred.Signature.KeyshareP = oldcred.SharedPoints[0]
			}
261
			var cred *credential
262
			if cred, err = newCredential(gabicred, client.Configuration); err != nil {
263
				return
264
			}
Sietse Ringers's avatar
Sietse Ringers committed
265
			if cred.CredentialType() == nil {
266
267
				err = errors.New("cannot add unknown credential type")
				return
Sietse Ringers's avatar
Sietse Ringers committed
268
269
			}

270
			if err = client.addCredential(cred, false); err != nil {
271
				return
Sietse Ringers's avatar
Sietse Ringers committed
272
273
274
275
			}
		}
	}

276
	if len(client.credentialsCache) > 0 {
277
		if err = client.storage.StoreAttributes(client.attributes); err != nil {
278
			return
Sietse Ringers's avatar
Sietse Ringers committed
279
		}
280
		if err = client.storage.StoreSecretKey(client.secretkey); err != nil {
281
			return
Sietse Ringers's avatar
Sietse Ringers committed
282
283
284
		}
	}

285
286
	if len(client.keyshareServers) > 0 {
		if err = client.storage.StoreKeyshareServers(client.keyshareServers); err != nil {
287
			return
Sietse Ringers's avatar
Sietse Ringers committed
288
289
290
		}
	}

291
	if err = client.storage.StorePaillierKeys(client.paillierKeyCache); err != nil {
292
		return
Sietse Ringers's avatar
Sietse Ringers committed
293
	}
294
295
	if client.paillierKeyCache == nil {
		client.paillierKey(false) // trigger calculating a new one
Sietse Ringers's avatar
Sietse Ringers committed
296
	}
297
	return
Sietse Ringers's avatar
Sietse Ringers committed
298
}