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

import (
Koen van Ingen's avatar
Koen van Ingen committed
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
6
	"fmt"
	"sort"
7
8
	"strings"

9
10
	"math/big"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

82
func calcVersion(qr *irma.Qr) (*irma.ProtocolVersion, error) {
83
84
	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
85
	for k := range supportedVersions {
86
87
88
89
90
		keys = append(keys, k)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(keys)))
	for _, major := range keys {
		for _, minor := range supportedVersions[major] {
91
92
			aboveMinimum := major > qr.ProtocolVersion.Major || (major == qr.ProtocolVersion.Major && minor >= qr.ProtocolVersion.Minor)
			underMaximum := major < qr.ProtocolMaxVersion.Major || (major == qr.ProtocolMaxVersion.Major && minor <= qr.ProtocolMaxVersion.Minor)
93
			if aboveMinimum && underMaximum {
94
				return irma.NewVersion(major, minor), nil
95
96
97
			}
		}
	}
98
	return nil, fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion.String(), qr.ProtocolMaxVersion.String())
99
100
}

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

106
107
108
// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
func (session *session) getBuilders() (gabi.ProofBuilderList, *big.Int, error) {
Koen van Ingen's avatar
Koen van Ingen committed
109
110
111
	var builders gabi.ProofBuilderList
	var err error

112
	var issuerProofNonce *big.Int
Koen van Ingen's avatar
Koen van Ingen committed
113
114
	switch session.Action {
	case irma.ActionSigning:
115
		builders, err = session.client.ProofBuilders(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
116
	case irma.ActionDisclosing:
117
		builders, err = session.client.ProofBuilders(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
118
	case irma.ActionIssuing:
119
		builders, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
120
	}
121

122
	return builders, issuerProofNonce, err
123
124
}

125
126
// getProofs computes the disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively) to be sent to the server.
Koen van Ingen's avatar
Koen van Ingen committed
127
128
129
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
130

Koen van Ingen's avatar
Koen van Ingen committed
131
132
	switch session.Action {
	case irma.ActionSigning:
133
		message, err = session.client.Proofs(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
134
	case irma.ActionDisclosing:
135
		message, err = session.client.Proofs(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
136
	case irma.ActionIssuing:
137
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
138
	}
139

Koen van Ingen's avatar
Koen van Ingen committed
140
141
	return message, err
}
142

143
144
// checkKeyshareEnrollment checks if we are enrolled into all involved keyshare servers,
// and aborts the session if not
Koen van Ingen's avatar
Koen van Ingen committed
145
func (session *session) checkKeyshareEnrollment() bool {
146
	for id := range session.request.Identifiers().SchemeManagers {
147
148
149
		manager, ok := session.client.Configuration.SchemeManagers[id]
		if !ok {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorUnknownSchemeManager, Info: id.String()})
Koen van Ingen's avatar
Koen van Ingen committed
150
			return false
151
152
153
154
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
155
			session.Handler.KeyshareEnrollmentMissing(id)
Koen van Ingen's avatar
Koen van Ingen committed
156
			return false
157
158
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
159
160
161
	return true
}

Sietse Ringers's avatar
Sietse Ringers committed
162
func (session *session) recoverFromPanic() {
Koen van Ingen's avatar
Koen van Ingen committed
163
164
165
166
167
168
169
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

170
func (session *session) checkAndUpateConfiguration() bool {
171
	for id := range session.request.Identifiers().SchemeManagers {
172
		manager, contains := session.client.Configuration.SchemeManagers[id]
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
		if !contains {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorUnknownSchemeManager,
				Info:      id.String(),
			})
			return false
		}
		if !manager.Valid {
			session.fail(&irma.SessionError{
				ErrorType: irma.ErrorInvalidSchemeManager,
				Info:      string(manager.Status),
			})
			return false
		}
	}

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

	// Download missing credential types/issuers/public keys from the scheme manager
195
	downloaded, err := session.client.Configuration.Download(session.request)
196
	if err != nil {
197
198
199
		session.fail(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
		return false
	}
200
201
202
	if downloaded != nil && !downloaded.Empty() {
		session.client.handler.UpdateConfiguration(downloaded)
	}
203
204
205
	return true
}

Sietse Ringers's avatar
Sietse Ringers committed
206
// NewManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
Koen van Ingen's avatar
Koen van Ingen committed
207
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
	var err error
	sigrequest := &irma.SignatureRequest{}
	if err = json.Unmarshal([]byte(sigrequestJSONString), sigrequest); err != nil {
Koen van Ingen's avatar
Koen van Ingen committed
211
212
213
214
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

215
	session := &session{
216
217
218
219
220
		Action:  irma.ActionSigning, // TODO hardcoded for now
		Handler: handler,
		client:  client,
		Version: irma.NewVersion(2, 0), // TODO hardcoded for now
		request: sigrequest,
Koen van Ingen's avatar
Koen van Ingen committed
221
222
223
224
	}

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

225
	if !session.checkAndUpateConfiguration() {
226
227
228
		return
	}

229
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
230
	if len(missing) > 0 {
231
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
232
233
		return
	}
234
	session.request.SetCandidates(candidates)
235
236
237
238

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
239
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
240
		go session.doSession(proceed)
241
242
	})
	session.Handler.RequestSignaturePermission(
243
		*session.request.(*irma.SignatureRequest), "E-mail request", callback)
244
245
}

246
// NewSession creates and starts a new interactive IRMA session
247
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
248
	session := &session{
249
		ServerURL: qr.URL,
250
		transport: irma.NewHTTPTransport(qr.URL),
251
252
253
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
254
	}
255
256
257
258
259
260

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

261
262
	version, err := calcVersion(qr)
	if err != nil {
263
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
264
		return nil
265
	}
266
267
	session.Version = version
	session.transport.SetHeader("X-IRMA-ProtocolVersion", version.String())
268
269
270

	// Check if the action is one of the supported types
	switch session.Action {
271
272
273
274
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
275
276
		fallthrough
	default:
277
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
278
		return nil
279
280
281
282
283
284
	}

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

Sietse Ringers's avatar
Sietse Ringers committed
285
	go session.getSessionInfo()
286

287
	return session
288
289
}

