updates.go 5.97 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"
9
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
10

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

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

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

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

	// 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
37
	nil, // made irrelevant by irma_configuration-autocopying
38
39

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

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

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

	func(client *Client) (err error) {
		return client.Configuration.DeleteSchemeManager(irma.NewSchemeManagerIdentifier("test"))
	},
77
}
78

79
// update performs any function from clientUpdates that has not
80
81
// already been executed in the past, keeping track of previously executed updates
// in the file at updatesFile.
82
func (client *Client) update() error {
83
84
	// Load and parse file containing info about already performed updates
	var err error
85
	if client.updates, err = client.storage.LoadUpdates(); err != nil {
86
87
88
89
		return err
	}

	// Perform all new updates
90
	for i := len(client.updates); i < len(clientUpdates); i++ {
91
92
93
		err = nil
		if clientUpdates[i] != nil {
			err = clientUpdates[i](client)
94
		}
Sietse Ringers's avatar
Sietse Ringers committed
95
		u := update{
96
			When:    irma.Timestamp(time.Now()),
97
98
99
100
101
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
Sietse Ringers's avatar
Sietse Ringers committed
102
			u.Error = &str
103
		}
104
		client.updates = append(client.updates, u)
105
106
107
		if err != nil {
			break
		}
108
109
	}

110
111
112
113
114
	storeErr := client.storage.StoreUpdates(client.updates)
	if storeErr != nil {
		return storeErr
	}
	return err
115
116
}

Sietse Ringers's avatar
Sietse Ringers committed
117
118
119
120
// 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.
121
122
func (client *Client) ParseAndroidStorage() (present bool, err error) {
	if client.androidStoragePath == "" {
123
124
125
		return false, nil
	}

126
	cardemuXML := client.androidStoragePath + "/shared_prefs/cardemu.xml"
127
	present, err = fs.PathExists(cardemuXML)
128
	if err != nil || !present {
Sietse Ringers's avatar
Sietse Ringers committed
129
130
		return
	}
131
	present = true
Sietse Ringers's avatar
Sietse Ringers committed
132

133
	bytes, err := ioutil.ReadFile(cardemuXML)
Sietse Ringers's avatar
Sietse Ringers committed
134
135
136
137
138
139
140
141
142
	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
143
144
145
	if err = xml.Unmarshal(bytes, &parsedxml); err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
146
147
148
149
150
151
152

	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"`
	})
153
	client.keyshareServers = make(map[irma.SchemeManagerIdentifier]*keyshareServer)
Sietse Ringers's avatar
Sietse Ringers committed
154
155
156
157
158
159
160
161
162
	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)
163
			if err = json.Unmarshal([]byte(jsontag), &client.keyshareServers); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
164
165
166
167
168
169
170
171
172
				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
			}
173
			client.paillierKeyCache = keys[0]
Sietse Ringers's avatar
Sietse Ringers committed
174
175
176
177
		}
	}

	for _, list := range parsedjson {
178
		client.secretkey = &secretKey{Key: list[0].Attributes[0]}
Sietse Ringers's avatar
Sietse Ringers committed
179
180
181
182
183
184
185
186
		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]
			}
187
			var cred *credential
188
			if cred, err = newCredential(gabicred, client.Configuration); err != nil {
189
				return
190
			}
Sietse Ringers's avatar
Sietse Ringers committed
191
			if cred.CredentialType() == nil {
192
193
				err = errors.New("cannot add unknown credential type")
				return
Sietse Ringers's avatar
Sietse Ringers committed
194
195
			}

196
			if err = client.addCredential(cred, false); err != nil {
197
				return
Sietse Ringers's avatar
Sietse Ringers committed
198
199
200
201
			}
		}
	}

202
203
	if len(client.credentials) > 0 {
		if err = client.storage.StoreAttributes(client.attributes); err != nil {
204
			return
Sietse Ringers's avatar
Sietse Ringers committed
205
		}
206
		if err = client.storage.StoreSecretKey(client.secretkey); err != nil {
207
			return
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
		}
	}

211
212
	if len(client.keyshareServers) > 0 {
		if err = client.storage.StoreKeyshareServers(client.keyshareServers); err != nil {
213
			return
Sietse Ringers's avatar
Sietse Ringers committed
214
215
		}
	}
216
	client.UnenrolledSchemeManagers = client.unenrolledSchemeManagers()
Sietse Ringers's avatar
Sietse Ringers committed
217

218
	if err = client.storage.StorePaillierKeys(client.paillierKeyCache); err != nil {
219
		return
Sietse Ringers's avatar
Sietse Ringers committed
220
	}
221
222
	if client.paillierKeyCache == nil {
		client.paillierKey(false) // trigger calculating a new one
Sietse Ringers's avatar
Sietse Ringers committed
223
	}
224
	return
Sietse Ringers's avatar
Sietse Ringers committed
225
}