session.go 11.2 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
package irmago
2
3

import (
Sietse Ringers's avatar
Sietse Ringers committed
4
5
	"fmt"
	"sort"
6
	"strconv"
7
8
	"strings"

9
	"github.com/go-errors/errors"
10
	"github.com/mhe/gabi"
11
12
)

Sietse Ringers's avatar
Sietse Ringers committed
13
14
15
// This file contains the client side of the IRMA protocol, as well as the Handler interface
// which is used to communicate session info with the user.

Sietse Ringers's avatar
Sietse Ringers committed
16
17
// PermissionHandler is a callback for providing permission for an IRMA session
// and specifying the attributes to be disclosed.
Sietse Ringers's avatar
Sietse Ringers committed
18
type PermissionHandler func(proceed bool, choice *DisclosureChoice)
19
20
21
22
23
24

// A Handler contains callbacks for communication to the user.
type Handler interface {
	StatusUpdate(action Action, status Status)
	Success(action Action)
	Cancelled(action Action)
Tomas's avatar
Tomas committed
25
	Failure(action Action, err *SessionError)
Sietse Ringers's avatar
Sietse Ringers committed
26
	UnsatisfiableRequest(action Action, missing AttributeDisjunctionList)
27

28
29
30
	RequestIssuancePermission(request IssuanceRequest, ServerName string, callback PermissionHandler)
	RequestVerificationPermission(request DisclosureRequest, ServerName string, callback PermissionHandler)
	RequestSignaturePermission(request SignatureRequest, ServerName string, callback PermissionHandler)
31
	RequestSchemeManagerPermission(manager *SchemeManager, callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
32

33
	RequestPin(remainingAttempts int, callback func(proceed bool, pin string))
34
35
}

Sietse Ringers's avatar
Sietse Ringers committed
36
37
// A session is an IRMA session.
type session struct {
38
39
40
41
42
	Action    Action
	Version   Version
	ServerURL string
	Handler   Handler

Sietse Ringers's avatar
Sietse Ringers committed
43
	info        *SessionInfo
44
	credManager *CredentialManager
Sietse Ringers's avatar
Sietse Ringers committed
45
	jwt         RequestorJwt
46
	irmaSession IrmaSession
Sietse Ringers's avatar
Sietse Ringers committed
47
48
	transport   *HTTPTransport
	choice      *DisclosureChoice
49
	newmanager  *SchemeManager
50
51
}

52
53
54
// We implement the handler for the keyshare protocol
var _ keyshareSessionHandler = (*session)(nil)

55
56
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
57
	2: {2, 1},
58
59
60
61
}

func calcVersion(qr *Qr) (string, error) {
	// Parse range supported by server
62
63
64
65
66
67
68
69
70
71
72
73
	var minmajor, minminor, maxmajor, maxminor int
	var err error
	if minmajor, err = strconv.Atoi(string(qr.ProtocolVersion[0])); err != nil {
		return "", err
	}
	if minminor, err = strconv.Atoi(string(qr.ProtocolVersion[2])); err != nil {
		return "", err
	}
	if maxmajor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[0])); err != nil {
		return "", err
	}
	if maxminor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[2])); err != nil {
74
75
76
77
78
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
79
	for k := range supportedVersions {
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
		keys = append(keys, k)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(keys)))
	for _, major := range keys {
		for _, minor := range supportedVersions[major] {
			aboveMinimum := major > minmajor || (major == minmajor && minor >= minminor)
			underMaximum := major < maxmajor || (major == maxmajor && minor <= maxminor)
			if aboveMinimum && underMaximum {
				return fmt.Sprintf("%d.%d", major, minor), nil
			}
		}
	}
	return "", fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion, qr.ProtocolMaxVersion)
}

