session.go 20.1 KB
Newer Older
1
package irmaclient
2
3

import (
Koen van Ingen's avatar
Koen van Ingen committed
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
6
	"fmt"
	"sort"
7
8
	"strings"

9
10
	"math/big"

11
	"github.com/go-errors/errors"
12
	"github.com/mhe/gabi"
13
	"github.com/privacybydesign/irmago"
14
15
)

Sietse Ringers's avatar
Sietse Ringers committed
16
17
18
// This file contains the logic and state of performing IRMA sessions, communicates
// with IRMA API servers, and uses the calling Client to construct messages and replies
// in the IRMA protocol.
Sietse Ringers's avatar
Sietse Ringers committed
19

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

Sietse Ringers's avatar
Sietse Ringers committed
24
// PinHandler is used to provide the user's PIN code.
25
26
type PinHandler func(proceed bool, pin string)

27
28
// A Handler contains callbacks for communication to the user.
type Handler interface {
29
	StatusUpdate(action irma.Action, status irma.Status)
30
	Success(action irma.Action, result string)
31
32
	Cancelled(action irma.Action)
	Failure(action irma.Action, err *irma.SessionError)
33
	UnsatisfiableRequest(action irma.Action, ServerName string, missing irma.AttributeDisjunctionList)
34
35

	KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int)
36
	KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier)
37
	KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier)
38
	KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier)
39
40
41
42
43

	RequestIssuancePermission(request irma.IssuanceRequest, ServerName string, callback PermissionHandler)
	RequestVerificationPermission(request irma.DisclosureRequest, ServerName string, callback PermissionHandler)
	RequestSignaturePermission(request irma.SignatureRequest, ServerName string, callback PermissionHandler)
	RequestSchemeManagerPermission(manager *irma.SchemeManager, callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
44

45
	RequestPin(remainingAttempts int, callback PinHandler)
46
47
}

Sietse Ringers's avatar
Sietse Ringers committed
48
// SessionDismisser can dismiss the current IRMA session.
49
50
51
52
type SessionDismisser interface {
	Dismiss()
}

Sietse Ringers's avatar
Sietse Ringers committed
53
type session struct {
Koen van Ingen's avatar
Koen van Ingen committed
54
55
	Action  irma.Action
	Handler Handler
56
	Version *irma.ProtocolVersion
57

58
59
60
61
	choice  *irma.DisclosureChoice
	client  *Client
	request irma.SessionRequest
	done    bool
62

63
64
65
66
	// State for issuance protocol
	issuerProofNonce *big.Int
	builders         gabi.ProofBuilderList

67
	// These are empty on manual sessions
68
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
69
70
71
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
72
73
}

74
// We implement the handler for the keyshare protocol
75
var _ keyshareSessionHandler = (*session)(nil)
76

77
78
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
79
	2: {3, 2, 1},
80
81
}

82
// Session constructors
83

Sietse Ringers's avatar
Sietse Ringers committed
84
// NewManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
Koen van Ingen's avatar
Koen van Ingen committed
85
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
86
87
88
	var err error
	sigrequest := &irma.SignatureRequest{}
	if err = json.Unmarshal([]byte(sigrequestJSONString), sigrequest); err != nil {
Koen van Ingen's avatar
Koen van Ingen committed
89
90
91
92
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

93
	session := &session{
94
95
96
97
98
		Action:  irma.ActionSigning, // TODO hardcoded for now
		Handler: handler,
		client:  client,
		Version: irma.NewVersion(2, 0), // TODO hardcoded for now
		request: sigrequest,
Koen van Ingen's avatar
Koen van Ingen committed
99
100
101
102
	}

	session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)

103
	if !session.checkAndUpateConfiguration() {
104
105
106
		return
	}

107
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
108
	if len(missing) > 0 {
109
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
110
111
		return
	}
112
	session.request.SetCandidates(candidates)
113
114
115
116

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
117
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
118
		go session.doSession(proceed)
119
120
	})
	session.Handler.RequestSignaturePermission(
121
		*session.request.(*irma.SignatureRequest), "E-mail request", callback)
122
123
}

