session.go 21.4 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
	"fmt"
6
	"net/url"
7
	"reflect"
8
9
	"strings"

10
	"github.com/bwesterb/go-atum"
11
	"github.com/go-errors/errors"
12
13
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
14
	"github.com/privacybydesign/irmago"
15
16
)

Sietse Ringers's avatar
Sietse Ringers committed
17
18
19
// 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
20

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

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

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

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

41
42
43
	RequestIssuancePermission(request *irma.IssuanceRequest, candidates [][][]*irma.AttributeIdentifier, ServerName irma.TranslatedString, callback PermissionHandler)
	RequestVerificationPermission(request *irma.DisclosureRequest, candidates [][][]*irma.AttributeIdentifier, ServerName irma.TranslatedString, callback PermissionHandler)
	RequestSignaturePermission(request *irma.SignatureRequest, candidates [][][]*irma.AttributeIdentifier, ServerName irma.TranslatedString, callback PermissionHandler)
44
	RequestSchemeManagerPermission(manager *irma.SchemeManager, callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
45

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

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

Sietse Ringers's avatar
Sietse Ringers committed
54
type session struct {
55
56
57
	Action     irma.Action
	Handler    Handler
	Version    *irma.ProtocolVersion
58
	ServerName irma.TranslatedString
59

60
61
62
63
64
	choice      *irma.DisclosureChoice
	attrIndices irma.DisclosedAttributeIndices
	client      *Client
	request     irma.SessionRequest
	done        bool
65

66
	// State for issuance sessions
67
68
69
	issuerProofNonce *big.Int
	builders         gabi.ProofBuilderList

70
71
72
	// State for signature sessions
	timestamp *atum.Timestamp

73
	// These are empty on manual sessions
74
	Hostname  string
75
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
76
	transport *irma.HTTPTransport
77
78
}

79
// We implement the handler for the keyshare protocol
80
var _ keyshareSessionHandler = (*session)(nil)
81

82
83
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
Sietse Ringers's avatar
Sietse Ringers committed
84
	2: {4},
85
}
Sietse Ringers's avatar
Sietse Ringers committed
86
87
88
var minVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][0]}
var maxVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][len(supportedVersions[2])-1]}

89
// Session constructors
90

91
92
// NewSession starts a new IRMA session, given (along with a handler to pass feedback to) a session request.
// When the request is not suitable to start an IRMA session from, it calls the Failure method of the specified Handler.
93
94
95
96
97
98
99
100
func (client *Client) NewSession(sessionrequest string, handler Handler) SessionDismisser {
	bts := []byte(sessionrequest)

	qr := &irma.Qr{}
	if err := irma.UnmarshalValidate(bts, qr); err == nil {
		return client.newQrSession(qr, handler)
	}

101
102
103
104
105
106
107
	schemeRequest := &irma.SchemeManagerRequest{}
	if err := irma.UnmarshalValidate(bts, schemeRequest); err == nil {
		return client.newSchemeSession(schemeRequest, handler)
	}

	sigRequest := &irma.SignatureRequest{}
	if err := irma.UnmarshalValidate(bts, sigRequest); err == nil {
108
109
110
111
112
113
		return client.newManualSession(sigRequest, handler, irma.ActionSigning)
	}

	disclosureRequest := &irma.DisclosureRequest{}
	if err := irma.UnmarshalValidate(bts, disclosureRequest); err == nil {
		return client.newManualSession(disclosureRequest, handler, irma.ActionDisclosing)
Koen van Ingen's avatar
Koen van Ingen committed
114
115
	}

116
	handler.Failure(&irma.SessionError{Err: errors.New("Session request could not be parsed"), Info: sessionrequest})
117
118
119
120
	return nil
}

// newManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
121
func (client *Client) newManualSession(request irma.SessionRequest, handler Handler, action irma.Action) SessionDismisser {
122
	session := &session{
123
124
125
126
127
		Action:  action,
		Handler: handler,
		client:  client,
		Version: minVersion,
		request: request,
Koen van Ingen's avatar
Koen van Ingen committed
128
	}
129
	session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)
Koen van Ingen's avatar
Koen van Ingen committed
130

131
	session.processSessionInfo()
132
	return session
133
134
}

135
136
137
138
139
140
141
142
143
144
145
146
147
148
func (client *Client) newSchemeSession(qr *irma.SchemeManagerRequest, handler Handler) SessionDismisser {
	session := &session{
		ServerURL: qr.URL,
		transport: irma.NewHTTPTransport(qr.URL),
		Action:    irma.ActionSchemeManager,
		Handler:   handler,
		client:    client,
	}
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)

	go session.managerSession()
	return session
}