Sietse Ringers's avatar
Sietse Ringers committed
290
// getSessionInfo retrieves the first message in the IRMA protocol, checks if we can perform
291
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
292
293
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
294

295
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
296

Sietse Ringers's avatar
Sietse Ringers committed
297
	// Get the first IRMA protocol message and parse it
298
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
299
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
300
	if Err != nil {
301
		session.fail(Err.(*irma.SessionError))
302
303
304
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
305
	var err error
306
	session.jwt, err = irma.ParseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
307
	if err != nil {
308
		session.fail(&irma.SessionError{ErrorType: irma.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
309
310
		return
	}
311
312
313
314
	session.request = session.jwt.SessionRequest()
	session.request.SetContext(session.info.Context)
	session.request.SetNonce(session.info.Nonce)
	session.request.SetVersion(session.Version)
315
	if session.Action == irma.ActionIssuing {
316
		ir := session.request.(*irma.IssuanceRequest)
317
		// Store which public keys the server will use
318
		for _, credreq := range ir.Credentials {
319
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
320
321
322
		}
	}

323
	if !session.checkAndUpateConfiguration() {
324
325
326
		return
	}

327
	if session.Action == irma.ActionIssuing {
328
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
329
330
331
332
333
334
335
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
			return
		}

		// Calculate singleton credentials to be removed
Sietse Ringers's avatar
Sietse Ringers committed
336
		for _, credreq := range ir.Credentials {
337
338
339
340
			preexistingCredentials := session.client.attrs(*credreq.CredentialTypeID)
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
341
342
343
		}
	}

344
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
345
	if len(missing) > 0 {
346
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
347
348
		return
	}
349
	session.request.SetCandidates(candidates)
350

Sietse Ringers's avatar
Sietse Ringers committed
351
	// Ask for permission to execute the session
352
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
353
		session.choice = choice
354
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
355
		go session.doSession(proceed)
356
	})
357
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
358
	switch session.Action {
359
	case irma.ActionDisclosing:
360
		session.Handler.RequestVerificationPermission(
361
			*session.request.(*irma.DisclosureRequest), session.jwt.Requestor(), callback)
362
	case irma.ActionSigning:
363
		session.Handler.RequestSignaturePermission(
364
			*session.request.(*irma.SignatureRequest), session.jwt.Requestor(), callback)
365
	case irma.ActionIssuing:
366
		session.Handler.RequestIssuancePermission(
367
			*session.request.(*irma.IssuanceRequest), session.jwt.Requestor(), callback)
368
369
370
371
372
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

373
374
375
// 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
376
377
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
378

379
	if !proceed {
380
		session.cancel()
381
382
		return
	}
383
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
384

385
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
386
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
387
		if err != nil {
388
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
389
390
391
392
			return
		}
		session.sendResponse(message)
	} else {
393
394
		var err error
		session.builders, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
395
		if err != nil {
396
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
397
		}
398
399
400
		startKeyshareSession(
			session,
			session.Handler,
401
			session.builders,
402
			session.request,
403
			session.client.Configuration,
404
			session.client.keyshareServers,
405
			session.issuerProofNonce,
406
		)
407
	}
