session.go 19.7 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
	"strconv"
8
9
	"strings"

10
11
	"math/big"

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

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

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

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

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

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

	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
45

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

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

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

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

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

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

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

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

83
func calcVersion(qr *irma.Qr) (*irma.ProtocolVersion, error) {
84
	// Parse range supported by server
85
86
87
	var minmajor, minminor, maxmajor, maxminor int
	var err error
	if minmajor, err = strconv.Atoi(string(qr.ProtocolVersion[0])); err != nil {
88
		return nil, err
89
90
	}
	if minminor, err = strconv.Atoi(string(qr.ProtocolVersion[2])); err != nil {
91
		return nil, err
92
93
	}
	if maxmajor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[0])); err != nil {
94
		return nil, err
95
96
	}
	if maxminor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[2])); err != nil {
97
		return nil, err
98
99
100
101
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
102
	for k := range supportedVersions {
103
104
105
106
107
108
109
110
		keys = append(keys, k)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(keys)))
	for _, major := range keys {
		for _, minor := range supportedVersions[major] {
			aboveMinimum := major > minmajor || (major == minmajor && minor >= minminor)
			underMaximum := major < maxmajor || (major == maxmajor && minor <= maxminor)
			if aboveMinimum && underMaximum {
111
				return irma.NewVersion(major, minor), nil
112
113
114
			}
		}
	}
115
	return nil, fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion, qr.ProtocolMaxVersion)
116
117
}

118
119
120
121
func (session *session) IsInteractive() bool {
	return session.ServerURL != ""
}

122
123
124
// 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
125
126
127
	var builders gabi.ProofBuilderList
	var err error

128
	var issuerProofNonce *big.Int
