session.go 21.3 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
	"runtime/debug"
9
10
	"strings"

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

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

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

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

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

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

44
45
46
47
48
49
50
51
52
53
54
55
56
57
	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)
	RequestSchemeManagerPermission(manager *irma.SchemeManager,
		callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
58

59
	RequestPin(remainingAttempts int, callback PinHandler)
60
61
}

Sietse Ringers's avatar
Sietse Ringers committed
62
// SessionDismisser can dismiss the current IRMA session.
63
64
65
66
type SessionDismisser interface {
	Dismiss()
}

Sietse Ringers's avatar
Sietse Ringers committed
67
type session struct {
68
69
70
	Action     irma.Action
	Handler    Handler
	Version    *irma.ProtocolVersion
71
	ServerName irma.TranslatedString
72

73
74
75
76
77
	choice      *irma.DisclosureChoice
	attrIndices irma.DisclosedAttributeIndices
	client      *Client
	request     irma.SessionRequest
	done        bool
78

79
	// State for issuance sessions
80
81
82
	issuerProofNonce *big.Int
	builders         gabi.ProofBuilderList

83
84
85
	// State for signature sessions
	timestamp *atum.Timestamp

86
	// These are empty on manual sessions
87
	Hostname  string
88
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
89
	transport *irma.HTTPTransport
90
91
}

92
// We implement the handler for the keyshare protocol
93
var _ keyshareSessionHandler = (*session)(nil)
94

95
96
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
Sietse Ringers's avatar
Sietse Ringers committed
97
	2: {4},
98
}
Sietse Ringers's avatar
Sietse Ringers committed
99
100
101
var minVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][0]}
var maxVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][len(supportedVersions[2])-1]}

102
// Session constructors
103

104
105
// 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.
106
107
108
109
110
111
112
113
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)
	}

114
115
116
117
118
119
120
	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 {
121
122
123
124
125
126
		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
127
128
	}

129
	handler.Failure(&irma.SessionError{Err: errors.New("Session request could not be parsed"), Info: sessionrequest})
130
131
132
133
	return nil
}

// newManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
134
func (client *Client) newManualSession(request irma.SessionRequest, handler Handler, action irma.Action) SessionDismisser {
135
	session := &session{
136
137
138
139
140
		Action:  action,
		Handler: handler,
		client:  client,
		Version: minVersion,
		request: request,
Koen van Ingen's avatar
Koen van Ingen committed
141
	}
142
	session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)
Koen van Ingen's avatar
Koen van Ingen committed
143

144
	session.processSessionInfo()
145
	return session
146
147
}

148
149
150
151
152
153
154
155
156
157
158
159
160
161
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
}

162
163
// newQrSession creates and starts a new interactive IRMA session
func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisser {
164
	u, _ := url.ParseRequestURI(qr.URL) // Qr validator already checked this for errors
165
	session := &session{
166
167
168
169
170
171
		ServerURL: qr.URL,
		Hostname:  u.Hostname(),
		transport: irma.NewHTTPTransport(qr.URL),
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
172
	}
173
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
174

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
	// 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
	}

190
191
	session.transport.SetHeader(irma.MinVersionHeader, minVersion.String())
	session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String())
192
193
194
	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}
195

Sietse Ringers's avatar
Sietse Ringers committed
196
	go session.getSessionInfo()
197
	return session
198
199
}

200
201
// Core session methods

202
// getSessionInfo retrieves the first message in the IRMA protocol (only in interactive sessions)
Sietse Ringers's avatar
Sietse Ringers committed
203
204
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
205

206
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
207

Sietse Ringers's avatar
Sietse Ringers committed
208
	// Get the first IRMA protocol message and parse it
209
	err := session.transport.Get("", session.request)
Sietse Ringers's avatar
Sietse Ringers committed
210
	if err != nil {
211
		session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
212
213
		return
	}
214

215
	session.processSessionInfo()
216
217
}

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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
}

241
242
// 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.
243
func (session *session) processSessionInfo() {
244
245
	defer session.recoverFromPanic()

246
247
	if err := session.checkAndUpateConfiguration(); err != nil {
		session.fail(err.(*irma.SessionError))
248
249
250
		return
	}

251
	baserequest := session.request.Base()
252
	confirmedProtocolVersion := baserequest.ProtocolVersion
253
254
255
256
	if confirmedProtocolVersion != nil {
		session.Version = confirmedProtocolVersion
	} else {
		session.Version = irma.NewVersion(2, 0)
257
		baserequest.ProtocolVersion = session.Version
258
259
	}

260
261
	session.ServerName = serverName(session.Hostname, session.request, session.client.Configuration)

262
	if session.Action == irma.ActionIssuing {
263
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
264
265
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
266
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownIdentifier, Err: err})
Tomas's avatar
Tomas committed
267
268
269
270
			return
		}

		// Calculate singleton credentials to be removed
271
		ir.RemovalCredentialInfoList = irma.CredentialInfoList{}
Sietse Ringers's avatar
Sietse Ringers committed
272
		for _, credreq := range ir.Credentials {
273
			preexistingCredentials := session.client.attrs(credreq.CredentialTypeID)
274
275
276
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
277
278
279
		}
	}

280
	candidates, missing := session.client.CheckSatisfiability(session.request.Disclosure().Disclose)
281
	if len(missing) > 0 {
282
		session.Handler.UnsatisfiableRequest(session.request, session.ServerName, missing)
283
284
285
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
286
	// Ask for permission to execute the session
287
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
288
		session.choice = choice
Sietse Ringers's avatar
Sietse Ringers committed
289
		go session.doSession(proceed)
290
	})