124
// NewSession creates and starts a new interactive IRMA session
125
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
126
	session := &session{
127
		ServerURL: qr.URL,
128
		transport: irma.NewHTTPTransport(qr.URL),
129
130
131
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
132
	}
133
134
135
136
137
138

	if session.Action == irma.ActionSchemeManager {
		go session.managerSession()
		return session
	}

139
140
	version, err := calcVersion(qr)
	if err != nil {
141
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
142
		return nil
143
	}
144
145
	session.Version = version
	session.transport.SetHeader("X-IRMA-ProtocolVersion", version.String())
146
147
148

	// Check if the action is one of the supported types
	switch session.Action {
149
150
151
152
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
153
154
		fallthrough
	default:
155
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
156
		return nil
157
158
159
160
161
162
	}

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

Sietse Ringers's avatar
Sietse Ringers committed
163
	go session.getSessionInfo()
164

165
	return session
166
167
}

168
169
// Core session methods

Sietse Ringers's avatar
Sietse Ringers committed
170
// getSessionInfo retrieves the first message in the IRMA protocol, checks if we can perform
171
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
172
173
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
174

175
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
176

Sietse Ringers's avatar
Sietse Ringers committed
177
	// Get the first IRMA protocol message and parse it
178
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
179
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
180
	if Err != nil {
181
		session.fail(Err.(*irma.SessionError))
182
183
184
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
185
	var err error
186
	session.jwt, err = irma.ParseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
187
	if err != nil {
188
		session.fail(&irma.SessionError{ErrorType: irma.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
189
190
		return
	}
191
192
193
194
	session.request = session.jwt.SessionRequest()
	session.request.SetContext(session.info.Context)
	session.request.SetNonce(session.info.Nonce)
	session.request.SetVersion(session.Version)
195
	if session.Action == irma.ActionIssuing {
196
		ir := session.request.(*irma.IssuanceRequest)
197
		// Store which public keys the server will use
198
		for _, credreq := range ir.Credentials {
199
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
200
201
202
		}
	}

203
	if !session.checkAndUpateConfiguration() {
204
205
206
		return
	}

207
	if session.Action == irma.ActionIssuing {
208
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
209
210
211
212
213
214
215
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
			return
		}

		// Calculate singleton credentials to be removed
Sietse Ringers's avatar
Sietse Ringers committed
216
		for _, credreq := range ir.Credentials {
217
218
219
220
			preexistingCredentials := session.client.attrs(*credreq.CredentialTypeID)
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
221
222
223
		}
	}

224
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
225
	if len(missing) > 0 {
226
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
227
228
		return
	}
229
	session.request.SetCandidates(candidates)
230

Sietse Ringers's avatar
Sietse Ringers committed
231
	// Ask for permission to execute the session
232
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
233
		session.choice = choice
234
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
235
		go session.doSession(proceed)
236
	})
237
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
238
	switch session.Action {
239
	case irma.ActionDisclosing:
240
		session.Handler.RequestVerificationPermission(
241
			*session.request.(*irma.DisclosureRequest), session.jwt.Requestor(), callback)
242
	case irma.ActionSigning:
243
		session.Handler.RequestSignaturePermission(
244
			*session.request.(*irma.SignatureRequest), session.jwt.Requestor(), callback)
245
	case irma.ActionIssuing:
246
		session.Handler.RequestIssuancePermission(
247
			*session.request.(*irma.IssuanceRequest), session.jwt.Requestor(), callback)
248
249
250
251
252
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

253
254
255
// doSession performs the session: it computes all proofs of knowledge, constructs credentials in case of issuance,
// asks for the pin and performs the keyshare session, and finishes the session by either POSTing the result to the
// API server or returning it to the caller (in case of interactive and noninteractive sessions, respectively).
Sietse Ringers's avatar
Sietse Ringers committed
256
257
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
258

259
	if !proceed {
260
		session.cancel()
261
262
		return
	}
263
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
264

265
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
266
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
267
		if err != nil {
268
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
269
270
271
272
			return
		}
		session.sendResponse(message)
	} else {
273
274
		var err error
		session.builders, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
275
		if err != nil {
276
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
277
		}
278
279
280
		startKeyshareSession(
			session,
			session.Handler,
281
			session.builders,
282
			session.request,
283
			session.client.Configuration,
284
			session.client.keyshareServers,
285
			session.issuerProofNonce,
286
		)
287
	}
