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

import (
Koen van Ingen's avatar
Koen van Ingen committed
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"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
	UnsatisfiableRequest(request irma.SessionRequest,
		ServerName irma.TranslatedString,
37
		missing MissingAttributes)
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
168
169
170
171
172
173
174
175
176
177
178
179
	if qr.Type == irma.ActionRedirect {
		newqr := &irma.Qr{}
		if err := irma.NewHTTPTransport("").Post(qr.URL, newqr, struct{}{}); err != nil {
			handler.Failure(&irma.SessionError{ErrorType: irma.ErrorTransport, Err: errors.Wrap(err, 0)})
			return nil
		}
		if newqr.Type == irma.ActionRedirect { // explicitly avoid infinite recursion
			handler.Failure(&irma.SessionError{ErrorType: irma.ErrorInvalidRequest, Err: errors.New("infinite static QR recursion")})
			return nil
		}
		return client.newQrSession(newqr, handler)
	}

180
	u, _ := url.ParseRequestURI(qr.URL) // Qr validator already checked this for errors
181
	session := &session{
182
183
184
185
186
187
		ServerURL: qr.URL,
		Hostname:  u.Hostname(),
		transport: irma.NewHTTPTransport(qr.URL),
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
188
	}
189

190
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
191
	min := minVersion
192

193
194
195
196
197
198
	// 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{}
199
		min = &irma.ProtocolVersion{2, 5} // New ABS format is not backwards compatible with old irma server
200
201
202
203
204
205
206
207
208
	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
	}

209
	session.transport.SetHeader(irma.MinVersionHeader, min.String())
210
	session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String())
211
212
213
	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}
214

Sietse Ringers's avatar
Sietse Ringers committed
215
	go session.getSessionInfo()
216
	return session
217
218
}

219
220
// Core session methods

221
// getSessionInfo retrieves the first message in the IRMA protocol (only in interactive sessions)
Sietse Ringers's avatar
Sietse Ringers committed
222
223
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
224

225
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
226

Sietse Ringers's avatar
Sietse Ringers committed
227
	// Get the first IRMA protocol message and parse it
228
	err := session.transport.Get("", session.request)
Sietse Ringers's avatar
Sietse Ringers committed
229
	if err != nil {
230
		session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
231
232
		return
	}
233

234
	session.processSessionInfo()
235
236
}

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
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
}

260
261
// 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.
262
func (session *session) processSessionInfo() {
263
264
	defer session.recoverFromPanic()

265
266
	if err := session.checkAndUpateConfiguration(); err != nil {
		session.fail(err.(*irma.SessionError))
267
268
269
		return
	}

270
	baserequest := session.request.Base()
271
	confirmedProtocolVersion := baserequest.ProtocolVersion
272
273
274
275
	if confirmedProtocolVersion != nil {
		session.Version = confirmedProtocolVersion
	} else {
		session.Version = irma.NewVersion(2, 0)
276
		baserequest.ProtocolVersion = session.Version
277
278
	}

279
280
	session.ServerName = serverName(session.Hostname, session.request, session.client.Configuration)

281
	if session.Action == irma.ActionIssuing {
282
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
283
284
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
285
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownIdentifier, Err: err})
Tomas's avatar
Tomas committed
286
287
288
289
			return
		}

		// Calculate singleton credentials to be removed
290
		ir.RemovalCredentialInfoList = irma.CredentialInfoList{}
Sietse Ringers's avatar
Sietse Ringers committed
291
		for _, credreq := range ir.Credentials {
292
			preexistingCredentials := session.client.attrs(credreq.CredentialTypeID)
293
294
295
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
296
297
298
		}
	}

299
	candidates, missing := session.client.CheckSatisfiability(session.request.Disclosure().Disclose)
300
	if len(missing) > 0 {
301
		session.Handler.UnsatisfiableRequest(session.request, session.ServerName, missing)
302
303
304
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
305
	// Ask for permission to execute the session
306
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
307
		session.choice = choice
Sietse Ringers's avatar
Sietse Ringers committed
308
		go session.doSession(proceed)
309
	})
310
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
311
	switch session.Action {
312
	case irma.ActionDisclosing:
313
		session.Handler.RequestVerificationPermission(
314
			session.request.(*irma.DisclosureRequest), candidates, session.ServerName, callback)
315
	case irma.ActionSigning:
316
		session.Handler.RequestSignaturePermission(
317
			session.request.(*irma.SignatureRequest), candidates, session.ServerName, callback)
318
	case irma.ActionIssuing:
319
		session.Handler.RequestIssuancePermission(
320
			session.request.(*irma.IssuanceRequest), candidates, session.ServerName, callback)
321
322
323
324
325
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

326
327
328
// 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
329
330
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
331

332
	if !proceed {
333
		session.cancel()
334
335
		return
	}
336
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
337

338
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
339
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
340
		if err != nil {
341
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
342
343
344
345
			return
		}
		session.sendResponse(message)
	} else {
346
		var err error
347
		session.builders, session.attrIndices, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
348
		if err != nil {
349
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
350
		}
351
352
353
		startKeyshareSession(
			session,
			session.Handler,
354
			session.builders,
355
			session.request,
356
			session.client.Configuration,
357
			session.client.keyshareServers,
358
			session.issuerProofNonce,
359
			session.timestamp,
360
		)
361
	}