Koen van Ingen's avatar
Koen van Ingen committed
129
130
	switch session.Action {
	case irma.ActionSigning:
131
		builders, err = session.client.ProofBuilders(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
132
	case irma.ActionDisclosing:
133
		builders, err = session.client.ProofBuilders(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
134
	case irma.ActionIssuing:
135
		builders, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
136
	}
137

138
	return builders, issuerProofNonce, err
139
140
}

141
142
// 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
143
144
145
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
146

Koen van Ingen's avatar
Koen van Ingen committed
147
148
	switch session.Action {
	case irma.ActionSigning:
149
		message, err = session.client.Proofs(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
150
	case irma.ActionDisclosing:
151
		message, err = session.client.Proofs(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
152
	case irma.ActionIssuing:
153
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
154
	}
155

Koen van Ingen's avatar
Koen van Ingen committed
156
157
	return message, err
}
158

159
160
// 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
161
func (session *session) checkKeyshareEnrollment() bool {
162
	for id := range session.request.Identifiers().SchemeManagers {
163
164
165
		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
166
			return false
167
168
169
170
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
171
			session.Handler.KeyshareEnrollmentMissing(id)
Koen van Ingen's avatar
Koen van Ingen committed
172
			return false
173
174
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
175
176
177
	return true
}

Sietse Ringers's avatar
Sietse Ringers committed
178
func (session *session) recoverFromPanic() {
Koen van Ingen's avatar
Koen van Ingen committed
179
180
181
182
183
184
185
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

186
func (session *session) checkAndUpateConfiguration() bool {
187
	for id := range session.request.Identifiers().SchemeManagers {
188
		manager, contains := session.client.Configuration.SchemeManagers[id]
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
		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
211
	downloaded, err := session.client.Configuration.Download(session.request)
212
	if err != nil {
213
214
215
		session.fail(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
		return false
	}
216
217
218
	if downloaded != nil && !downloaded.Empty() {
		session.client.handler.UpdateConfiguration(downloaded)
	}
219
220
221
	return true
}

Sietse Ringers's avatar
Sietse Ringers committed
222
// 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
223
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
224
225
226
	var err error
	sigrequest := &irma.SignatureRequest{}
	if err = json.Unmarshal([]byte(sigrequestJSONString), sigrequest); err != nil {
Koen van Ingen's avatar
Koen van Ingen committed
227
228
229
230
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

231
	session := &session{
232
233
234
235
236
		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
237
238
239
240
	}

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

241
	if !session.checkAndUpateConfiguration() {
242
243
244
		return
	}

245
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
246
	if len(missing) > 0 {
247
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
248
249
		return
	}
250
	session.request.SetCandidates(candidates)
251
252
253
254

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
255
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
256
		go session.doSession(proceed)
257
258
	})
	session.Handler.RequestSignaturePermission(
259
		*session.request.(*irma.SignatureRequest), "E-mail request", callback)
260
261
}

262
// NewSession creates and starts a new interactive IRMA session
263
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
264
	session := &session{
265
		ServerURL: qr.URL,
266
		transport: irma.NewHTTPTransport(qr.URL),
267
268
269
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
270
	}
271
272
273
274
275
276

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

277
278
	version, err := calcVersion(qr)
	if err != nil {
279
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
280
		return nil
281
	}
282
283
	session.Version = version
	session.transport.SetHeader("X-IRMA-ProtocolVersion", version.String())
284
285
286

	// Check if the action is one of the supported types
	switch session.Action {
287
288
289
290
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
291
292
		fallthrough
	default:
293
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
294
		return nil
295
296
297
298
299
300
	}

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

Sietse Ringers's avatar
Sietse Ringers committed
301
	go session.getSessionInfo()
302

303
	return session
304
305
}

Sietse Ringers's avatar
Sietse Ringers committed
306
// getSessionInfo retrieves the first message in the IRMA protocol, checks if we can perform
307
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
308
309
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
310

311
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
312

Sietse Ringers's avatar
Sietse Ringers committed
313
	// Get the first IRMA protocol message and parse it
314
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
315
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
316
	if Err != nil {
317
		session.fail(Err.(*irma.SessionError))
318
319
320
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
321
	var err error
322
	session.jwt, err = irma.ParseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
323
	if err != nil {
324
		session.fail(&irma.SessionError{ErrorType: irma.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
325
326
		return
	}
327
328
329
330
	session.request = session.jwt.SessionRequest()
	session.request.SetContext(session.info.Context)
	session.request.SetNonce(session.info.Nonce)
	session.request.SetVersion(session.Version)
331
	if session.Action == irma.ActionIssuing {
332
		ir := session.request.(*irma.IssuanceRequest)
333
		// Store which public keys the server will use
334
		for _, credreq := range ir.Credentials {
335
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
336
337
338
		}
	}

339
	if !session.checkAndUpateConfiguration() {
340
341
342
		return
	}

343
	if session.Action == irma.ActionIssuing {
344
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
345
346
347
348
349
350
351
		_, 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
352
		for _, credreq := range ir.Credentials {
353
354
355
356
			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())
			}
357
358
359
		}
	}

360
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
361
	if len(missing) > 0 {
362
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
363
364
		return
	}
365
	session.request.SetCandidates(candidates)
366

Sietse Ringers's avatar
Sietse Ringers committed
367
	// Ask for permission to execute the session
368
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
369
		session.choice = choice
370
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
371
		go session.doSession(proceed)
372
	})
373
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
374
	switch session.Action {
375
	case irma.ActionDisclosing:
376
		session.Handler.RequestVerificationPermission(
377
			*session.request.(*irma.DisclosureRequest), session.jwt.Requestor(), callback)
378
	case irma.ActionSigning:
379
		session.Handler.RequestSignaturePermission(
380
			*session.request.(*irma.SignatureRequest), session.jwt.Requestor(), callback)
381
	case irma.ActionIssuing:
382
		session.Handler.RequestIssuancePermission(
383
			*session.request.(*irma.IssuanceRequest), session.jwt.Requestor(), callback)
384
385
386
387
388
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
389
390
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
391

392
	if !proceed {
393
		session.cancel()
394
395
		return
	}
396
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
397

398
	if !session.Distributed() {
Koen van Ingen's avatar
Koen van Ingen committed
399
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
400
		if err != nil {
401
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
402
403
404
405
			return
		}
		session.sendResponse(message)
	} else {
406
407
		var err error
		session.builders, session.issuerProofNonce, err = session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
408
		if err != nil {
409
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
410
		}
411
412
413
		startKeyshareSession(
			session,
			session.Handler,
414
			session.builders,
415
			session.request,
416
			session.client.Configuration,
417
			session.client.keyshareServers,
418
			session.issuerProofNonce,
419
		)
420
	}
Sietse Ringers's avatar
Sietse Ringers committed
421
}
422

423
424
425
func (session *session) Distributed() bool {
	var smi irma.SchemeManagerIdentifier
	if session.Action == irma.ActionIssuing {
426
		for _, credreq := range session.request.(*irma.IssuanceRequest).Credentials {
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
			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
}

448
func (session *session) KeyshareDone(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
449
450
451
	session.sendResponse(message)
}

452
func (session *session) KeyshareCancelled() {
453
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
454
455
}

Sietse Ringers's avatar
Sietse Ringers committed
456
func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
457
	session.Handler.KeyshareEnrollmentIncomplete(manager)
Sietse Ringers's avatar
Sietse Ringers committed
458
459
}

460
461
462
463
func (session *session) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareEnrollmentDeleted(manager)
}

464
func (session *session) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
465
	session.Handler.KeyshareBlocked(manager, duration)
Sietse Ringers's avatar
Sietse Ringers committed
466
467
}

468
func (session *session) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
469
470
471
472
473
474
475
476
	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
477
478
}

