session.go 23.9 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
	raven "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
	ClientReturnURLSet(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
	prepRevocation chan error // used when nonrevocation preprocessing is done
81

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

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

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

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

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

108
// Session constructors
109

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

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

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

// newManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
140
func (client *Client) newManualSession(request irma.SessionRequest, handler Handler, action irma.Action) SessionDismisser {
141
	client.PauseJobs()
142

143
	session := &session{
144
145
146
147
148
149
		Action:         action,
		Handler:        handler,
		client:         client,
		Version:        minVersion,
		request:        request,
		prepRevocation: make(chan error),
Koen van Ingen's avatar
Koen van Ingen committed
150
	}
151
	session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)
Koen van Ingen's avatar
Koen van Ingen committed
152

153
	session.processSessionInfo()
154
	return session
155
156
}

157
158
159
160
161
162
163
164
165
166
167
168
169
170
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
}

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

186
	client.PauseJobs()
187

188
	u, _ := url.ParseRequestURI(qr.URL) // Qr validator already checked this for errors
189
	session := &session{
190
191
192
193
194
195
196
		ServerURL:      qr.URL,
		Hostname:       u.Hostname(),
		transport:      irma.NewHTTPTransport(qr.URL),
		Action:         irma.Action(qr.Type),
		Handler:        handler,
		client:         client,
		prepRevocation: make(chan error),
197
	}
198

199
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
200
	min := minVersion
201

202
203
204
205
206
207
	// 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{}
208
		min = &irma.ProtocolVersion{Major: 2, Minor: 5} // New ABS format is not backwards compatible with old irma server
209
210
211
212
213
214
215
216
217
	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
	}

218
	session.transport.SetHeader(irma.MinVersionHeader, min.String())
219
	session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String())
220
221
222
	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}
223

Sietse Ringers's avatar
Sietse Ringers committed
224
	go session.getSessionInfo()
225
	return session
226
227
}

228
229
// Core session methods

230
// getSessionInfo retrieves the first message in the IRMA protocol (only in interactive sessions)
Sietse Ringers's avatar
Sietse Ringers committed
231
232
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
233

234
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
235

Sietse Ringers's avatar
Sietse Ringers committed
236
	// Get the first IRMA protocol message and parse it
237
	err := session.transport.Get("", session.request)
Sietse Ringers's avatar
Sietse Ringers committed
238
	if err != nil {
239
		session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
240
241
		return
	}
242

243
	session.processSessionInfo()
244
245
}

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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
}

269
270
// 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.
271
func (session *session) processSessionInfo() {
272
273
	defer session.recoverFromPanic()

274
	if err := session.checkAndUpdateConfiguration(); err != nil {
275
		session.fail(err.(*irma.SessionError))
276
277
278
		return
	}

279
	baserequest := session.request.Base()
280
	confirmedProtocolVersion := baserequest.ProtocolVersion
281
282
283
284
	if confirmedProtocolVersion != nil {
		session.Version = confirmedProtocolVersion
	} else {
		session.Version = irma.NewVersion(2, 0)
285
		baserequest.ProtocolVersion = session.Version
286
287
	}

288
289
	session.ServerName = serverName(session.Hostname, session.request, session.client.Configuration)

290
	if session.Action == irma.ActionIssuing {
291
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
292
293
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
294
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownIdentifier, Err: err})
Tomas's avatar
Tomas committed
295
296
297
298
			return
		}

		// Calculate singleton credentials to be removed
299
		ir.RemovalCredentialInfoList = irma.CredentialInfoList{}
Sietse Ringers's avatar
Sietse Ringers committed
300
		for _, credreq := range ir.Credentials {
301
			preexistingCredentials := session.client.attrs(credreq.CredentialTypeID)
302
303
304
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
305
306
307
		}
	}

308
	candidates, missing := session.client.CheckSatisfiability(session.request.Disclosure().Disclose)
309
	if len(missing) > 0 {
310
		session.Handler.UnsatisfiableRequest(session.request, session.ServerName, missing)
311
312
313
		return
	}

314
315
	// Prepare and update all revocation state asynchroniously while the user makes her choices
	go func() {
316
		session.prepRevocation <- session.client.NonrevPreprare(session.request)
317
318
	}()