95
// NewSession creates and starts a new IRMA session.
96
func (cm *CredentialManager) NewSession(qr *Qr, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
97
	session := &session{
98
99
100
101
		Action:      Action(qr.Type),
		ServerURL:   qr.URL,
		Handler:     handler,
		transport:   NewHTTPTransport(qr.URL),
102
		credManager: cm,
103
	}
104
105
	version, err := calcVersion(qr)
	if err != nil {
Tomas's avatar
Tomas committed
106
		session.fail(&SessionError{ErrorType: ErrorProtocolVersionNotSupported, Err: err})
107
108
109
		return
	}
	session.Version = Version(version)
110
111
112
113
114
115

	// Check if the action is one of the supported types
	switch session.Action {
	case ActionDisclosing: // nop
	case ActionSigning: // nop
	case ActionIssuing: // nop
116
	//case ActionSchemeManager: // nop
117
118
119
	case ActionUnknown:
		fallthrough
	default:
Tomas's avatar
Tomas committed
120
		session.fail(&SessionError{ErrorType: ErrorUnknownAction, Info: string(session.Action)})
Sietse Ringers's avatar
Sietse Ringers committed
121
		return
122
123
124
125
126
127
128
129
	}

	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}

	go session.start()

Sietse Ringers's avatar
Sietse Ringers committed
130
	return
131
132
}

Tomas's avatar
Tomas committed
133
func (session *session) fail(err *SessionError) {
134
135
136
137
138
	session.transport.Delete()
	err.Err = errors.Wrap(err.Err, 0)
	session.Handler.Failure(session.Action, err)
}