291
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
292
	switch session.Action {
293
	case irma.ActionDisclosing:
294
		session.Handler.RequestVerificationPermission(
295
			session.request.(*irma.DisclosureRequest), candidates, session.ServerName, callback)
296
	case irma.ActionSigning:
297
		session.Handler.RequestSignaturePermission(
298
			session.request.(*irma.SignatureRequest), candidates, session.ServerName, callback)
299
	case irma.ActionIssuing:
300
		session.Handler.RequestIssuancePermission(
301
			session.request.(*irma.IssuanceRequest), candidates, session.ServerName, callback)
302
303
304
305
306
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

307
308
309
// 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
310
311
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
312

313
	if !proceed {
314
		session.cancel()
315
316
		return
	}
317
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
318

319
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
320
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
321
		if err != nil {
322
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
323
324
325
326
			return
		}
		session.sendResponse(message)
	} else {
327
		var err error
328
		session.builders, session.attrIndices, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
329
		if err != nil {
330
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
331
		}
332
333
334
		startKeyshareSession(
			session,
			session.Handler,
335
			session.builders,
336
			session.request,
337
			session.client.Configuration,
338
			session.client.keyshareServers,
339
			session.issuerProofNonce,
340
			session.timestamp,
341
		)
342
	}
Sietse Ringers's avatar
Sietse Ringers committed
343
}
344

Sietse Ringers's avatar
Sietse Ringers committed
345
346
type disclosureResponse string

347
348
// 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.
349
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
350
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
351
	var err error
352
353
	var messageJson []byte

354
355
	switch session.Action {
	case irma.ActionSigning:
356
		irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message, session.timestamp)
357
358
359
360
361
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

362
363
364
365
366
367
368
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
369
			var response disclosureResponse
370
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
371
372
373
374
375
376
377
				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
378
		}
379
		log, _ = session.createLogEntry(message) // TODO err
380
	case irma.ActionDisclosing:
381
382
383
		messageJson, err = json.Marshal(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
384
385
			return
		}
386
387
388
389
390
391
392
393
394
395
		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
			}
396
		}
397
		log, _ = session.createLogEntry(message) // TODO err
398
399
400
401
402
403
	case irma.ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		if err = session.transport.Post("commitments", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
404
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
405
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
406
407
			return
		}
408
		log, _ = session.createLogEntry(message) // TODO err
409
410
	}

411
	_ = session.client.addLogEntry(log) // TODO err
412
	if session.Action == irma.ActionIssuing {
413
		session.client.handler.UpdateAttributes()
414
	}
415
	session.done = true
416
	session.Handler.Success(string(messageJson))
417
418
}

419
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
420
func (session *session) managerSession() {
421
	defer session.recoverFromPanic()
422
423
424
425

	// 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.
426
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
427
	if err != nil {
428
		session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
429
430
		return
	}
431

432
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
433
		if !proceed {
434
			session.Handler.Cancelled() // No need to DELETE session here
435
436
			return
		}
437
		if err := session.client.Configuration.InstallSchemeManager(manager, nil); err != nil {
438
			session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
439
440
			return
		}
441
442

		// Update state and inform user of success
443
		session.client.handler.UpdateConfiguration(
444
445
446
447
			&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
448
449
			},
		)
450
		session.Handler.Success("")
451
452
453
	})
	return
}
454

455
456
457
458
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
459
func (session *session) getBuilders() (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
460
461
462
	var builders gabi.ProofBuilderList
	var err error
	var issuerProofNonce *big.Int
463
464
	var choices irma.DisclosedAttributeIndices

465
	switch session.Action {
466
467
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
468
	case irma.ActionIssuing:
469
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
470
471
	}

472
	return builders, choices, issuerProofNonce, err
473
474
475
476
477
478
479
480
481
}

// 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 {
482
483
	case irma.ActionSigning, irma.ActionDisclosing:
		message, session.timestamp, err = session.client.Proofs(session.choice, session.request)
484
	case irma.ActionIssuing:
485
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest), session.choice)
486
487
488
489
490
491
492
493
494
495
496
	}

	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 {
497
		distributed := session.client.Configuration.SchemeManagers[id].Distributed()
498
499
500
501
502
503
504
505
506
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

507
func (session *session) checkAndUpateConfiguration() error {
508
509
	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
510
	if uerr, ok := err.(*irma.UnknownIdentifierError); ok {
511
		return &irma.SessionError{ErrorType: uerr.ErrorType, Err: uerr}
512
	} else if err != nil {
513
		return &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err}
514
515
516
517
	}
	if downloaded != nil && !downloaded.Empty() {
		session.client.handler.UpdateConfiguration(downloaded)
	}
518
519
520

	// Check if we are enrolled into all involved keyshare servers
	if !session.checkKeyshareEnrollment() {
521
		return &irma.SessionError{ErrorType: irma.ErrorKeyshare}
522
523
	}

524
525
526
527
528
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
}

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

552
553
554
555
556
557
	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
			}
558
559
560
561
562
563
		}
	}

	return false
}

564
565
// Session lifetime functions

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

574
575
576
577
578
579
580
581
582
583
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
584
	}
585
	fmt.Println("Panic: " + info)
586
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info + "\n\n" + string(debug.Stack())}
587
588
589
}

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

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

608
func (session *session) cancel() {
609
	if session.delete() {
610
		session.Handler.Cancelled()
611
612
613
	}
}

614
func (session *session) Dismiss() {
615
616
	session.cancel()
}
617
618
619
620

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
621
622
623
624
625
626
627
628
629
630
631
632
633
634
	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,
		})
	}
635
636
637
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
}

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