Sietse Ringers's avatar
Sietse Ringers committed
319
	// Ask for permission to execute the session
320
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
321
		session.choice = choice
Sietse Ringers's avatar
Sietse Ringers committed
322
		go session.doSession(proceed)
323
	})
324
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
325
326
327

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

331
	switch session.Action {
332
	case irma.ActionDisclosing:
333
		session.Handler.RequestVerificationPermission(
334
			session.request.(*irma.DisclosureRequest), candidates, session.ServerName, callback)
335
	case irma.ActionSigning:
336
		session.Handler.RequestSignaturePermission(
337
			session.request.(*irma.SignatureRequest), candidates, session.ServerName, callback)
338
	case irma.ActionIssuing:
339
		session.Handler.RequestIssuancePermission(
340
			session.request.(*irma.IssuanceRequest), candidates, session.ServerName, callback)
341
342
343
344
345
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

346
347
348
// 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
349
350
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
351

352
	if !proceed {
353
		session.cancel()
354
355
		return
	}
356
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
357

358
359
360
361
	// wait for revocation preparation to finish
	err := <-session.prepRevocation
	if err != nil {
		session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err}) // TODO error type
362
		return
363
364
	}

365
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
366
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
367
		if err != nil {
368
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
369
370
371
372
			return
		}
		session.sendResponse(message)
	} else {
373
		var err error
374
		session.builders, session.attrIndices, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
375
		if err != nil {
376
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
377
		}
378
379
380
		startKeyshareSession(
			session,
			session.Handler,
381
			session.builders,
382
			session.request,
383
			session.client.Configuration,
384
			session.client.keyshareServers,
385
			session.issuerProofNonce,
386
			session.timestamp,
387
		)
388
	}
Sietse Ringers's avatar
Sietse Ringers committed
389
}
390

Sietse Ringers's avatar
Sietse Ringers committed
391
392
type disclosureResponse string

393
394
// 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.
395
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
396
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
397
	var err error
398
399
	var messageJson []byte

400
401
	switch session.Action {
	case irma.ActionSigning:
402
		irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message, session.timestamp)
403
404
405
406
407
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

408
409
410
411
412
413
414
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
415
			var response disclosureResponse
416
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
417
418
419
420
421
422
423
				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
424
		}
425
426
427
		log, err = session.createLogEntry(message)
		if err != nil {
			irma.Logger.Warn(errors.WrapPrefix(err, "Failed to create log entry", 0).ErrorStack())
428
			raven.CaptureError(err, nil)
429
		}
430
	case irma.ActionDisclosing:
431
432
433
		messageJson, err = json.Marshal(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
434
435
			return
		}
436
437
438
439
440
441
442
443
444
445
		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
			}
446
		}
447
448
449
		log, err = session.createLogEntry(message)
		if err != nil {
			irma.Logger.Warn(errors.WrapPrefix(err, "Failed to create log entry", 0).ErrorStack())
450
			raven.CaptureError(err, nil)
451
		}
452
453
454
455
456
457
	case irma.ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		if err = session.transport.Post("commitments", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
458
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
459
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
460
461
			return
		}
462
463
464
		log, err = session.createLogEntry(message)
		if err != nil {
			irma.Logger.Warn(errors.WrapPrefix(err, "Failed to create log entry", 0).ErrorStack())
465
			raven.CaptureError(err, nil)
466
		}
467
468
	}

469
470
471
	if err = session.client.storage.AddLogEntry(log); err != nil {
		irma.Logger.Warn(errors.WrapPrefix(err, "Failed to write log entry", 0).ErrorStack())
	}
472
	if session.Action == irma.ActionIssuing {
473
		session.client.handler.UpdateAttributes()
474
	}
475
	session.done = true
476
	session.client.nonrevRepopulateCaches(session.request)
477
	session.client.StartJobs()
478
	session.Handler.Success(string(messageJson))
479
480
}

481
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
482
func (session *session) managerSession() {
483
	defer session.recoverFromPanic()
484
485
486
487

	// 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.
488
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
489
	if err != nil {
490
		session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
491
492
		return
	}
493

494
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
495
		if !proceed {
496
			session.Handler.Cancelled() // No need to DELETE session here
497
498
			return
		}
499
		if err := session.client.Configuration.InstallSchemeManager(manager, nil); err != nil {
500
			session.Handler.Failure(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
501
502
			return
		}
503
504

		// Update state and inform user of success
505
		session.client.handler.UpdateConfiguration(
506
507
508
509
			&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
510
511
			},
		)
512
		session.Handler.Success("")
513
514
515
	})
	return
}
516