Koen van Ingen's avatar
Koen van Ingen committed
479
func (session *session) KeysharePin() {
480
481
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
482

Koen van Ingen's avatar
Koen van Ingen committed
483
func (session *session) KeysharePinOK() {
484
485
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
486

Sietse Ringers's avatar
Sietse Ringers committed
487
488
type disclosureResponse string

489
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
490
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
491
	var err error
492
493
	var messageJson []byte

494
495
	switch session.Action {
	case irma.ActionSigning:
496
		request, ok := session.request.(*irma.SignatureRequest)
497
498
499
500
501
		if !ok {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

502
503
504
505
506
507
		irmaSignature, err := request.SignatureFromMessage(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

508
509
510
511
512
513
514
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
515
			var response disclosureResponse
516
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
517
518
519
520
521
522
523
				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
524
		}
525
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
	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
		}
543
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
544
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
545
546
			return
		}
547
		log, _ = session.createLogEntry(message) // TODO err
548
549
	}

550
	_ = session.client.addLogEntry(log) // TODO err
551
	if session.Action == irma.ActionIssuing {
552
		session.client.handler.UpdateAttributes()
553
	}
554
	session.done = true
555
556
557
	session.Handler.Success(session.Action, string(messageJson))
}

558
func (session *session) managerSession() {
559
	defer func() {
560
		if e := recover(); e != nil {
561
			if session.Handler != nil {
562
				session.Handler.Failure(session.Action, panicToError(e))
563
			}
564
		}
565
566
567
568
569
	}()

	// 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.
570
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
571
	if err != nil {
572
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
573
574
		return
	}
575

576
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
577
		if !proceed {
578
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
579
580
			return
		}
581
		if err := session.client.Configuration.InstallSchemeManager(manager); err != nil {
582
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
583
584
			return
		}
585
586

		// Update state and inform user of success
587
		session.client.handler.UpdateConfiguration(
588
589
590
591
			&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
592
593
			},
		)
594
		session.Handler.Success(session.Action, "")
595
596
597
	})
	return
}
598
599
600

// Session lifetime functions

601
602
603
604
605
606
607
608
609
610
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
611
	}
612
	fmt.Println("Panic: " + info)
613
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
614
615
616
}

// Idempotently send DELETE to remote server, returning whether or not we did something
617
func (session *session) delete() bool {
618
	if !session.done {
619
620
621
		if session.IsInteractive() {
			session.transport.Delete()
		}
622
		session.done = true
623
624
625
626
627
		return true
	}
	return false
}

628
func (session *session) fail(err *irma.SessionError) {
629
630
631
632
633
634
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		session.Handler.Failure(session.Action, err)
	}
}

635
func (session *session) cancel() {
636
637
638
639
640
	if session.delete() {
		session.Handler.Cancelled(session.Action)
	}
}

641
func (session *session) Dismiss() {
642
643
	session.cancel()
}