Sietse Ringers's avatar
Sietse Ringers committed
362
}
363

Sietse Ringers's avatar
Sietse Ringers committed
364
365
type disclosureResponse string

366
367
// 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.
368
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
369
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
370
	var err error
371
372
	var messageJson []byte

373
374
	switch session.Action {
	case irma.ActionSigning:
375
		irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message, session.timestamp)
376
377
378
379
380
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

381
382
383
384
385
386
387
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
388
			var response disclosureResponse
389
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
390
391
392
393
394
395
396
				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
397
		}
398
		log, _ = session.createLogEntry(message) // TODO err
399
	case irma.ActionDisclosing:
400
401
402
		messageJson, err = json.Marshal(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
403
404
			return
		}
405
406
407
408
409
410
411
412
413
414
		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
			}
415
		}
416
		log, _ = session.createLogEntry(message) // TODO err
417
418
419
420
421
422
	case irma.ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		if err = session.transport.Post("commitments", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
423
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
424
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
425
426
			return
		}
427
		log, _ = session.createLogEntry(message) // TODO err
428
429
	}

430
	_ = session.client.addLogEntry(log) // TODO err
431
	if session.Action == irma.ActionIssuing {
432
		session.client.handler.UpdateAttributes()
433
	}
434
	session.done = true
435
	session.Handler.Success(string(messageJson))
436
437
}

438
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
439
func (session *session) managerSession() {
440
	defer session.recoverFromPanic()
441
442
443
444

	// 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.
445
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
446
	if err != nil {
447
		session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
448
449
		return
	}
450

451
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
452
		if !proceed {
453
			session.Handler.Cancelled() // No need to DELETE session here
454
455
			return
		}
456
		if err := session.client.Configuration.InstallSchemeManager(manager, nil); err != nil {
457
			session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
458
459
			return
		}
460
461

		// Update state and inform user of success
462
		session.client.handler.UpdateConfiguration(
463
464
465
466
			&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
467
468
			},
		)
469
		session.Handler.Success("")
470
471
472
	})
	return
}
473

474
475
476
477
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
478
func (session *session) getBuilders() (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
479
480
481
	var builders gabi.ProofBuilderList
	var err error
	var issuerProofNonce *big.Int
482
483
	var choices irma.DisclosedAttributeIndices

484
	switch session.Action {
485
486
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
487
	case irma.ActionIssuing:
488
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
489
490
	}

491
	return builders, choices, issuerProofNonce, err
492
493
494
495
496
497
498
499
500
}

// 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 {
501
502
	case irma.ActionSigning, irma.ActionDisclosing:
		message, session.timestamp, err = session.client.Proofs(session.choice, session.request)
503
	case irma.ActionIssuing:
504
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest), session.choice)
505
506
507
508
509
510
511
512
513
514
515
	}

	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 {
516
		distributed := session.client.Configuration.SchemeManagers[id].Distributed()
517
518
519
520
521
522
523
524
525
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

526
func (session *session) checkAndUpateConfiguration() error {
527
528
	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
529
	if uerr, ok := err.(*irma.UnknownIdentifierError); ok {
530
		return &irma.SessionError{ErrorType: uerr.ErrorType, Err: uerr}
531
	} else if err != nil {
532
		return &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err}
533
534
	}
	if downloaded != nil && !downloaded.Empty() {
535
536
537
		if err = session.client.ConfigurationUpdated(downloaded); err != nil {
			return err
		}
538
539
		session.client.handler.UpdateConfiguration(downloaded)
	}
540
541
542

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

546
547
548
549
550
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
}

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

574
575
576
577
578
579
	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
			}
580
581
582
583
584
585
		}
	}

	return false
}

586
587
// Session lifetime functions

588
589
590
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
591
			session.Handler.Failure(panicToError(e))
592
593
594
595
		}
	}
}

596
597
598
599
600
601
602
603
604
605
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
606
	}
607
	fmt.Println("Panic: " + info)
608
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info + "\n\n" + string(debug.Stack())}
609
610
611
}

// Idempotently send DELETE to remote server, returning whether or not we did something
612
func (session *session) delete() bool {
613
	if !session.done {
614
615
616
		if session.IsInteractive() {
			session.transport.Delete()
		}
617
		session.done = true
618
619
620
621
622
		return true
	}
	return false
}

623
func (session *session) fail(err *irma.SessionError) {
624
625
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
626
		session.Handler.Failure(err)
627
628
629
	}
}

630
func (session *session) cancel() {
631
	if session.delete() {
632
		session.Handler.Cancelled()
633
634
635
	}
}

636
func (session *session) Dismiss() {
637
638
	session.cancel()
}
639
640
641
642

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
643
644
645
646
647
648
649
650
651
652
653
654
655
656
	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,
		})
	}
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
}

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