517
518
519
520
// Response calculation methods

// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
521
func (session *session) getBuilders() (gabi.ProofBuilderList, irma.DisclosedAttributeIndices, *big.Int, error) {
522
523
524
	var builders gabi.ProofBuilderList
	var err error
	var issuerProofNonce *big.Int
525
526
	var choices irma.DisclosedAttributeIndices

527
	switch session.Action {
528
529
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
530
	case irma.ActionIssuing:
531
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
532
533
	}

534
	return builders, choices, issuerProofNonce, err
535
536
537
538
539
540
541
542
543
}

// 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 {
544
545
	case irma.ActionSigning, irma.ActionDisclosing:
		message, session.timestamp, err = session.client.Proofs(session.choice, session.request)
546
	case irma.ActionIssuing:
547
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest), session.choice)
548
549
550
551
552
553
554
555
556
	}

	return message, err
}

// Helper functions

// checkKeyshareEnrollment checks if we are enrolled into all involved keyshare servers,
// and aborts the session if not
557
func (session *session) checkKeyshareEnrollment() bool {
558
	for id := range session.request.Identifiers().SchemeManagers {
559
		distributed := session.client.Configuration.SchemeManagers[id].Distributed()
560
561
562
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.KeyshareEnrollmentMissing(id)
563
			return false
564
565
		}
	}
566
	return true
567
568
}

569
func (session *session) checkAndUpdateConfiguration() error {
570
571
	// Download missing credential types/issuers/public keys from the scheme manager
	downloaded, err := session.client.Configuration.Download(session.request)
572
	if uerr, ok := err.(*irma.UnknownIdentifierError); ok {
573
		return &irma.SessionError{ErrorType: uerr.ErrorType, Err: uerr}
574
	} else if err != nil {
575
		return &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err}
576
577
	}
	if downloaded != nil && !downloaded.Empty() {
578
579
580
		if err = session.client.ConfigurationUpdated(downloaded); err != nil {
			return err
		}
581
582
		session.client.handler.UpdateConfiguration(downloaded)
	}
583
584

	// Check if we are enrolled into all involved keyshare servers
585
586
	if !session.checkKeyshareEnrollment() {
		return &irma.SessionError{ErrorType: irma.ErrorKeyshareUnenrolled}
587
588
	}

589
590
591
592
593
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
}

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

617
618
619
620
621
622
	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
			}
623
624
625
626
627
628
		}
	}

	return false
}

629
630
// Session lifetime functions

631
632
633
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
634
			session.Handler.Failure(panicToError(e))
635
636
637
638
		}
	}
}

639
640
641
642
643
644
645
646
647
648
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
649
	}
650
	fmt.Println("Panic: " + info)
651
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info + "\n\n" + string(debug.Stack())}
652
653
}

654
655
656
657
// finish the session, by sending a DELETE to the server if there is one, and restarting local
// background jobs. This function is idempotent, doing nothing when called a second time. It
// returns whether or not it did something.
func (session *session) finish() bool {
658
	if !session.done {
659
660
661
		if session.IsInteractive() {
			session.transport.Delete()
		}
662
		session.client.nonrevRepopulateCaches(session.request)
663
		session.client.StartJobs()
664
		session.done = true
665
666
667
668
669
		return true
	}
	return false
}

670
func (session *session) fail(err *irma.SessionError) {
671
	if session.finish() && err.ErrorType != irma.ErrorKeyshareUnenrolled {
672
		irma.Logger.Warn("client session error: ", err.Error())
673
		err.Err = errors.Wrap(err.Err, 0)
674
		session.Handler.Failure(err)
675
676
677
	}
}

678
func (session *session) cancel() {
679
	if session.finish() {
680
		session.Handler.Cancelled()
681
682
683
	}
}

684
func (session *session) Dismiss() {
685
686
	session.cancel()
}
687
688
689
690

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
691
692
693
694
695
696
697
698
699
700
701
702
703
704
	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,
		})
	}
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
}

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