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
6
	"fmt"
	"sort"
7
8
	"strings"

9
10
	"math/big"

11
	"github.com/go-errors/errors"
12
	"github.com/mhe/gabi"
13
	"github.com/privacybydesign/irmago"
14
15
)

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

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

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

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

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

	RequestIssuancePermission(request irma.IssuanceRequest, ServerName string, callback PermissionHandler)
	RequestVerificationPermission(request irma.DisclosureRequest, ServerName string, callback PermissionHandler)
	RequestSignaturePermission(request irma.SignatureRequest, ServerName string, callback PermissionHandler)
	RequestSchemeManagerPermission(manager *irma.SchemeManager, callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
44

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

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

Sietse Ringers's avatar
Sietse Ringers committed
53
type session struct {
Koen van Ingen's avatar
Koen van Ingen committed
54
55
	Action  irma.Action
	Handler Handler
56
	Version *irma.ProtocolVersion
57

58
59
60
61
	choice  *irma.DisclosureChoice
	client  *Client
	request irma.SessionRequest
	done    bool
62

63
64
65
66
	// State for issuance protocol
	issuerProofNonce *big.Int
	builders         gabi.ProofBuilderList

67
	// These are empty on manual sessions
68
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
69
70
71
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
72
73
}

74
// We implement the handler for the keyshare protocol
75
var _ keyshareSessionHandler = (*session)(nil)
76

77
78
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
79
	2: {3, 2, 1},
80
81
}

82
// Session constructors
83

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// NewSession starts a new IRMA session, given (along with a handler to pass feedback to)
// either an *irma.QR; a string that contains a JSON-serialized irma.QR;
// or a string that contains a serialized *irma.SignatureRequest.
// In any other case it calls the Failure method of the specified Handler.
func (client *Client) NewSession(info interface{}, handler Handler) SessionDismisser {
	parsed := map[string]interface{}{}

	switch x := info.(type) {
	case *irma.Qr: // Just start the session directly
		return client.newQrSession(x, handler)
	case irma.Qr:
		return client.newQrSession(&x, handler)

	// We assume the string contains a JSON object
	// Deserialize it into a temp to see which fields it contains, and infer from that what kind of object it is
	case string:
		bts := []byte(x)
		if err := json.Unmarshal(bts, &parsed); err != nil {
			handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
			return nil
		}

		if _, isqr := parsed["irmaqr"]; isqr {
			qr := &irma.Qr{}
			if err := json.Unmarshal(bts, qr); err != nil {
				handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
				return nil
			}
			return client.newQrSession(qr, handler)
		}

		if _, isSigRequest := parsed["message"]; isSigRequest {
			sigrequest := &irma.SignatureRequest{}
			if err := json.Unmarshal([]byte(x), sigrequest); err != nil {
				handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
				return nil
			}
			return client.newManualSession(sigrequest, handler)
		}
Koen van Ingen's avatar
Koen van Ingen committed
123
124
	}

125
126
127
128
129
130
	handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Info specified of unsupported type")})
	return nil
}

// newManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
func (client *Client) newManualSession(sigrequest *irma.SignatureRequest, handler Handler) SessionDismisser {
131
	session := &session{
132
133
134
135
136
		Action:  irma.ActionSigning, // TODO hardcoded for now
		Handler: handler,
		client:  client,
		Version: irma.NewVersion(2, 0), // TODO hardcoded for now
		request: sigrequest,
Koen van Ingen's avatar
Koen van Ingen committed
137
138
139
140
	}

	session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)

141
	if !session.checkAndUpateConfiguration() {
142
		return nil
143
144
	}

145
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
146
	if len(missing) > 0 {
147
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
148
		return nil
149
	}
150
	session.request.SetCandidates(candidates)
151
152
153
154

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
155
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
156
		go session.doSession(proceed)
157
158
	})
	session.Handler.RequestSignaturePermission(
159
		*session.request.(*irma.SignatureRequest), "E-mail request", callback)
160
161

	return session
162
163
}

164
165
// newQrSession creates and starts a new interactive IRMA session
func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisser {
166
	session := &session{
167
		ServerURL: qr.URL,
168
		transport: irma.NewHTTPTransport(qr.URL),
169
170
171
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
172
	}
173
174
175
176
177
178

	if session.Action == irma.ActionSchemeManager {
		go session.managerSession()
		return session
	}

179
180
	version, err := calcVersion(qr)
	if err != nil {
181
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
182
		return nil
183
	}
184
185
	session.Version = version
	session.transport.SetHeader("X-IRMA-ProtocolVersion", version.String())
186
187
188

	// Check if the action is one of the supported types
	switch session.Action {
189
190
191
192
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
193
194
		fallthrough
	default:
195
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
196
		return nil
197
198
199
200
201
202
	}

	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}

Sietse Ringers's avatar
Sietse Ringers committed
203
	go session.getSessionInfo()
204

205
	return session
206
207
}

208
209
// Core session methods

Sietse Ringers's avatar
Sietse Ringers committed
210
// getSessionInfo retrieves the first message in the IRMA protocol, checks if we can perform
211
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
212
213
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
214

215
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
216

Sietse Ringers's avatar
Sietse Ringers committed
217
	// Get the first IRMA protocol message and parse it