149
150
// newQrSession creates and starts a new interactive IRMA session
func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisser {
151
	u, _ := url.ParseRequestURI(qr.URL) // Qr validator already checked this for errors
152
	session := &session{
153
154
155
156
157
158
		ServerURL: qr.URL,
		Hostname:  u.Hostname(),
		transport: irma.NewHTTPTransport(qr.URL),
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
159
	}
160
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
161

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
	// Check if the action is one of the supported types
	switch session.Action {
	case irma.ActionDisclosing:
		session.request = &irma.DisclosureRequest{}
	case irma.ActionSigning:
		session.request = &irma.SignatureRequest{}
	case irma.ActionIssuing:
		session.request = &irma.IssuanceRequest{}
	case irma.ActionUnknown:
		fallthrough
	default:
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
		return nil
	}

177
178
	session.transport.SetHeader(irma.MinVersionHeader, minVersion.String())
	session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String())
179
180
181
	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}
182

Sietse Ringers's avatar
Sietse Ringers committed
183
	go session.getSessionInfo()
184
	return session
185
186
}

187
188
// Core session methods

189
// getSessionInfo retrieves the first message in the IRMA protocol (only in interactive sessions)
Sietse Ringers's avatar
Sietse Ringers committed
190
191
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
192

193
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
194

Sietse Ringers's avatar
Sietse Ringers committed
195
	// Get the first IRMA protocol message and parse it
196
	err := session.transport.Get("", session.request)
Sietse Ringers's avatar
Sietse Ringers committed
197
	if err != nil {
198
		session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
199
200
		return
	}
201

202
	session.processSessionInfo()
203
204
}

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
func serverName(hostname string, request irma.SessionRequest, conf *irma.Configuration) irma.TranslatedString {
	sn := irma.NewTranslatedString(&hostname)

	if ir, ok := request.(*irma.IssuanceRequest); ok {
		// If there is only one issuer in the current request, use its name as ServerName
		var iss irma.TranslatedString
		for _, credreq := range ir.Credentials {
			credIssuer := conf.Issuers[credreq.CredentialTypeID.IssuerIdentifier()].Name
			if !reflect.DeepEqual(credIssuer, iss) { // Can't just test pointer equality: credIssuer != iss
				if len(iss) != 0 {
					return sn
				}
				iss = credIssuer
			}
		}
		if len(iss) != 0 {
			return iss
		}
	}

	return sn
}

228
229
// processSessionInfo continues the session after all session state has been received:
// it checks if the session can be performed and asks the user for consent.
230
func (session *session) processSessionInfo() {
231
232
	defer session.recoverFromPanic()

233
	if !session.checkAndUpateConfiguration() {
234
235
236
		return
	}

237
	baserequest := session.request.Base()
238
	confirmedProtocolVersion := baserequest.ProtocolVersion
239
240
241
242
	if confirmedProtocolVersion != nil {
		session.Version = confirmedProtocolVersion
	} else {
		session.Version = irma.NewVersion(2, 0)
243
		baserequest.ProtocolVersion = session.Version
244
245
	}

246
247
	session.ServerName = serverName(session.Hostname, session.request, session.client.Configuration)

248
	if session.Action == irma.ActionIssuing {
249
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
250
251
252
253
254
255
256
		_, 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
257
		ir.RemovalCredentialInfoList = irma.CredentialInfoList{}
Sietse Ringers's avatar
Sietse Ringers committed
258
		for _, credreq := range ir.Credentials {
259
			preexistingCredentials := session.client.attrs(credreq.CredentialTypeID)
260
261
262
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
263
264
265
		}
	}

266
	candidates, missing := session.client.CheckSatisfiability(session.request.Disclosure().Disclose)
267
	if len(missing) > 0 {
268
		session.Handler.UnsatisfiableRequest(session.ServerName, missing)
269
270
271
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
272
	// Ask for permission to execute the session
273
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
274
		session.choice = choice
Sietse Ringers's avatar
Sietse Ringers committed
275
		go session.doSession(proceed)
276
	})
277
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
278
	switch session.Action {
279
	case irma.ActionDisclosing:
280
		session.Handler.RequestVerificationPermission(
281
			session.request.(*irma.DisclosureRequest), candidates, session.ServerName, callback)
282
	case irma.ActionSigning:
283
		session.Handler.RequestSignaturePermission(
284
			session.request.(*irma.SignatureRequest), candidates, session.ServerName, callback)
285
	case irma.ActionIssuing:
286
		session.Handler.RequestIssuancePermission(
287
			session.request.(*irma.IssuanceRequest), candidates, session.ServerName, callback)
288
289
290
291
292
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

293
294
295
// 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
296
297
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
298

299
	if !proceed {
300
		session.cancel()
301
302
		return
	}
303
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
304

305
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
306
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
307
		if err != nil {
308
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
309
310
311
312
			return
		}
		session.sendResponse(message)
	} else {
313
		var err error
314
		session.builders, session.attrIndices, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
315
		if err != nil {
316
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
317
		}
318
319
320
		startKeyshareSession(
			session,
			session.Handler,
321
			session.builders,
322
			session.request,
323
			session.client.Configuration,
324
			session.client.keyshareServers,
325
			session.issuerProofNonce,
326
			session.timestamp,
327
		)
328
	}