Sietse Ringers's avatar
Sietse Ringers committed
288
}
289

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

292
293
// sendResponse sends the proofs of knowledge of the hidden attributes and/or the secret key, or the constructed
// attribute-based signature, to the API server.
294
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
295
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
296
	var err error
297
298
	var messageJson []byte

299
300
	switch session.Action {
	case irma.ActionSigning:
301
		request, ok := session.request.(*irma.SignatureRequest)
302
303
304
305
306
		if !ok {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

307
308
309
310
311
312
		irmaSignature, err := request.SignatureFromMessage(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

313
314
315
316
317
318
319
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
320
			var response disclosureResponse
321
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
322
323
324
325
326
327
328
				session.fail(err.(*irma.SessionError))
				return
			}
			if response != "VALID" {
				session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
				return
			}
Sietse Ringers's avatar
Sietse Ringers committed
329
		}
330
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
	case irma.ActionDisclosing:
		var response disclosureResponse
		if err = session.transport.Post("proofs", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
		if response != "VALID" {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
			return
		}
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
	case irma.ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		if err = session.transport.Post("commitments", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
348
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
349
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
350
351
			return
		}
352
		log, _ = session.createLogEntry(message) // TODO err
353
354
	}

355
	_ = session.client.addLogEntry(log) // TODO err
356
	if session.Action == irma.ActionIssuing {
357
		session.client.handler.UpdateAttributes()
358
	}
359
	session.done = true
360
361
362
	session.Handler.Success(session.Action, string(messageJson))
}

363
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
364
func (session *session) managerSession() {
365
	defer session.recoverFromPanic()
366
367
368
369

	// We have to download the scheme manager description.xml here before installing it,
	// because we need to show its contents (name, description, website) to the user
	// when asking installation permission.
370
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
371
	if err != nil {
372
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
373
374
		return
	}
375

376
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
377
		if !proceed {
378
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
379
380
			return
		}
381
		if err := session.client.Configuration.InstallSchemeManager(manager); err != nil {
382
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
383
384
			return
		}
385
386

		// Update state and inform user of success
387
		session.client.handler.UpdateConfiguration(
388
389
390
391
			&irma.IrmaIdentifierSet{
				SchemeManagers:  map[irma.SchemeManagerIdentifier]struct{}{manager.Identifier(): {}},
				Issuers:         map[irma.IssuerIdentifier]struct{}{},
				CredentialTypes: map[irma.CredentialTypeIdentifier]struct{}{},
Sietse Ringers's avatar
Sietse Ringers committed
392
393
			},
		)
394
		session.Handler.Success(session.Action, "")
395
396
397
	})
	return
}
398

399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
func (session *session) getBuilders() (gabi.ProofBuilderList, *big.Int, error) {
	var builders gabi.ProofBuilderList
	var err error

	var issuerProofNonce *big.Int
	switch session.Action {
	case irma.ActionSigning:
		builders, err = session.client.ProofBuilders(session.choice, session.request, true)
	case irma.ActionDisclosing:
		builders, err = session.client.ProofBuilders(session.choice, session.request, false)
	case irma.ActionIssuing:
		builders, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest))
	}

	return builders, issuerProofNonce, err
}

// getProofs computes the disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively) to be sent to the server.
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error

	switch session.Action {
	case irma.ActionSigning:
		message, err = session.client.Proofs(session.choice, session.request, true)
	case irma.ActionDisclosing:
		message, err = session.client.Proofs(session.choice, session.request, false)
	case irma.ActionIssuing:
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest))
	}

	return message, err
}

// Helper functions

func calcVersion(qr *irma.Qr) (*irma.ProtocolVersion, error) {
	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
	for k := range supportedVersions {
		keys = append(keys, k)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(keys)))
	for _, major := range keys {
		for _, minor := range supportedVersions[major] {
			aboveMinimum := major > qr.ProtocolVersion.Major || (major == qr.ProtocolVersion.Major && minor >= qr.ProtocolVersion.Minor)
			underMaximum := major < qr.ProtocolMaxVersion.Major || (major == qr.ProtocolMaxVersion.Major && minor <= qr.ProtocolMaxVersion.Minor)
			if aboveMinimum && underMaximum {
				return irma.NewVersion(major, minor), nil
			}
		}
	}
	return nil, fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion.String(), qr.ProtocolMaxVersion.String())
}

