updates.go 6.18 KB
Newer Older
1
package irmaclient
Sietse Ringers's avatar
Sietse Ringers committed
2
3

import (
Sietse Ringers's avatar
Sietse Ringers committed
4
5
	"encoding/json"
	"regexp"
6
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
7

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

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

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

25
var clientUpdates = []func(client *Client) error{
26
	// 0: Convert old cardemu.xml Android storage to our own storage format
27
	nil, // No longer necessary as the Android app was deprecated long ago
28

29
	// 1: Adding scheme manager index, signature and public key
30
31
	// Check the signatures of all scheme managers, if any is not ok,
	// copy the entire irma_configuration folder from assets
32
	nil, // made irrelevant by irma_configuration-autocopying
33

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

54
	// 3: Copy new irma_configuration out of assets
55
	nil, // made irrelevant by irma_configuration-autocopying
56

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

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

72
73
	// 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
74
	func(client *Client) (err error) {
Sietse Ringers's avatar
Sietse Ringers committed
75
76
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
		logs, err := client.Logs()
		if err != nil {
			return
		}
		// 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
			SessionInfo struct {
				Nonce   *big.Int                      `json:"nonce"`
				Context *big.Int                      `json:"context"`
				Jwt     string                        `json:"jwt"`
				Keys    map[irma.IssuerIdentifier]int `json:"keys"`
			}
		}
		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")
			}

			var jwt irma.RequestorJwt
			jwt, err = irma.ParseRequestorJwt(string(entry.Type), oldEntry.SessionInfo.Jwt)
			if err != nil {
				return err
			}

			entry.request = jwt.SessionRequest()
			// 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.
			if entry.Type == irma.ActionIssuing && entry.Version == nil {
				for _, attr := range entry.request.(*irma.IssuanceRequest).Credentials[0].Attributes {
					if regexp.MustCompile("^\\w").Match([]byte(attr)) {
						entry.Version = irma.NewVersion(2, 2)
					} else {
						entry.Version = irma.NewVersion(2, 3)
					}
					break
				}
			}
			if entry.Version == nil {
				entry.Version = irma.NewVersion(2, 3)
			}
			entry.request.SetNonce(oldEntry.SessionInfo.Nonce)
			entry.request.SetContext(oldEntry.SessionInfo.Context)
			entry.request.SetVersion(entry.Version)
			if err := entry.setSessionRequest(); err != nil {
				return err
			}

			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
				}
				isreq := entry.request.(*irma.IssuanceRequest)
				for _, cred := range isreq.Credentials {
					cred.KeyCounter = oldEntry.SessionInfo.Keys[cred.CredentialTypeID.IssuerIdentifier()]
				}
			default:
				return errors.New("Invalid log type")
			}
		}
		return client.storage.StoreLogs(logs)
Tomas's avatar
Tomas committed
164
	},
165
}
166

167
// update performs any function from clientUpdates that has not
168
169
// already been executed in the past, keeping track of previously executed updates
// in the file at updatesFile.
170
func (client *Client) update() error {
171
172
	// Load and parse file containing info about already performed updates
	var err error
173
	if client.updates, err = client.storage.LoadUpdates(); err != nil {
174
175
176
177
		return err
	}

	// Perform all new updates
178
	for i := len(client.updates); i < len(clientUpdates); i++ {
179
180
181
		err = nil
		if clientUpdates[i] != nil {
			err = clientUpdates[i](client)
182
		}
Sietse Ringers's avatar
Sietse Ringers committed
183
		u := update{
184
			When:    irma.Timestamp(time.Now()),
185
186
187
188
189
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
Sietse Ringers's avatar
Sietse Ringers committed
190
			u.Error = &str
191
		}
192
		client.updates = append(client.updates, u)
193
194
195
		if err != nil {
			break
		}
196
197
	}

198
199
200
201
202
	storeErr := client.storage.StoreUpdates(client.updates)
	if storeErr != nil {
		return storeErr
	}
	return err
203
}