Sietse Ringers's avatar
Sietse Ringers committed
408
}
409

410
// Distributed returns whether or not this session involves a keyshare server.
411
412
413
func (session *session) Distributed() bool {
	var smi irma.SchemeManagerIdentifier
	if session.Action == irma.ActionIssuing {
414
		for _, credreq := range session.request.(*irma.IssuanceRequest).Credentials {
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
			smi = credreq.CredentialTypeID.IssuerIdentifier().SchemeManagerIdentifier()
			if session.client.Configuration.SchemeManagers[smi].Distributed() {
				return true
			}
		}
	}

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

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

	return false
}

436
func (session *session) KeyshareDone(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
437
438
439
	session.sendResponse(message)
}

440
func (session *session) KeyshareCancelled() {
441
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
442
443
}

Sietse Ringers's avatar
Sietse Ringers committed
444
func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
445
	session.Handler.KeyshareEnrollmentIncomplete(manager)
Sietse Ringers's avatar
Sietse Ringers committed
446
447
}

448
449
450
451
func (session *session) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareEnrollmentDeleted(manager)
}

452
func (session *session) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
453
	session.Handler.KeyshareBlocked(manager, duration)
Sietse Ringers's avatar
Sietse Ringers committed
454
455
}

456
func (session *session) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
457
458
459
460
461
462
463
464
	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)
Sietse Ringers's avatar
Sietse Ringers committed
465
466
}

Koen van Ingen's avatar
Koen van Ingen committed
467
func (session *session) KeysharePin() {
468
469
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
470

Koen van Ingen's avatar
Koen van Ingen committed
471
func (session *session) KeysharePinOK() {
472
473
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
474

Sietse Ringers's avatar
Sietse Ringers committed
475
476
type disclosureResponse string

477
478
// 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.
479
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
480
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
481
	var err error
482
483
	var messageJson []byte

484
485
	switch session.Action {
	case irma.ActionSigning:
486
		request, ok := session.request.(*irma.SignatureRequest)
487
488
489
490
491
		if !ok {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

492
493
494
495
496
497
		irmaSignature, err := request.SignatureFromMessage(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

498
499
500
501
502
503
504
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
505
			var response disclosureResponse
506
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
507
508
509
510
511
512
513
				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
514
		}
515
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
	case irma.ActionDisclosing:
		var response disclosureResponse
		if err = session.transport.Post("proofs", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
		if response != "VALID" {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
			return
		}
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
	case irma.ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		if err = session.transport.Post("commitments", &response, message); err != nil {
			session.fail(err.(*irma.SessionError))
			return
		}
533
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
534
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
535
536
			return
		}
537
		log, _ = session.createLogEntry(message) // TODO err
538
539
	}

540
	_ = session.client.addLogEntry(log) // TODO err
541
	if session.Action == irma.ActionIssuing {
542
		session.client.handler.UpdateAttributes()
543
	}
544
	session.done = true
545
546
547
	session.Handler.Success(session.Action, string(messageJson))
}

548
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
549
func (session *session) managerSession() {
550
	defer session.recoverFromPanic()
551
552
553
554

	// 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.
555
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
556
	if err != nil {
557
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
558
559
		return
	}
560

561
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
562
		if !proceed {
563
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
564
565
			return
		}
566
		if err := session.client.Configuration.InstallSchemeManager(manager); err != nil {
567
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
568
569
			return
		}
570
571

		// Update state and inform user of success
572
		session.client.handler.UpdateConfiguration(
573
574
575
576
			&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
577
578
			},
		)
579
		session.Handler.Success(session.Action, "")
580
581
582
	})
	return
}
583
584
585

// Session lifetime functions

586
587
588
589
590
591
592
593
594
595
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
596
	}
597
	fmt.Println("Panic: " + info)
598
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
599
600
601
}

// Idempotently send DELETE to remote server, returning whether or not we did something
602
func (session *session) delete() bool {
603
	if !session.done {
604
605
606
		if session.IsInteractive() {
			session.transport.Delete()
		}
607
		session.done = true
608
609
610
611
612
		return true
	}
	return false
}

613
func (session *session) fail(err *irma.SessionError) {
614
615
616
617
618
619
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		session.Handler.Failure(session.Action, err)
	}
}

620
func (session *session) cancel() {
621
622
623
624
625
	if session.delete() {
		session.Handler.Cancelled(session.Action)
	}
}

626
func (session *session) Dismiss() {
627
628
	session.cancel()
}