139
140
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
141
func (session *session) start() {
142
143
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

144
145
146
147
148
	if session.Action == ActionSchemeManager {
		session.managerSession()
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
149
	// Get the first IRMA protocol message and parse it
Sietse Ringers's avatar
Sietse Ringers committed
150
151
	session.info = &SessionInfo{}
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
152
	if Err != nil {
Tomas's avatar
Tomas committed
153
		session.fail(Err.(*SessionError))
154
155
156
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
157
158
159
	var server string
	var err error
	session.jwt, server, err = parseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
160
	if err != nil {
Tomas's avatar
Tomas committed
161
		session.fail(&SessionError{ErrorType: ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
162
163
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
164
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
165
166
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
167
	if session.Action == ActionIssuing {
168
		ir := session.irmaSession.(*IssuanceRequest)
169
		// Store which public keys the server will use
170
		for _, credreq := range ir.Credentials {
171
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
172
173
174
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
175
	// Download missing credential types/issuers/public keys from the scheme manager
176
177
178
179
180
181
182
183
	if err = session.credManager.ConfigurationStore.Download(session.irmaSession.Identifiers()); err != nil {
		session.Handler.Failure(
			session.Action,
			&SessionError{ErrorType: ErrorConfigurationStoreDownload, Err: err},
		)
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
184
185
186
	if session.Action == ActionIssuing {
		ir := session.irmaSession.(*IssuanceRequest)
		for _, credreq := range ir.Credentials {
187
188
189
190
191
192
			info, err := credreq.Info(session.credManager.ConfigurationStore)
			if err != nil {
				session.fail(&SessionError{ErrorType: ErrorUnknownCredentialType, Err: err})
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
193
194
195
		}
	}

196
	candidates, missing := session.credManager.CheckSatisfiability(session.irmaSession.ToDisclose())
197
198
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
199
		// TODO: session.transport.Delete() on dialog cancel
200
201
		return
	}
202
	session.irmaSession.SetCandidates(candidates)
203

Sietse Ringers's avatar
Sietse Ringers committed
204
	// Ask for permission to execute the session
Sietse Ringers's avatar
Sietse Ringers committed
205
	callback := PermissionHandler(func(proceed bool, choice *DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
206
207
208
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
209
210
211
212
	})
	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
213
		session.Handler.RequestVerificationPermission(*session.irmaSession.(*DisclosureRequest), server, callback)
214
	case ActionSigning:
215
		session.Handler.RequestSignaturePermission(*session.irmaSession.(*SignatureRequest), server, callback)
216
	case ActionIssuing:
217
		session.Handler.RequestIssuancePermission(*session.irmaSession.(*IssuanceRequest), server, callback)
218
219
220
221
222
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
223
func (session *session) do(proceed bool) {
224
	if !proceed {
225
		session.transport.Delete()
226
227
228
229
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
230

231
	if !session.irmaSession.Identifiers().Distributed(session.credManager.ConfigurationStore) {
Sietse Ringers's avatar
Sietse Ringers committed
232
233
234
235
		var message interface{}
		var err error
		switch session.Action {
		case ActionSigning:
236
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, true)
Sietse Ringers's avatar
Sietse Ringers committed
237
		case ActionDisclosing:
238
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, false)
Sietse Ringers's avatar
Sietse Ringers committed
239
		case ActionIssuing:
240
			message, err = session.credManager.IssueCommitments(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
241
242
		}
		if err != nil {
Tomas's avatar
Tomas committed
243
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
244
245
246
247
			return
		}
		session.sendResponse(message)
	} else {
248
		var builders gabi.ProofBuilderList
Sietse Ringers's avatar
Sietse Ringers committed
249
250
251
252
253
		var err error
		switch session.Action {
		case ActionSigning:
			fallthrough
		case ActionDisclosing:
254
			builders, err = session.credManager.ProofBuilders(session.choice)
Sietse Ringers's avatar
Sietse Ringers committed
255
		case ActionIssuing:
256
			builders, err = session.credManager.IssuanceProofBuilders(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
257
258
		}
		if err != nil {
Tomas's avatar
Tomas committed
259
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
260
261
		}

262
263
264
265
266
267
268
269
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
			session.credManager.ConfigurationStore,
			session.credManager.keyshareServers,
		)
270
	}
Sietse Ringers's avatar
Sietse Ringers committed
271
}
272

Sietse Ringers's avatar
Sietse Ringers committed
273
274
275
276
func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

Sietse Ringers's avatar
Sietse Ringers committed
277
func (session *session) KeyshareCancelled() {
278
	session.transport.Delete()
Sietse Ringers's avatar
Sietse Ringers committed
279
280
281
	session.Handler.Cancelled(session.Action)
}

Sietse Ringers's avatar
Sietse Ringers committed
282
func (session *session) KeyshareBlocked(duration int) {
Tomas's avatar
Tomas committed
283
	session.fail(&SessionError{ErrorType: ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
284
285
286
}

func (session *session) KeyshareError(err error) {
Tomas's avatar
Tomas committed
287
	session.fail(&SessionError{ErrorType: ErrorKeyshare, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
288
289
}

Sietse Ringers's avatar
Sietse Ringers committed
290
291
type disclosureResponse string

Sietse Ringers's avatar
Sietse Ringers committed
292
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
293
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
294
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
295

Sietse Ringers's avatar
Sietse Ringers committed
296
297
298
299
	switch session.Action {
	case ActionSigning:
		fallthrough
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
300
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
301
		if err = session.transport.Post("proofs", &response, message); err != nil {
Tomas's avatar
Tomas committed
302
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
303
304
305
			return
		}
		if response != "VALID" {
Tomas's avatar
Tomas committed
306
			session.fail(&SessionError{ErrorType: ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
307
308
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
309
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
Sietse Ringers's avatar
Sietse Ringers committed
310
311
	case ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
312
		if err = session.transport.Post("commitments", &response, message); err != nil {
Tomas's avatar
Tomas committed
313
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
314
315
			return
		}
316
		if err = session.credManager.ConstructCredentials(response, session.irmaSession.(*IssuanceRequest)); err != nil {
Tomas's avatar
Tomas committed
317
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
318
319
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
320
		log, _ = session.createLogEntry(message) // TODO err
321
322
	}

323
	_ = session.credManager.addLogEntry(log) // TODO err
324
	session.Handler.Success(session.Action)
325
}
326
327
328
329
330
331
332

func (session *session) managerSession() {
	manager, err := session.credManager.ConfigurationStore.DownloadSchemeManager(session.ServerURL)
	if err != nil {
		session.Handler.Failure(session.Action, &SessionError{Err: err}) // TODO
		return
	}
333
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
		if !proceed {
			session.Handler.Cancelled(session.Action)
			return
		}
		session.newmanager = manager
		if manager.Distributed() {
			session.credManager.KeyshareEnroll(manager, KeyshareHandler(session))
		} else {
			session.RegistrationSuccess()
		}
	})
	return
}

func (session *session) StartRegistration(manager *SchemeManager, callback func(email, pin string)) {
	session.credManager.keyshareHandler.StartRegistration(manager, callback)
}

func (session *session) RegistrationError(err error) {
	session.Handler.Failure(session.Action, &SessionError{Err: err}) // TODO
	session.credManager.keyshareHandler.RegistrationError(err)
}

func (session *session) RegistrationSuccess() {
	if err := session.credManager.ConfigurationStore.AddSchemeManager(session.newmanager); err != nil {
		session.Handler.Failure(session.Action, &SessionError{})
		return
	}
	session.Handler.Success(session.Action)
	session.credManager.keyshareHandler.RegistrationSuccess()
}