session.go 22.8 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/getsentry/raven-go"
13
	"github.com/go-errors/errors"
14
15
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
16
	"github.com/privacybydesign/irmago"
17
18
)

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

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

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

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

	KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int)
42
	KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier)
43
	KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier)
44
	KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier)
45

46
47
48
49
50
51
52
53
54
55
56
57
58
59
	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
60

61
	RequestPin(remainingAttempts int, callback PinHandler)
62
63
}

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

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

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

81
	// State for issuance sessions
82
83
84
	issuerProofNonce *big.Int
	builders         gabi.ProofBuilderList

85
86
87
	// State for signature sessions
	timestamp *atum.Timestamp

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

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

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

107
// Session constructors
108

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

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

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

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

149
	session.processSessionInfo()
150
	return session
151
152
}

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

167
168
// newQrSession creates and starts a new interactive IRMA session
func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisser {
169
170
171
172
173
174
175
176
177
178
179
180
181
	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)
	}

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

192
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
193
	min := minVersion
194

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

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

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

221
222
// Core session methods

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

227
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
228

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

236
	session.processSessionInfo()
237
238
}

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

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

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

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

281
282
	session.ServerName = serverName(session.Hostname, session.request, session.client.Configuration)

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
307
	// Ask for permission to execute the session
308
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
309
		session.choice = choice
Sietse Ringers's avatar
Sietse Ringers committed
310
		go session.doSession(proceed)
311
	})
312
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
313
314
315
316
317
318

	// Handle ClientReturnURL if one is found in the session request
	if session.request.Base().ClientReturnURL != "" {
		session.Handler.ClientReturnURLFound(session.request.Base().ClientReturnURL)
	}

319
	switch session.Action {
320
	case irma.ActionDisclosing:
321
		session.Handler.RequestVerificationPermission(
322
			session.request.(*irma.DisclosureRequest), candidates, session.ServerName, callback)
323
	case irma.ActionSigning:
324
		session.Handler.RequestSignaturePermission(
325
			session.request.(*irma.SignatureRequest), candidates, session.ServerName, callback)
326
	case irma.ActionIssuing:
327
		session.Handler.RequestIssuancePermission(
328
			session.request.(*irma.IssuanceRequest), candidates, session.ServerName, callback)
329
330
331
332
333
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

334
335
336
// 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
337
338
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
339

340
	if !proceed {
341
		session.cancel()
342
343
		return
	}
344
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
345

346
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
347
		message, err := session.getProof()
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
			return
		}
		session.sendResponse(message)
	} else {
354
		var err error
355
		session.builders, session.attrIndices, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
356
		if err != nil {
357
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
358
		}
359
360
361
		startKeyshareSession(
			session,
			session.Handler,
362
			session.builders,
363
			session.request,
364
			session.client.Configuration,
365
			session.client.keyshareServers,
366
			session.issuerProofNonce,
367
			session.timestamp,
368
		)
369
	}
Sietse Ringers's avatar
Sietse Ringers committed
370
}
371

Sietse Ringers's avatar
Sietse Ringers committed
372
373
type disclosureResponse string

374
375
// 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.
376
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
377
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
378
	var err error
379
380
	var messageJson []byte

381
382
	switch session.Action {
	case irma.ActionSigning:
383
		irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message, session.timestamp)
384
385
386
387
388
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

389
390
391
392
393
394
395
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
396
			var response disclosureResponse
397
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
398
399
400
401
402
403
404
				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
405
		}
406
407
408
		log, err = session.createLogEntry(message)
		if err != nil {
			irma.Logger.Warn(errors.WrapPrefix(err, "Failed to create log entry", 0).ErrorStack())
409
			raven.CaptureError(err, nil)
410
		}
411
	case irma.ActionDisclosing:
412
413
414
		messageJson, err = json.Marshal(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
415
416
			return
		}
417
418
419
420
421
422
423
424
425
426
		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
			}
427
		}
428
429
430
		log, err = session.createLogEntry(message)
		if err != nil {
			irma.Logger.Warn(errors.WrapPrefix(err, "Failed to create log entry", 0).ErrorStack())
431
			raven.CaptureError(err, nil)
432
		}
