updates.go 7.06 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
	// 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
36
37

	// Adding scheme manager index, signature and public key
	// 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

	// 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

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

63
64
	// For each keyshare server, include in its struct the identifier of its scheme manager
	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
76
	// Remove the test scheme manager which was erroneously included in a production build
	nil, // No longer necessary, also broke many unit tests
Tomas's avatar
Tomas committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

	// Guess and include version protocol in issuance logs
	func(client *Client) (err error) {
		logs, err := client.Logs()
		if err != nil {
			return
		}
		for _, log := range logs {
			if log.Type != irma.ActionIssuing {
				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
			jwt, err = log.Jwt()
			if err != nil {
				return
			}
			for _, attr := range jwt.IrmaSession().(*irma.IssuanceRequest).Credentials[0].Attributes {
				if regexp.MustCompile("^\\w").Match([]byte(attr)) {
					log.Version = irma.NewVersion(2, 2)
				} else {
					log.Version = irma.NewVersion(2, 3)
				}
				break
			}
		}
		return client.storage.StoreLogs(logs)
	},
109
}
110

111
// update performs any function from clientUpdates that has not
112
113
// already been executed in the past, keeping track of previously executed updates
// in the file at updatesFile.
114
func (client *Client) update() error {
115
116
	// Load and parse file containing info about already performed updates
	var err error
117
	if client.updates, err = client.storage.LoadUpdates(); err != nil {
118
119
120
121
		return err
	}

	// Perform all new updates
122
	for i := len(client.updates); i < len(clientUpdates); i++ {
123
124
125
		err = nil
		if clientUpdates[i] != nil {
			err = clientUpdates[i](client)
126
		}
Sietse Ringers's avatar
Sietse Ringers committed
127
		u := update{
128
			When:    irma.Timestamp(time.Now()),
129
130
131
132
133
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
Sietse Ringers's avatar
Sietse Ringers committed
134
			u.Error = &str
135
		}
136
		client.updates = append(client.updates, u)
137
138
139
		if err != nil {
			break
		}
140
141
	}

142
143
144
145
146
	storeErr := client.storage.StoreUpdates(client.updates)
	if storeErr != nil {
		return storeErr
	}
	return err
147
148
}

Sietse Ringers's avatar
Sietse Ringers committed
149
150
151
152
// 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.
153
154
func (client *Client) ParseAndroidStorage() (present bool, err error) {
	if client.androidStoragePath == "" {
155
156
157
		return false, nil
	}

158
	cardemuXML := client.androidStoragePath + "/shared_prefs/cardemu.xml"
159
	present, err = fs.PathExists(cardemuXML)
160
	if err != nil || !present {
Sietse Ringers's avatar
Sietse Ringers committed
161
162
		return
	}
163
	present = true
Sietse Ringers's avatar
Sietse Ringers committed
164

165
	bytes, err := ioutil.ReadFile(cardemuXML)
Sietse Ringers's avatar
Sietse Ringers committed
166
167
168
169
170
171
172
173
174
	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
175
176
177
	if err = xml.Unmarshal(bytes, &parsedxml); err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
178
179
180
181
182
183
184

	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"`
	})
185
	client.keyshareServers = make(map[irma.SchemeManagerIdentifier]*keyshareServer)
Sietse Ringers's avatar
Sietse Ringers committed
186
187
188
189
190
191
192
193
194
	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)
195
			if err = json.Unmarshal([]byte(jsontag), &client.keyshareServers); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
196
197
198
199
200
201
202
203
204
				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
			}
205
			client.paillierKeyCache = keys[0]
Sietse Ringers's avatar
Sietse Ringers committed
206
207
208
209
		}
	}

	for _, list := range parsedjson {
210
		client.secretkey = &secretKey{Key: list[0].Attributes[0]}
Sietse Ringers's avatar
Sietse Ringers committed
211
212
213
214
215
216
217
218
		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]
			}
219
			var cred *credential
220
			if cred, err = newCredential(gabicred, client.Configuration); err != nil {
221
				return
222
			}
Sietse Ringers's avatar
Sietse Ringers committed
223
			if cred.CredentialType() == nil {
224
225
				err = errors.New("cannot add unknown credential type")
				return
Sietse Ringers's avatar
Sietse Ringers committed
226
227
			}

228
			if err = client.addCredential(cred, false); err != nil {
229
				return
Sietse Ringers's avatar
Sietse Ringers committed
230
231
232
233
			}
		}
	}

234
	if len(client.credentialsCache) > 0 {
235
		if err = client.storage.StoreAttributes(client.attributes); err != nil {
236
			return
Sietse Ringers's avatar
Sietse Ringers committed
237
		}
238
		if err = client.storage.StoreSecretKey(client.secretkey); err != nil {
239
			return
Sietse Ringers's avatar
Sietse Ringers committed
240
241
242
		}
	}

243
244
	if len(client.keyshareServers) > 0 {
		if err = client.storage.StoreKeyshareServers(client.keyshareServers); err != nil {
245
			return
Sietse Ringers's avatar
Sietse Ringers committed
246
247
248
		}
	}

249
	if err = client.storage.StorePaillierKeys(client.paillierKeyCache); err != nil {
250
		return
Sietse Ringers's avatar
Sietse Ringers committed
251
	}
252
253
	if client.paillierKeyCache == nil {
		client.paillierKey(false) // trigger calculating a new one
Sietse Ringers's avatar
Sietse Ringers committed
254
	}
255
	return
Sietse Ringers's avatar
Sietse Ringers committed
256
}