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

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

11
	"github.com/bwesterb/go-atum"
12
	"github.com/go-errors/errors"
13
14
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
15
	"github.com/privacybydesign/irmago"
16
17
)

Sietse Ringers's avatar
Sietse Ringers committed
18
19
20
// This file contains the logic and state of performing IRMA sessions, communicates
// with IRMA API servers, and uses the calling Client to construct messages and replies
// in the IRMA protocol.
Sietse Ringers's avatar
Sietse Ringers committed
21

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

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

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

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

44
45
46
47
48
49
50
51
52
53
54
55
56
57
	RequestIssuancePermission(request *irma.IssuanceRequest,
		candidates [][][]*irma.AttributeIdentifier,
		ServerName irma.TranslatedString,
		callback PermissionHandler)
	RequestVerificationPermission(request *irma.DisclosureRequest,
		candidates [][][]*irma.AttributeIdentifier,
		ServerName irma.TranslatedString,
		callback PermissionHandler)
	RequestSignaturePermission(request *irma.SignatureRequest,
		candidates [][][]*irma.AttributeIdentifier,
		ServerName irma.TranslatedString,
		callback PermissionHandler)
	RequestSchemeManagerPermission(manager *irma.SchemeManager,
		callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
58

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

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

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

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

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

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

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

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

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

105
// Session constructors
106

107
108
// NewSession starts a new IRMA session, given (along with a handler to pass feedback to) a session request.
// When the request is not suitable to start an IRMA session from, it calls the Failure method of the specified Handler.
109
110
111
112
113
114
115
116
func (client *Client) NewSession(sessionrequest string, handler Handler) SessionDismisser {
	bts := []byte(sessionrequest)

	qr := &irma.Qr{}
	if err := irma.UnmarshalValidate(bts, qr); err == nil {
		return client.newQrSession(qr, handler)
	}

117
118
119
120
121
122
123
	schemeRequest := &irma.SchemeManagerRequest{}
	if err := irma.UnmarshalValidate(bts, schemeRequest); err == nil {
		return client.newSchemeSession(schemeRequest, handler)
	}

	sigRequest := &irma.SignatureRequest{}
	if err := irma.UnmarshalValidate(bts, sigRequest); err == nil {
124
125
126
127
128
129
		return client.newManualSession(sigRequest, handler, irma.ActionSigning)
	}

	disclosureRequest := &irma.DisclosureRequest{}
	if err := irma.UnmarshalValidate(bts, disclosureRequest); err == nil {
		return client.newManualSession(disclosureRequest, handler, irma.ActionDisclosing)
Koen van Ingen's avatar
Koen van Ingen committed
130
131
	}

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

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

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

151
152
153
154
155
156
157
158
159
160
161
162
163
164
func (client *Client) newSchemeSession(qr *irma.SchemeManagerRequest, handler Handler) SessionDismisser {
	session := &session{
		ServerURL: qr.URL,
		transport: irma.NewHTTPTransport(qr.URL),
		Action:    irma.ActionSchemeManager,
		Handler:   handler,
		client:    client,
	}
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)

	go session.managerSession()
	return session
}

165
166
// newQrSession creates and starts a new interactive IRMA session
func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisser {
167
168
169
170
171
172
173
174
175
176
177
178
179
	if qr.Type == irma.ActionRedirect {
		newqr := &irma.Qr{}
		if err := irma.NewHTTPTransport("").Post(qr.URL, newqr, struct{}{}); err != nil {
			handler.Failure(&irma.SessionError{ErrorType: irma.ErrorTransport, Err: errors.Wrap(err, 0)})
			return nil
		}
		if newqr.Type == irma.ActionRedirect { // explicitly avoid infinite recursion
			handler.Failure(&irma.SessionError{ErrorType: irma.ErrorInvalidRequest, Err: errors.New("infinite static QR recursion")})
			return nil
		}
		return client.newQrSession(newqr, handler)
	}

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

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

193
194
195
196
197
198
	// Check if the action is one of the supported types
	switch session.Action {
	case irma.ActionDisclosing:
		session.request = &irma.DisclosureRequest{}
	case irma.ActionSigning:
		session.request = &irma.SignatureRequest{}
199
		min = &irma.ProtocolVersion{2, 5} // New ABS format is not backwards compatible with old irma server
200
201
202
203
204
205
206
207
208
	case irma.ActionIssuing:
		session.request = &irma.IssuanceRequest{}
	case irma.ActionUnknown:
		fallthrough
	default:
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
		return nil
	}

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

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

219
220
// Core session methods

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

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

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

234
	session.processSessionInfo()
235
236
}

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
func serverName(hostname string, request irma.SessionRequest, conf *irma.Configuration) irma.TranslatedString {
	sn := irma.NewTranslatedString(&hostname)

	if ir, ok := request.(*irma.IssuanceRequest); ok {
		// If there is only one issuer in the current request, use its name as ServerName
		var iss irma.TranslatedString
		for _, credreq := range ir.Credentials {
			credIssuer := conf.Issuers[credreq.CredentialTypeID.IssuerIdentifier()].Name
			if !reflect.DeepEqual(credIssuer, iss) { // Can't just test pointer equality: credIssuer != iss
				if len(iss) != 0 {
					return sn
				}
				iss = credIssuer
			}
		}
		if len(iss) != 0 {
			return iss
		}
	}

	return sn
}

260
261
// processSessionInfo continues the session after all session state has been received:
// it checks if the session can be performed and asks the user for consent.
262
func (session *session) processSessionInfo() {
263
264
	defer session.recoverFromPanic()

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

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

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

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

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

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

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

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

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

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

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

366
367
// sendResponse sends the proofs of knowledge of the hidden attributes and/or the secret key, or the constructed
// attribute-based signature, to the API server.
368
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
369
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
370
	var err error
371
372
	var messageJson []byte

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

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

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

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

449
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
450
func (session *session) managerSession() {
451
	defer session.recoverFromPanic()
452
453
454
455

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

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

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

485
486
487
488
// Response calculation methods

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

495
	switch session.Action {
496
497
	case irma.ActionSigning, irma.ActionDisclosing:
		builders, choices, session.timestamp, err = session.client.ProofBuilders(session.choice, session.request)
498
	case irma.ActionIssuing:
499
		builders, choices, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest), session.choice)
500
501
	}

502
	return builders, choices, issuerProofNonce, err
503
504
505
506
507
508
509
510
511
}

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

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

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

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

557
558
559
560
561
	if err = session.request.Disclosure().Disclose.Validate(session.client.Configuration); err != nil {
		return &irma.SessionError{ErrorType: irma.ErrorInvalidRequest}
	}

	return nil
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
}

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

585
586
587
588
589
590
	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
			}
591
592
593
594
595
596
		}
	}

	return false
}

597
598
// Session lifetime functions

599
600
601
func (session *session) recoverFromPanic() {
	if e := recover(); e != nil {
		if session.Handler != nil {
602
			session.Handler.Failure(panicToError(e))
603
604
605
606
		}
	}
}

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

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

634
func (session *session) fail(err *irma.SessionError) {
635
636
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
637
		session.Handler.Failure(err)
638
639
640
	}
}

641
func (session *session) cancel() {
642
	if session.delete() {
643
		session.Handler.Cancelled()
644
645
646
	}
}

647
func (session *session) Dismiss() {
648
649
	session.cancel()
}
650
651
652
653

// Keyshare session handler methods

func (session *session) KeyshareDone(message interface{}) {
654
655
656
657
658
659
660
661
662
663
664
665
666
667
	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,
		})
	}
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
}

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