session.go 12.8 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
type PinHandler func(proceed bool, pin string)

22
23
24
25
26
// 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
27
	Failure(action Action, err *SessionError)
Sietse Ringers's avatar
Sietse Ringers committed
28
	UnsatisfiableRequest(action Action, missing AttributeDisjunctionList)
29
	MissingKeyshareEnrollment(manager SchemeManagerIdentifier)
30

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

36
	RequestPin(remainingAttempts int, callback PinHandler)
37
38
}

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

Sietse Ringers's avatar
Sietse Ringers committed
46
	info        *SessionInfo
47
	credManager *CredentialManager
Sietse Ringers's avatar
Sietse Ringers committed
48
	jwt         RequestorJwt
49
	irmaSession IrmaSession
Sietse Ringers's avatar
Sietse Ringers committed
50
51
	transport   *HTTPTransport
	choice      *DisclosureChoice
52
	downloaded  *IrmaIdentifierSet
53
54
}

55
56
57
// We implement the handler for the keyshare protocol
var _ keyshareSessionHandler = (*session)(nil)

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

func calcVersion(qr *Qr) (string, error) {
	// Parse range supported by server
65
66
67
68
69
70
71
72
73
74
75
76
	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 {
77
78
79
80
81
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
82
	for k := range supportedVersions {
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
		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)
}

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

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

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

	go session.start()

Sietse Ringers's avatar
Sietse Ringers committed
133
	return
134
135
}

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
func handlePanic(callback func(*SessionError)) {
	if e := recover(); e != nil {
		var info string
		switch x := e.(type) {
		case string:
			info = x
		case error:
			info = x.Error()
		case fmt.Stringer:
			info = x.String()
		default: // nop
		}
		fmt.Printf("Recovered from panic: '%v'\n%s\n", e, info)
		if callback != nil {
			callback(&SessionError{ErrorType: ErrorPanic, Info: info})
		}
	}
}

