session.go 21.6 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
// Supported protocol versions. Minor version numbers should be sorted.
96
var supportedVersions = map[int][]int{
97
98
99
100
	2: {
		4, // old protocol with legacy session requests
		5, // introduces condiscon feature
	},
101
}
Sietse Ringers's avatar
Sietse Ringers committed
102
103
104
var minVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][0]}
var maxVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][len(supportedVersions[2])-1]}

105
// Session constructors
106

107
108
// 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.
109
110
111
112
113
114
115
116
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)
	}

117
118
119
120
121
122
123
	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 {
124
125
126
127
128
129
		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
130
131
	}

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

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

147
	session.processSessionInfo()
148
	return session
149
150
}

151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
}

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

177
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
178
	min := minVersion
179

180
181
182
183
184
185
	// 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{}
186
		min = &irma.ProtocolVersion{2, 5} // New ABS format is not backwards compatible with old irma server
187
188
189
190
191
192
193
194
195
	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
	}

196
	session.transport.SetHeader(irma.MinVersionHeader, min.String())
197
	session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String())
198
199
200
	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}
201

Sietse Ringers's avatar
Sietse Ringers committed
202
	go session.getSessionInfo()
203
	return session
204
205
}

206
207
// Core session methods

208
// getSessionInfo retrieves the first message in the IRMA protocol (only in interactive sessions)
Sietse Ringers's avatar
Sietse Ringers committed
209
210
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
211

212
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
213

Sietse Ringers's avatar
Sietse Ringers committed
214
	// Get the first IRMA protocol message and parse it
215
	err := session.transport.Get("", session.request)
Sietse Ringers's avatar
Sietse Ringers committed
216
	if err != nil {
217
		session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
218
219
		return
	}
220

221
	session.processSessionInfo()
222
223
}

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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
}

247
248
// 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.
249
func (session *session) processSessionInfo() {
250
251
	defer session.recoverFromPanic()

252
253
	if err := session.checkAndUpateConfiguration(); err != nil {
		session.fail(err.(*irma.SessionError))
254
255
256
		return
	}

257
	baserequest := session.request.Base()
258
	confirmedProtocolVersion := baserequest.ProtocolVersion
259
260
261
262
	if confirmedProtocolVersion != nil {
		session.Version = confirmedProtocolVersion
	} else {
		session.Version = irma.NewVersion(2, 0)
263
		baserequest.ProtocolVersion = session.Version
264
265
	}

266
267
	session.ServerName = serverName(session.Hostname, session.request, session.client.Configuration)

268
	if session.Action == irma.ActionIssuing {
269
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
270
271
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
272
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownIdentifier, Err: err})
Tomas's avatar
Tomas committed
273
274
275
276
			return
		}

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

286
	candidates, missing := session.client.CheckSatisfiability(session.request.Disclosure().Disclose)
287
	if len(missing) > 0 {
288
		session.Handler.UnsatisfiableRequest(session.request, session.ServerName, missing)
289
290
291
		return
	}

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

313
314
315
// 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
316
317
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
318

319
	if !proceed {
320
		session.cancel()
321
322
		return
	}
323
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
324

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

Sietse Ringers's avatar
Sietse Ringers committed
351
352
type disclosureResponse string

353
354
// 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.
355
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
356
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
357
	var err error
358
359
	var messageJson []byte

360
361
	switch session.Action {
	case irma.ActionSigning:
362
		irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message, session.timestamp)
363
364
365
366
367
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

368
369
370
371
372
373
374
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

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

417
	_ = session.client.addLogEntry(log) // TODO err
418
	if session.Action == irma.ActionIssuing {
419
		session.client.handler.UpdateAttributes()
420
	}
421
	session.done = true
422
	session.Handler.Success(string(messageJson))
423
424
}

425
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
426
func (session *session) managerSession() {
427
	defer session.recoverFromPanic()
428
429
430
431

	// 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.
432
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
433
	if err != nil {
434
		session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
435
436
		return
	}
437

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

		// Update state and inform user of success
449
		session.client.handler.UpdateConfiguration(
450
451
452
453
			&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
454
455
			},
		)
456
		session.Handler.Success("")
457
458
459
	})
	return
}
460

461
462
463
464
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
465
func (session *session) getBuilders() (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
466
467
468
	var builders gabi.ProofBuilderList
	var err error
	var issuerProofNonce *big.Int
469
470
	var choices irma.DisclosedAttributeIndices

471
	switch session.Action {
472
473
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
474
	case irma.ActionIssuing:
475
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
476
477
	}

478
	return builders, choices, issuerProofNonce, err
479
480
481
482
483
484
485
486
487
}

// 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 {
488
489
	case irma.ActionSigning, irma.ActionDisclosing:
		message, session.timestamp, err = session.client.Proofs(session.choice, session.request)
490
	case irma.ActionIssuing:
491
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest), session.choice)
492
493
494
495
496
497
498
499
500
501
502
	}

	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 {
503
		distributed := session.client.Configuration.SchemeManagers[id].Distributed()
504
505
506
507
508
509
510
511
512
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

513
func (session *session) checkAndUpateConfiguration() error {
514
515
	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
516
	if uerr, ok := err.(*irma.UnknownIdentifierError); ok {
517
		return &irma.SessionError{ErrorType: uerr.ErrorType, Err: uerr}
518
	} else if err != nil {
519
		return &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err}
520
521
	}
	if downloaded != nil && !downloaded.Empty() {
522
523
524
		if err = session.client.ConfigurationUpdated(downloaded); err != nil {
			return err
		}
525
526
		session.client.handler.UpdateConfiguration(downloaded)
	}
527
528
529

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

533
534
535
536
537
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
}

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

561
562
563
564
565
566
	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
			}
567
568
569
570
571
572
		}
	}

	return false
}

573
574
// Session lifetime functions

575
576
577
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
578
			session.Handler.Failure(panicToError(e))
579
580
581
582
		}
	}
}

583
584
585
586
587
588
589
590
591
592
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
593
	}
594
	fmt.Println("Panic: " + info)
595
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info + "\n\n" + string(debug.Stack())}
596
597
598
}

// Idempotently send DELETE to remote server, returning whether or not we did something
599
func (session *session) delete() bool {
600
	if !session.done {
601
602
603
		if session.IsInteractive() {
			session.transport.Delete()
		}
604
		session.done = true
605
606
607
608
609
		return true
	}
	return false
}

610
func (session *session) fail(err *irma.SessionError) {
611
612
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
613
		session.Handler.Failure(err)
614
615
616
	}
}

617
func (session *session) cancel() {
618
	if session.delete() {
619
		session.Handler.Cancelled()
620
621
622
	}
}

623
func (session *session) Dismiss() {
624
625
	session.cancel()
}
626
627
628
629

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
630
631
632
633
634
635
636
637
638
639
640
641
642
643
	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,
		})
	}
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
674
675
676
677
678
679
}

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