Sietse Ringers's avatar
Sietse Ringers committed
329
}
330

Sietse Ringers's avatar
Sietse Ringers committed
331
332
type disclosureResponse string

333
334
// 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.
335
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
336
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
337
	var err error
338
339
	var messageJson []byte

340
341
	switch session.Action {
	case irma.ActionSigning:
342
		irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message, session.timestamp)
343
344
345
346
347
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

348
349
350
351
352
353
354
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
355
			var response disclosureResponse
356
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
357
358
359
360
361
362
363
				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
364
		}
365
		log, _ = session.createLogEntry(message) // TODO err
366
	case irma.ActionDisclosing:
367
368
369
		messageJson, err = json.Marshal(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
370
371
			return
		}
372
373
374
375
376
377
378
379
380
381
		if session.IsInteractive() {
			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
			}
382
		}
383
		log, _ = session.createLogEntry(message) // TODO err
384
385
386
387
388
389
	case irma.ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		if err = session.transport.Post("commitments", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
390
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
391
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
392
393
			return
		}
394
		log, _ = session.createLogEntry(message) // TODO err
395
396
	}

397
	_ = session.client.addLogEntry(log) // TODO err
398
	if session.Action == irma.ActionIssuing {
399
		session.client.handler.UpdateAttributes()
400
	}
401
	session.done = true
402
	session.Handler.Success(string(messageJson))
403
404
}

405
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
406
func (session *session) managerSession() {
407
	defer session.recoverFromPanic()
408
409
410
411

	// 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.
412
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
413
	if err != nil {
414
		session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
415
416
		return
	}
417

418
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
419
		if !proceed {
420
			session.Handler.Cancelled() // No need to DELETE session here
421
422
			return
		}
423
		if err := session.client.Configuration.InstallSchemeManager(manager, nil); err != nil {
424
			session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
425
426
			return
		}
427
428

		// Update state and inform user of success
429
		session.client.handler.UpdateConfiguration(
430
431
432
433
			&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
434
435
			},
		)
436
		session.Handler.Success("")
437
438
439
	})
	return
}
440

441
442
443
444
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
445
func (session *session) getBuilders() (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
446
447
448
	var builders gabi.ProofBuilderList
	var err error
	var issuerProofNonce *big.Int
449
450
	var choices irma.DisclosedAttributeIndices

451
	switch session.Action {
452
453
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
454
	case irma.ActionIssuing:
455
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
456
457
	}

458
	return builders, choices, issuerProofNonce, err
459
460
461
462
463
464
465
466
467
}

// 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 {
468
469
	case irma.ActionSigning, irma.ActionDisclosing:
		message, session.timestamp, err = session.client.Proofs(session.choice, session.request)
470
	case irma.ActionIssuing:
471
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest), session.choice)
472
473
474
475
476
477
478
479
480
481
482
483
484
	}

	return message, err
}

// Helper functions

// 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 {
485
			session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorUnknownSchemeManager, Info: id.String()})
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
545
546
547
548
549
550
551
552
553
554
			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
	}

555
556
557
558
559
560
	for _, attrlist := range session.choice.Attributes {
		for _, ai := range attrlist {
			smi = ai.Type.CredentialTypeIdentifier().IssuerIdentifier().SchemeManagerIdentifier()
			if session.client.Configuration.SchemeManagers[smi].Distributed() {
				return true
			}
561
562
563
564
565
566
		}
	}

	return false
}

567
568
// Session lifetime functions

569
570
571
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
572
			session.Handler.Failure(panicToError(e))
573
574
575
576
		}
	}
}

577
578
579
580
581
582
583
584
585
586
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
587
	}
588
	fmt.Println("Panic: " + info)
589
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
590
591
592
}

// Idempotently send DELETE to remote server, returning whether or not we did something
593
func (session *session) delete() bool {
594
	if !session.done {
595
596
597
		if session.IsInteractive() {
			session.transport.Delete()
		}
598
		session.done = true
599
600
601
602
603
		return true
	}
	return false
}

604
func (session *session) fail(err *irma.SessionError) {
605
606
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
607
		session.Handler.Failure(err)
608
609
610
	}
}

611
func (session *session) cancel() {
612
	if session.delete() {
613
		session.Handler.Cancelled()
614
615
616
	}
}

617
func (session *session) Dismiss() {
618
619
	session.cancel()
}
620
621
622
623

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
624
625
626
627
628
629
630
631
632
633
634
635
636
637
	switch session.Action {
	case irma.ActionSigning:
		fallthrough
	case irma.ActionDisclosing:
		session.sendResponse(&irma.Disclosure{
			Proofs:  message.(gabi.ProofList),
			Indices: session.attrIndices,
		})
	case irma.ActionIssuing:
		session.sendResponse(&irma.IssueCommitmentMessage{
			IssueCommitmentMessage: message.(*gabi.IssueCommitmentMessage),
			Indices:                session.attrIndices,
		})
	}
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
}

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)
}