433
434
435
436
437
438
	case irma.ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		if err = session.transport.Post("commitments", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
439
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
440
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
441
442
			return
		}
443
444
445
		log, err = session.createLogEntry(message)
		if err != nil {
			irma.Logger.Warn(errors.WrapPrefix(err, "Failed to create log entry", 0).ErrorStack())
446
			raven.CaptureError(err, nil)
447
		}
448
449
	}

450
451
452
	if err = session.client.storage.AddLogEntry(log); err != nil {
		irma.Logger.Warn(errors.WrapPrefix(err, "Failed to write log entry", 0).ErrorStack())
	}
453
	if session.Action == irma.ActionIssuing {
454
		session.client.handler.UpdateAttributes()
455
	}
456
	session.done = true
457
	session.Handler.Success(string(messageJson))
458
459
}

460
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
461
func (session *session) managerSession() {
462
	defer session.recoverFromPanic()
463
464
465
466

	// 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.
467
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
468
	if err != nil {
469
		session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
470
471
		return
	}
472

473
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
474
		if !proceed {
475
			session.Handler.Cancelled() // No need to DELETE session here
476
477
			return
		}
478
		if err := session.client.Configuration.InstallSchemeManager(manager, nil); err != nil {
479
			session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
480
481
			return
		}
482
483

		// Update state and inform user of success
484
		session.client.handler.UpdateConfiguration(
485
486
487
488
			&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
489
490
			},
		)
491
		session.Handler.Success("")
492
493
494
	})
	return
}
495

496
497
498
499
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
500
func (session *session) getBuilders() (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
501
502
503
	var builders gabi.ProofBuilderList
	var err error
	var issuerProofNonce *big.Int
504
505
	var choices irma.DisclosedAttributeIndices

506
	switch session.Action {
507
508
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
509
	case irma.ActionIssuing:
510
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
511
512
	}

513
	return builders, choices, issuerProofNonce, err
514
515
516
517
518
519
520
521
522
}

// 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 {
523
524
	case irma.ActionSigning, irma.ActionDisclosing:
		message, session.timestamp, err = session.client.Proofs(session.choice, session.request)
525
	case irma.ActionIssuing:
526
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest), session.choice)
527
528
529
530
531
532
533
534
535
536
537
	}

	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 {
538
		distributed := session.client.Configuration.SchemeManagers[id].Distributed()
539
540
541
542
543
544
545
546
547
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

548
func (session *session) checkAndUpateConfiguration() error {
549
550
	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
551
	if uerr, ok := err.(*irma.UnknownIdentifierError); ok {
552
		return &irma.SessionError{ErrorType: uerr.ErrorType, Err: uerr}
553
	} else if err != nil {
554
		return &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err}
555
556
	}
	if downloaded != nil && !downloaded.Empty() {
557
558
559
		if err = session.client.ConfigurationUpdated(downloaded); err != nil {
			return err
		}
560
561
		session.client.handler.UpdateConfiguration(downloaded)
	}
562
563
564

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

568
569
570
571
572
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
}

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

596
597
598
599
600
601
	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
			}
602
603
604
605
606
607
		}
	}

	return false
}

608
609
// Session lifetime functions

610
611
612
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
613
			session.Handler.Failure(panicToError(e))
614
615
616
617
		}
	}
}

618
619
620
621
622
623
624
625
626
627
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
628
	}
629
	fmt.Println("Panic: " + info)
630
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info + "\n\n" + string(debug.Stack())}
631
632
633
}

// Idempotently send DELETE to remote server, returning whether or not we did something
634
func (session *session) delete() bool {
635
	if !session.done {
636
637
638
		if session.IsInteractive() {
			session.transport.Delete()
		}
639
		session.done = true
640
641
642
643
644
		return true
	}
	return false
}

645
func (session *session) fail(err *irma.SessionError) {
646
647
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
648
		session.Handler.Failure(err)
649
650
651
	}
}

652
func (session *session) cancel() {
653
	if session.delete() {
654
		session.Handler.Cancelled()
655
656
657
	}
}

658
func (session *session) Dismiss() {
659
660
	session.cancel()
}
661
662
663
664

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
665
666
667
668
669
670
671
672
673
674
675
676
677
678
	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,
		})
	}
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
}

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