// checkKeyshareEnrollment checks if we are enrolled into all involved keyshare servers,
// and aborts the session if not
func (session *session) checkKeyshareEnrollment() bool {
	for id := range session.request.Identifiers().SchemeManagers {
		manager, ok := session.client.Configuration.SchemeManagers[id]
		if !ok {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorUnknownSchemeManager, Info: id.String()})
			return false
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

func (session *session) checkAndUpateConfiguration() bool {
	for id := range session.request.Identifiers().SchemeManagers {
		manager, contains := session.client.Configuration.SchemeManagers[id]
		if !contains {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorUnknownSchemeManager,
				Info:      id.String(),
			})
			return false
		}
		if !manager.Valid {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorInvalidSchemeManager,
				Info:      string(manager.Status),
			})
			return false
		}
	}

	// Check if we are enrolled into all involved keyshare servers
	if !session.checkKeyshareEnrollment() {
		return false
	}

	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
	if err != nil {
		session.fail(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
		return false
	}
	if downloaded != nil && !downloaded.Empty() {
		session.client.handler.UpdateConfiguration(downloaded)
	}
	return true
}

// IsInteractive returns whether this session uses an API server or not.
func (session *session) IsInteractive() bool {
	return session.ServerURL != ""
}

// Distributed returns whether or not this session involves a keyshare server.
func (session *session) Distributed() bool {
	var smi irma.SchemeManagerIdentifier
	if session.Action == irma.ActionIssuing {
		for _, credreq := range session.request.(*irma.IssuanceRequest).Credentials {
			smi = credreq.CredentialTypeID.IssuerIdentifier().SchemeManagerIdentifier()
			if session.client.Configuration.SchemeManagers[smi].Distributed() {
				return true
			}
		}
	}

	if session.choice == nil || session.choice.Attributes == nil {
		return false
	}

	for _, ai := range session.choice.Attributes {
		smi = ai.Type.CredentialTypeIdentifier().IssuerIdentifier().SchemeManagerIdentifier()
		if session.client.Configuration.SchemeManagers[smi].Distributed() {
			return true
		}
	}

	return false
}

545
546
// Session lifetime functions

547
548
549
550
551
552
553
554
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

555
556
557
558
559
560
561
562
563
564
func panicToError(e interface{}) *irma.SessionError {
	var info string
	switch x := e.(type) {
	case string:
		info = x
	case error:
		info = x.Error()
	case fmt.Stringer:
		info = x.String()
	default: // nop
565
	}
566
	fmt.Println("Panic: " + info)
567
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
568
569
570
}

// Idempotently send DELETE to remote server, returning whether or not we did something
571
func (session *session) delete() bool {
572
	if !session.done {
573
574
575
		if session.IsInteractive() {
			session.transport.Delete()
		}
576
		session.done = true
577
578
579
580
581
		return true
	}
	return false
}

582
func (session *session) fail(err *irma.SessionError) {
583
584
585
586
587
588
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		session.Handler.Failure(session.Action, err)
	}
}

589
func (session *session) cancel() {
590
591
592
593
594
	if session.delete() {
		session.Handler.Cancelled(session.Action)
	}
}

595
func (session *session) Dismiss() {
596
597
	session.cancel()
}
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

func (session *session) KeyshareCancelled() {
	session.cancel()
}

func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareEnrollmentIncomplete(manager)
}

func (session *session) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareEnrollmentDeleted(manager)
}

func (session *session) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
	session.Handler.KeyshareBlocked(manager, duration)
}

func (session *session) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
	var serr *irma.SessionError
	var ok bool
	if serr, ok = err.(*irma.SessionError); !ok {
		serr = &irma.SessionError{ErrorType: irma.ErrorKeyshare, Err: err}
	} else {
		serr.ErrorType = irma.ErrorKeyshare
	}
	session.fail(serr)
}

func (session *session) KeysharePin() {
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}

func (session *session) KeysharePinOK() {
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}