updates.go 6.27 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
		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
83
			ProofList   gabi.ProofList
Sietse Ringers's avatar
Sietse Ringers committed
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
			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
				}
148
				entry.Disclosure = &irma.Disclosure{}
Sietse Ringers's avatar
Sietse Ringers committed
149
				for _, proof := range proofs {
150
					entry.Disclosure.Proofs = append(entry.Disclosure.Proofs, proof)
Sietse Ringers's avatar
Sietse Ringers committed
151
152
				}
			case irma.ActionIssuing:
153
				entry.IssueCommitment = &irma.IssueCommitmentMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
154
155
156
157
158
159
160
161
162
163
164
165
				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
166
	},
167
}
168

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

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

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