session.go 21.2 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
234
	if err := session.checkAndUpateConfiguration(); err != nil {
		session.fail(err.(*irma.SessionError))
235
236
237
		return
	}

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

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

249
	if session.Action == irma.ActionIssuing {
250
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
251
252
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
253
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownIdentifier, Err: err})
Tomas's avatar
Tomas committed
254
255
256
257
			return
		}

		// Calculate singleton credentials to be removed
258
		ir.RemovalCredentialInfoList = irma.CredentialInfoList{}
Sietse Ringers's avatar
Sietse Ringers committed
259
		for _, credreq := range ir.Credentials {
260
			preexistingCredentials := session.client.attrs(credreq.CredentialTypeID)
261
262
263
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
264
265
266
		}
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

442
443
444
445
// Response calculation methods

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

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

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

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

	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 {
484
		distributed := session.client.Configuration.SchemeManagers[id].Distributed()
485
486
487
488
489
490
491
492
493
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

494
func (session *session) checkAndUpateConfiguration() error {
495
496
	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
497
	if uerr, ok := err.(*irma.UnknownIdentifierError); ok {
498
		return &irma.SessionError{ErrorType: uerr.ErrorType, Err: uerr}
499
	} else if err != nil {
500
		return &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err}
501
502
503
504
	}
	if downloaded != nil && !downloaded.Empty() {
		session.client.handler.UpdateConfiguration(downloaded)
	}
505
506
507

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

511
512
513
514
515
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
}

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

539
540
541
542
543
544
	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
			}
545
546
547
548
549
550
		}
	}

	return false
}

551
552
// Session lifetime functions

553
554
555
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
556
			session.Handler.Failure(panicToError(e))
557
558
559
560
		}
	}
}

561
562
563
564
565
566
567
568
569
570
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
571
	}
572
	fmt.Println("Panic: " + info)
573
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
574
575
576
}

// Idempotently send DELETE to remote server, returning whether or not we did something
577
func (session *session) delete() bool {
578
	if !session.done {
579
580
581
		if session.IsInteractive() {
			session.transport.Delete()
		}
582
		session.done = true
583
584
585
586
587
		return true
	}
	return false
}

588
func (session *session) fail(err *irma.SessionError) {
589
590
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
591
		session.Handler.Failure(err)
592
593
594
	}
}

595
func (session *session) cancel() {
596
	if session.delete() {
597
		session.Handler.Cancelled()
598
599
600
	}
}

601
func (session *session) Dismiss() {
602
603
	session.cancel()
}
604
605
606
607

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
608
609
610
611
612
613
614
615
616
617
618
619
620
621
	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,
		})
	}
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
}

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