Tomas's avatar
Tomas committed
155
func (session *session) fail(err *SessionError) {
156
157
	session.transport.Delete()
	err.Err = errors.Wrap(err.Err, 0)
Sietse Ringers's avatar
Sietse Ringers committed
158
	if session.downloaded != nil && !session.downloaded.Empty() {
159
160
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
161
162
163
	session.Handler.Failure(session.Action, err)
}

164
165
func (session *session) cancel() {
	session.transport.Delete()
Sietse Ringers's avatar
Sietse Ringers committed
166
	if session.downloaded != nil && !session.downloaded.Empty() {
167
168
169
170
171
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
	session.Handler.Cancelled(session.Action)
}

172
173
// 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
174
func (session *session) start() {
175
176
177
178
179
180
181
182
	defer func() {
		handlePanic(func(err *SessionError) {
			if session.Handler != nil {
				session.Handler.Failure(session.Action, err)
			}
		})
	}()

183
184
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

185
186
187
188
189
	if session.Action == ActionSchemeManager {
		session.managerSession()
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
190
	// Get the first IRMA protocol message and parse it
Sietse Ringers's avatar
Sietse Ringers committed
191
192
	session.info = &SessionInfo{}
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
193
	if Err != nil {
Tomas's avatar
Tomas committed
194
		session.fail(Err.(*SessionError))
195
196
197
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
198
	var err error
199
	session.jwt, err = parseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
200
	if err != nil {
Tomas's avatar
Tomas committed
201
		session.fail(&SessionError{ErrorType: ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
202
203
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
204
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
205
206
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
207
	if session.Action == ActionIssuing {
208
		ir := session.irmaSession.(*IssuanceRequest)
209
		// Store which public keys the server will use
210
		for _, credreq := range ir.Credentials {
211
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
212
213
214
		}
	}

215
	// Check if we are enrolled into all involved keyshare servers
Sietse Ringers's avatar
Sietse Ringers committed
216
	for id := range session.irmaSession.Identifiers().SchemeManagers {
217
218
219
220
221
222
		manager, ok := session.credManager.ConfigurationStore.SchemeManagers[id]
		if !ok {
			session.fail(&SessionError{ErrorType: ErrorUnknownSchemeManager, Info: id.String()})
			return
		}
		distributed := manager.Distributed()
223
224
		_, enrolled := session.credManager.keyshareServers[id]
		if distributed && !enrolled {
Sietse Ringers's avatar
Sietse Ringers committed
225
			session.transport.Delete()
226
			session.Handler.MissingKeyshareEnrollment(id)
Sietse Ringers's avatar
Sietse Ringers committed
227
228
229
230
			return
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
231
	// Download missing credential types/issuers/public keys from the scheme manager
232
	if session.downloaded, err = session.credManager.ConfigurationStore.Download(session.irmaSession.Identifiers()); err != nil {
233
234
235
236
237
238
239
		session.Handler.Failure(
			session.Action,
			&SessionError{ErrorType: ErrorConfigurationStoreDownload, Err: err},
		)
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
240
241
242
	if session.Action == ActionIssuing {
		ir := session.irmaSession.(*IssuanceRequest)
		for _, credreq := range ir.Credentials {
243
244
245
246
247
248
			info, err := credreq.Info(session.credManager.ConfigurationStore)
			if err != nil {
				session.fail(&SessionError{ErrorType: ErrorUnknownCredentialType, Err: err})
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
249
250
251
		}
	}

252
	candidates, missing := session.credManager.CheckSatisfiability(session.irmaSession.ToDisclose())
253
254
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
255
		// TODO: session.transport.Delete() on dialog cancel
256
257
		return
	}
258
	session.irmaSession.SetCandidates(candidates)
259

Sietse Ringers's avatar
Sietse Ringers committed
260
	// Ask for permission to execute the session
Sietse Ringers's avatar
Sietse Ringers committed
261
	callback := PermissionHandler(func(proceed bool, choice *DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
262
263
264
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
265
266
267
268
	})
	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
269
270
		session.Handler.RequestVerificationPermission(
			*session.irmaSession.(*DisclosureRequest), session.jwt.Requestor(), callback)
271
	case ActionSigning:
272
273
		session.Handler.RequestSignaturePermission(
			*session.irmaSession.(*SignatureRequest), session.jwt.Requestor(), callback)
274
	case ActionIssuing:
275
276
		session.Handler.RequestIssuancePermission(
			*session.irmaSession.(*IssuanceRequest), session.jwt.Requestor(), callback)
277
278
279
280
281
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
282
func (session *session) do(proceed bool) {
283
284
285
286
287
288
289
290
	defer func() {
		handlePanic(func(err *SessionError) {
			if session.Handler != nil {
				session.Handler.Failure(session.Action, err)
			}
		})
	}()

291
	if !proceed {
292
		session.cancel()
293
294
295
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
296

297
	if !session.irmaSession.Identifiers().Distributed(session.credManager.ConfigurationStore) {
Sietse Ringers's avatar
Sietse Ringers committed
298
299
300
301
		var message interface{}
		var err error
		switch session.Action {
		case ActionSigning:
302
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, true)
Sietse Ringers's avatar
Sietse Ringers committed
303
		case ActionDisclosing:
304
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, false)
Sietse Ringers's avatar
Sietse Ringers committed
305
		case ActionIssuing:
306
			message, err = session.credManager.IssueCommitments(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
307
308
		}
		if err != nil {
Tomas's avatar
Tomas committed
309
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
310
311
312
313
			return
		}
		session.sendResponse(message)
	} else {
314
		var builders gabi.ProofBuilderList
Sietse Ringers's avatar
Sietse Ringers committed
315
316
317
318
319
		var err error
		switch session.Action {
		case ActionSigning:
			fallthrough
		case ActionDisclosing:
320
			builders, err = session.credManager.ProofBuilders(session.choice)
Sietse Ringers's avatar
Sietse Ringers committed
321
		case ActionIssuing:
322
			builders, err = session.credManager.IssuanceProofBuilders(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
323
324
		}
		if err != nil {
Tomas's avatar
Tomas committed
325
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
326
327
		}

328
329
330
331
332
333
334
335
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
			session.credManager.ConfigurationStore,
			session.credManager.keyshareServers,
		)
336
	}
Sietse Ringers's avatar
Sietse Ringers committed
337
}
338

Sietse Ringers's avatar
Sietse Ringers committed
339
340
341
342
func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

Sietse Ringers's avatar
Sietse Ringers committed
343
func (session *session) KeyshareCancelled() {
344
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
345
346
}

Sietse Ringers's avatar
Sietse Ringers committed
347
func (session *session) KeyshareBlocked(duration int) {
Tomas's avatar
Tomas committed
348
	session.fail(&SessionError{ErrorType: ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
349
350
351
}

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

Sietse Ringers's avatar
Sietse Ringers committed
355
356
type disclosureResponse string

Sietse Ringers's avatar
Sietse Ringers committed
357
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
358
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
359
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
360

Sietse Ringers's avatar
Sietse Ringers committed
361
362
363
364
	switch session.Action {
	case ActionSigning:
		fallthrough
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
365
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
366
		if err = session.transport.Post("proofs", &response, message); err != nil {
Tomas's avatar
Tomas committed
367
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
368
369
370
			return
		}
		if response != "VALID" {
Tomas's avatar
Tomas committed
371
			session.fail(&SessionError{ErrorType: ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
372
373
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
374
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
Sietse Ringers's avatar
Sietse Ringers committed
375
376
	case ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
377
		if err = session.transport.Post("commitments", &response, message); err != nil {
Tomas's avatar
Tomas committed
378
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
379
380
			return
		}
381
		if err = session.credManager.ConstructCredentials(response, session.irmaSession.(*IssuanceRequest)); err != nil {
Tomas's avatar
Tomas committed
382
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
383
384
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
385
		log, _ = session.createLogEntry(message) // TODO err
386
387
	}

388
	_ = session.credManager.addLogEntry(log) // TODO err
389
390
391
392
393
394
	if !session.downloaded.Empty() {
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
	if session.Action == ActionIssuing {
		session.credManager.handler.UpdateAttributes()
	}
395
	session.Handler.Success(session.Action)
396
}
397
398
399
400
401
402
403

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
	}
404
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
405
		if !proceed {
406
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
407
408
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
409
410
411
412
		if err := session.credManager.ConfigurationStore.AddSchemeManager(manager); err != nil {
			session.Handler.Failure(session.Action, &SessionError{})
			return
		}
413
		if manager.Distributed() {
Sietse Ringers's avatar
Sietse Ringers committed
414
			session.credManager.UnenrolledKeyshareServers = session.credManager.unenrolledKeyshareServers()
415
		}
Sietse Ringers's avatar
Sietse Ringers committed
416
417
418
419
420
421
422
423
		session.credManager.handler.UpdateConfigurationStore(
			&IrmaIdentifierSet{
				SchemeManagers:  map[SchemeManagerIdentifier]struct{}{manager.Identifier(): {}},
				Issuers:         map[IssuerIdentifier]struct{}{},
				CredentialTypes: map[CredentialTypeIdentifier]struct{}{},
			},
		)
		session.Handler.Success(session.Action)
424
425
426
	})
	return
}