218
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
219
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
220
	if Err != nil {
221
		session.fail(Err.(*irma.SessionError))
222
223
224
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
225
	var err error
226
	session.jwt, err = irma.ParseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
227
	if err != nil {
228
		session.fail(&irma.SessionError{ErrorType: irma.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
229
230
		return
	}
231
232
233
234
	session.request = session.jwt.SessionRequest()
	session.request.SetContext(session.info.Context)
	session.request.SetNonce(session.info.Nonce)
	session.request.SetVersion(session.Version)
235
	if session.Action == irma.ActionIssuing {
236
		ir := session.request.(*irma.IssuanceRequest)
237
		// Store which public keys the server will use
238
		for _, credreq := range ir.Credentials {
239
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
240
241
242
		}
	}

243
	if !session.checkAndUpateConfiguration() {
244
245
246
		return
	}

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

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

264
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
265
	if len(missing) > 0 {
266
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
267
268
		return
	}
269
	session.request.SetCandidates(candidates)
270

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

293
294
295
// doSession performs the session: it computes all proofs of knowledge, constructs credentials in case of issuance,
// asks for the pin and performs the keyshare session, and finishes the session by either POSTing the result to the
// API server or returning it to the caller (in case of interactive and noninteractive sessions, respectively).
Sietse Ringers's avatar
Sietse Ringers committed
296
297
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
298

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

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

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

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

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

347
348
349
350
351
352
		irmaSignature, err := request.SignatureFromMessage(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

353
354
355
356
357
358
359
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

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

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

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

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

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

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

439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
func (session *session) getBuilders() (gabi.ProofBuilderList, *big.Int, error) {
	var builders gabi.ProofBuilderList
	var err error

	var issuerProofNonce *big.Int
	switch session.Action {
	case irma.ActionSigning:
		builders, err = session.client.ProofBuilders(session.choice, session.request, true)
	case irma.ActionDisclosing:
		builders, err = session.client.ProofBuilders(session.choice, session.request, false)
	case irma.ActionIssuing:
		builders, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest))
	}

	return builders, issuerProofNonce, err
}

// 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 {
	case irma.ActionSigning:
		message, err = session.client.Proofs(session.choice, session.request, true)
	case irma.ActionDisclosing:
		message, err = session.client.Proofs(session.choice, session.request, false)
	case irma.ActionIssuing:
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest))
	}

	return message, err
}

// Helper functions

func calcVersion(qr *irma.Qr) (*irma.ProtocolVersion, error) {
	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
	for k := range supportedVersions {
		keys = append(keys, k)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(keys)))
	for _, major := range keys {
		for _, minor := range supportedVersions[major] {
			aboveMinimum := major > qr.ProtocolVersion.Major || (major == qr.ProtocolVersion.Major && minor >= qr.ProtocolVersion.Minor)
			underMaximum := major < qr.ProtocolMaxVersion.Major || (major == qr.ProtocolMaxVersion.Major && minor <= qr.ProtocolMaxVersion.Minor)
			if aboveMinimum && underMaximum {
				return irma.NewVersion(major, minor), nil
			}
		}
	}
	return nil, fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion.String(), qr.ProtocolMaxVersion.String())
}

// checkKeyshareEnrollment checks if we are enrolled into all involved keyshare servers,
// and aborts the session if not
func (session *session) checkKeyshareEnrollment() bool {
	for id := range session.request.Identifiers().SchemeManagers {
		manager, ok := session.client.Configuration.SchemeManagers[id]
		if !ok {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorUnknownSchemeManager, Info: id.String()})
			return false
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

func (session *session) checkAndUpateConfiguration() bool {
	for id := range session.request.Identifiers().SchemeManagers {
		manager, contains := session.client.Configuration.SchemeManagers[id]
		if !contains {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorUnknownSchemeManager,
				Info:      id.String(),
			})
			return false
		}
		if !manager.Valid {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorInvalidSchemeManager,
				Info:      string(manager.Status),
			})
			return false
		}
	}

	// Check if we are enrolled into all involved keyshare servers
	if !session.checkKeyshareEnrollment() {
		return false
	}

	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
	if err != nil {
		session.fail(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
		return false
	}
	if downloaded != nil && !downloaded.Empty() {
		session.client.handler.UpdateConfiguration(downloaded)
	}
	return true
}

// IsInteractive returns whether this session uses an API server or not.
func (session *session) IsInteractive() bool {
	return session.ServerURL != ""
}

// Distributed returns whether or not this session involves a keyshare server.
func (session *session) Distributed() bool {
	var smi irma.SchemeManagerIdentifier
	if session.Action == irma.ActionIssuing {
		for _, credreq := range session.request.(*irma.IssuanceRequest).Credentials {
			smi = credreq.CredentialTypeID.IssuerIdentifier().SchemeManagerIdentifier()
			if session.client.Configuration.SchemeManagers[smi].Distributed() {
				return true
			}
		}
	}

	if session.choice == nil || session.choice.Attributes == nil {
		return false
	}

	for _, ai := range session.choice.Attributes {
		smi = ai.Type.CredentialTypeIdentifier().IssuerIdentifier().SchemeManagerIdentifier()
		if session.client.Configuration.SchemeManagers[smi].Distributed() {
			return true
		}
	}

	return false
}

585
586
// Session lifetime functions

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

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

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

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

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

635
func (session *session) Dismiss() {
636
637
	session.cancel()
}
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

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