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

import (
Koen van Ingen's avatar
Koen van Ingen committed
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"fmt"
6
	"net/url"
7
	"reflect"
8
	"runtime/debug"
9
10
	"strings"

11
	"github.com/bwesterb/go-atum"
12
	"github.com/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
34
35
	Success(result string)
	Cancelled()
	Failure(err *irma.SessionError)
36
37
	UnsatisfiableRequest(request irma.SessionRequest,
		ServerName irma.TranslatedString,
38
		missing MissingAttributes)
39
40

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

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

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

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

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

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

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

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

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

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

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

106
// Session constructors
107

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

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

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

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

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

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

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

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

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

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

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

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

220
221
// Core session methods

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

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

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

235
	session.processSessionInfo()
236
237
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

443
444
445
	if err = session.client.storage.AddLogEntry(log); err != nil {
		irma.Logger.Warn(errors.WrapPrefix(err, "Failed to write log entry", 0).ErrorStack())
	}
446
	if session.Action == irma.ActionIssuing {
447
		session.client.handler.UpdateAttributes()
448
	}
449
	session.done = true
450
	session.Handler.Success(string(messageJson))
451
452
}

453
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
454
func (session *session) managerSession() {
455
	defer session.recoverFromPanic()
456
457
458
459

	// 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.
460
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
461
	if err != nil {
462
		session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
463
464
		return
	}
465

466
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
467
		if !proceed {
468
			session.Handler.Cancelled() // No need to DELETE session here
469
470
			return
		}
471
		if err := session.client.Configuration.InstallSchemeManager(manager, nil); err != nil {
472
			session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
473
474
			return
		}
475
476

		// Update state and inform user of success
477
		session.client.handler.UpdateConfiguration(
478
479
480
481
			&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
482
483
			},
		)
484
		session.Handler.Success("")
485
486
487
	})
	return
}
488

489
490
491
492
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
493
func (session *session) getBuilders() (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
494
495
496
	var builders gabi.ProofBuilderList
	var err error
	var issuerProofNonce *big.Int
497
498
	var choices irma.DisclosedAttributeIndices

499
	switch session.Action {
500
501
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
502
	case irma.ActionIssuing:
503
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
504
505
	}

506
	return builders, choices, issuerProofNonce, err
507
508
509
510
511
512
513
514
515
}

// 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 {
516
517
	case irma.ActionSigning, irma.ActionDisclosing:
		message, session.timestamp, err = session.client.Proofs(session.choice, session.request)
518
	case irma.ActionIssuing:
519
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest), session.choice)
520
521
522
523
524
525
526
527
528
529
530
	}

	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 {
531
		distributed := session.client.Configuration.SchemeManagers[id].Distributed()
532
533
534
535
536
537
538
539
540
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
			return false
		}
	}
	return true
}

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

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

561
562
563
564
565
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
}

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

589
590
591
592
593
594
	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
			}
595
596
597
598
599
600
		}
	}

	return false
}

601
602
// Session lifetime functions

603
604
605
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
606
			session.Handler.Failure(panicToError(e))
607
608
609
610
		}
	}
}

611
612
613
614
615
616
617
618
619
620
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
621
	}
622
	fmt.Println("Panic: " + info)
623
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info + "\n\n" + string(debug.Stack())}
624
625
626
}

// Idempotently send DELETE to remote server, returning whether or not we did something
627
func (session *session) delete() bool {
628
	if !session.done {
629
630
631
		if session.IsInteractive() {
			session.transport.Delete()
		}
632
		session.done = true
633
634
635
636
637
		return true
	}
	return false
}

638
func (session *session) fail(err *irma.SessionError) {
639
640
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
641
		session.Handler.Failure(err)
642
643
644
	}
}

645
func (session *session) cancel() {
646
	if session.delete() {
647
		session.Handler.Cancelled()
648
649
650
	}
}

651
func (session *session) Dismiss() {
652
653
	session.cancel()
}
654
655
656
657

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
658
659
660
661
662
663
664
665
666
667
668
669
670
671
	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,
		})
	}
672
673
674
675
676
677
678
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
}

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