session.go 19.4 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/privacybydesign/irmago"
13
	"github.com/go-errors/errors"
14
	"github.com/mhe/gabi"
15
16
)

Sietse Ringers's avatar
Sietse Ringers committed
17
18
19
// This file contains the client side of the IRMA protocol, as well as the Handler interface
// which is used to communicate session info with the user.

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
33
34
35
36
37
38
39
	Cancelled(action irma.Action)
	Failure(action irma.Action, err *irma.SessionError)
	UnsatisfiableRequest(action irma.Action, missing irma.AttributeDisjunctionList)
	MissingKeyshareEnrollment(manager irma.SchemeManagerIdentifier)

	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
40

41
	RequestPin(remainingAttempts int, callback PinHandler)
42
43
}

Sietse Ringers's avatar
Sietse Ringers committed
44
// SessionDismisser can dismiss the current IRMA session.
45
46
47
48
type SessionDismisser interface {
	Dismiss()
}

Koen van Ingen's avatar
Koen van Ingen committed
49
// baseSession contains methods generic to both manual and interactive sessions
50
type baseSession interface {
Koen van Ingen's avatar
Koen van Ingen committed
51
52
53
54
	getBuilders() (gabi.ProofBuilderList, error)
	getProof() (interface{}, error)
	panicFailure()
	checkKeyshareEnrollment() bool
55
56
}

Sietse Ringers's avatar
Sietse Ringers committed
57
type session struct {
58
	baseSession
Koen van Ingen's avatar
Koen van Ingen committed
59
60
61
	Action  irma.Action
	Handler Handler
	Version irma.Version
62
63
64

	choice      *irma.DisclosureChoice
	client      *Client
Koen van Ingen's avatar
Koen van Ingen committed
65
	downloaded  *irma.IrmaIdentifierSet
66
67
68
69
70
71
72
	irmaSession irma.IrmaSession
}

// A interactiveSession is an interactive IRMA session
type interactiveSession struct {
	session

73
74
	ServerURL string

Koen van Ingen's avatar
Koen van Ingen committed
75
76
77
78
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
	done      bool
79
80
}

81
// A manualSession is a session started from a request
82
type manualSession struct {
83
	session
84
85
}

86
// We implement the handler for the keyshare protocol
87
var _ keyshareSessionHandler = (*interactiveSession)(nil)
88
var _ keyshareSessionHandler = (*manualSession)(nil)
89

90
91
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
92
	2: {2, 1},
93
94
}

95
func calcVersion(qr *irma.Qr) (string, error) {
96
	// Parse range supported by server
97
98
99
100
101
102
103
104
105
106
107
108
	var minmajor, minminor, maxmajor, maxminor int
	var err error
	if minmajor, err = strconv.Atoi(string(qr.ProtocolVersion[0])); err != nil {
		return "", err
	}
	if minminor, err = strconv.Atoi(string(qr.ProtocolVersion[2])); err != nil {
		return "", err
	}
	if maxmajor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[0])); err != nil {
		return "", err
	}
	if maxminor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[2])); err != nil {
109
110
111
112
113
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
114
	for k := range supportedVersions {
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
		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 {
				return fmt.Sprintf("%d.%d", major, minor), nil
			}
		}
	}
	return "", fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion, qr.ProtocolMaxVersion)
}

Koen van Ingen's avatar
Koen van Ingen committed
130
131
132
133
134
135
136
137
138
139
140
141
func (session *session) getBuilders() (gabi.ProofBuilderList, error) {
	var builders gabi.ProofBuilderList
	var err error

	switch session.Action {
	case irma.ActionSigning:
		fallthrough
	case irma.ActionDisclosing:
		builders, err = session.client.ProofBuilders(session.choice)
	case irma.ActionIssuing:
		builders, err = session.client.IssuanceProofBuilders(session.irmaSession.(*irma.IssuanceRequest))
	}
142

Koen van Ingen's avatar
Koen van Ingen committed
143
	return builders, err
144
145
}

Koen van Ingen's avatar
Koen van Ingen committed
146
147
148
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
149

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

Koen van Ingen's avatar
Koen van Ingen committed
159
160
	return message, err
}
161

Koen van Ingen's avatar
Koen van Ingen committed
162
163
// Check if we are enrolled into all involved keyshare servers
func (session *session) checkKeyshareEnrollment() bool {
164
	for id := range session.irmaSession.Identifiers().SchemeManagers {
165
166
167
		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
168
			return false
169
170
171
172
173
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.MissingKeyshareEnrollment(id)
Koen van Ingen's avatar
Koen van Ingen committed
174
			return false
175
176
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
	return true
}

func (session *session) panicFailure() {
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

func parseSigrequestJSON(sigrequestJSONString string) (*irma.SignatureRequest, error) {
	sigrequestJSON := []byte(sigrequestJSONString)
	sigrequest := &irma.SignatureRequest{}
	err := json.Unmarshal(sigrequestJSON, sigrequest)

	return sigrequest, err
}

// Start a manual session
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
	sigrequest, err := parseSigrequestJSON(sigrequestJSONString)
	if err != nil {
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

	session := &manualSession{
		session: session{
			Action:      irma.ActionSigning, // TODO hardcoded for now
			Handler:     handler,
			client:      client,
			Version:     irma.Version("2"), // TODO hardcoded for now
			irmaSession: sigrequest,
		},
	}

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

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

221
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
222
223
224
225
226
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		// TODO: session.transport.Delete() on dialog cancel
		return
	}
227
	session.irmaSession.SetCandidates(candidates)
228
229
230
231

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
232
		session.irmaSession.SetDisclosureChoice(choice)
Koen van Ingen's avatar
Koen van Ingen committed
233
		go session.do(proceed)
234
235
	})
	session.Handler.RequestSignaturePermission(
Koen van Ingen's avatar
Koen van Ingen committed
236
		*session.irmaSession.(*irma.SignatureRequest), "E-mail request", callback)
237
238
239
}

func (session *manualSession) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
240
	defer session.panicFailure()
241

242
	if !proceed {
Koen van Ingen's avatar
Koen van Ingen committed
243
		session.Handler.Cancelled(session.Action)
244
245
246
		return
	}

247
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Koen van Ingen's avatar
Koen van Ingen committed
248
		message, err := session.getProof()
249
250
251
252
		if err != nil {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
			return
		}
253
		session.sendResponse(message)
254
	} else {
Koen van Ingen's avatar
Koen van Ingen committed
255
		builders, err := session.getBuilders()
256
257
258
259
260
261
262
263
		if err != nil {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
		}

		startKeyshareSession(
			session,
			session.Handler,
			builders,
264
			session.irmaSession,
265
266
267
268
269
270
271
			session.client.Configuration,
			session.client.keyshareServers,
			session.client.state,
		)
	}
}

272
// NewSession creates and starts a new interactive IRMA session
273
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
274
	session := &interactiveSession{
275
		ServerURL: qr.URL,
276
		transport: irma.NewHTTPTransport(qr.URL),
Koen van Ingen's avatar
Koen van Ingen committed
277
278
279
280
281
		session: session{
			Action:  irma.Action(qr.Type),
			Handler: handler,
			client:  client,
		},
282
	}
283
284
285
286
287
288

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

289
290
	version, err := calcVersion(qr)
	if err != nil {
291
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
292
		return nil
293
	}
294
	session.Version = irma.Version(version)
295
296
297

	// Check if the action is one of the supported types
	switch session.Action {
298
299
300
301
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
302
303
		fallthrough
	default:
304
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
305
		return nil
306
307
308
309
310
311
312
313
	}

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

	go session.start()

314
	return session
315
316
}

317
318
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
319
func (session *interactiveSession) start() {
Koen van Ingen's avatar
Koen van Ingen committed
320
	defer session.panicFailure()
321

322
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
323

Sietse Ringers's avatar
Sietse Ringers committed
324
	// Get the first IRMA protocol message and parse it
325
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
326
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
327
	if Err != nil {
328
		session.fail(Err.(*irma.SessionError))
329
330
331
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
332
	var err error
333
	session.jwt, err = irma.ParseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
334
	if err != nil {
335
		session.fail(&irma.SessionError{ErrorType: irma.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
336
337
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
338
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
339
340
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
341
342
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
343
		// Store which public keys the server will use
344
		for _, credreq := range ir.Credentials {
345
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
346
347
348
		}
	}

349
	// Check if we are enrolled into all involved keyshare servers
Koen van Ingen's avatar
Koen van Ingen committed
350
351
	if !session.checkKeyshareEnrollment() {
		return
Sietse Ringers's avatar
Sietse Ringers committed
352
353
	}

Sietse Ringers's avatar
Sietse Ringers committed
354
	// Download missing credential types/issuers/public keys from the scheme manager
355
	if session.downloaded, err = session.client.Configuration.Download(session.irmaSession.Identifiers()); err != nil {
356
357
		session.Handler.Failure(
			session.Action,
358
			&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err},
359
360
361
362
		)
		return
	}

363
364
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
Sietse Ringers's avatar
Sietse Ringers committed
365
		for _, credreq := range ir.Credentials {
366
			info, err := credreq.Info(session.client.Configuration)
367
			if err != nil {
368
				session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
369
370
371
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
372
373
374
		}
	}

375
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
376
377
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
378
		// TODO: session.transport.Delete() on dialog cancel
379
380
		return
	}
381
	session.irmaSession.SetCandidates(candidates)
382

Sietse Ringers's avatar
Sietse Ringers committed
383
	// Ask for permission to execute the session
384
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
385
386
387
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
388
	})
389
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
390
	switch session.Action {
391
	case irma.ActionDisclosing:
392
		session.Handler.RequestVerificationPermission(
393
394
			*session.irmaSession.(*irma.DisclosureRequest), session.jwt.Requestor(), callback)
	case irma.ActionSigning:
395
		session.Handler.RequestSignaturePermission(
396
397
			*session.irmaSession.(*irma.SignatureRequest), session.jwt.Requestor(), callback)
	case irma.ActionIssuing:
398
		session.Handler.RequestIssuancePermission(
399
			*session.irmaSession.(*irma.IssuanceRequest), session.jwt.Requestor(), callback)
400
401
402
403
404
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

405
func (session *interactiveSession) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
406
	defer session.panicFailure()
407

408
	if !proceed {
409
		session.cancel()
410
411
		return
	}
412
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
413

414
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Koen van Ingen's avatar
Koen van Ingen committed
415
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
416
		if err != nil {
417
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
418
419
420
421
			return
		}
		session.sendResponse(message)
	} else {
Koen van Ingen's avatar
Koen van Ingen committed
422
		builders, err := session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
423
		if err != nil {
424
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
425
		}
426
427
428
429
430
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
431
			session.client.Configuration,
432
			session.client.keyshareServers,
433
			session.client.state,
434
		)
435
	}
Sietse Ringers's avatar
Sietse Ringers committed
436
}
437

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

442
func (session *manualSession) KeyshareDone(message interface{}) {
Koen van Ingen's avatar
Koen van Ingen committed
443
	messageJson, err := json.Marshal(message)
444
445
446
447
448
	if err != nil {
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
		return
	}
	session.Handler.Success(session.Action, string(messageJson))
449
450
}

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

455
456
457
458
func (session *manualSession) KeyshareCancelled() {
	session.Handler.Cancelled(session.Action)
}

459
func (session *interactiveSession) KeyshareBlocked(duration int) {
460
	session.fail(&irma.SessionError{ErrorType: irma.ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
461
462
}

463
464
465
466
func (session *manualSession) KeyshareBlocked(duration int) {
	session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
}

467
func (session *interactiveSession) KeyshareError(err error) {
468
469
470
471
472
473
474
475
	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
476
477
}

478
479
480
481
482
483
484
485
486
487
488
func (session *manualSession) KeyshareError(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.Handler.Failure(session.Action, serr)
}

Koen van Ingen's avatar
Koen van Ingen committed
489
func (session *session) KeysharePin() {
490
491
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
492

Koen van Ingen's avatar
Koen van Ingen committed
493
func (session *session) KeysharePinOK() {
494
495
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
496

Sietse Ringers's avatar
Sietse Ringers committed
497
498
type disclosureResponse string

499
func (session *interactiveSession) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
500
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
501
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
502

Sietse Ringers's avatar
Sietse Ringers committed
503
	switch session.Action {
504
	case irma.ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
505
		fallthrough
506
	case irma.ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
507
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
508
		if err = session.transport.Post("proofs", &response, message); err != nil {
509
			session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
510
511
512
			return
		}
		if response != "VALID" {
513
			session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
514
515
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
516
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
517
	case irma.ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
518
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
519
		if err = session.transport.Post("commitments", &response, message); err != nil {
520
			session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
521
522
			return
		}
523
524
		if err = session.client.ConstructCredentials(response, session.irmaSession.(*irma.IssuanceRequest)); err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
525
526
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
527
		log, _ = session.createLogEntry(message) // TODO err
528
529
	}

530
	_ = session.client.addLogEntry(log) // TODO err
531
	if !session.downloaded.Empty() {
532
		session.client.handler.UpdateConfiguration(session.downloaded)
533
	}
534
	if session.Action == irma.ActionIssuing {
535
		session.client.handler.UpdateAttributes()
536
	}
537
	session.done = true
538
	session.Handler.Success(session.Action, "")
539
}
540

541
542
543
544
545
546
547
548
549
550
551
func (session *manualSession) sendResponse(message interface{}) {
	messageJson, err := json.Marshal(message)
	if err != nil {
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
		return
	}

	session.Handler.Success(session.Action, string(messageJson))
}

func (session *interactiveSession) managerSession() {
552
	defer func() {
553
		if e := recover(); e != nil {
554
			if session.Handler != nil {
555
				session.Handler.Failure(session.Action, panicToError(e))
556
			}
557
		}
558
559
560
561
562
	}()

	// 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.
563
	manager, err := session.client.Configuration.DownloadSchemeManager(session.ServerURL)
564
	if err != nil {
565
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
566
567
		return
	}
568

569
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
570
		if !proceed {
571
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
572
573
			return
		}
574
		if err := session.client.Configuration.AddSchemeManager(manager); err != nil {
575
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
576
577
			return
		}
578
579

		// Update state and inform user of success
580
		if manager.Distributed() {
581
			session.client.UnenrolledSchemeManagers = session.client.unenrolledSchemeManagers()
582
		}
583
		session.client.handler.UpdateConfiguration(
584
585
586
587
			&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
588
589
			},
		)
590
		session.Handler.Success(session.Action, "")
591
592
593
	})
	return
}
594
595
596

// Session lifetime functions

597
598
599
600
601
602
603
604
605
606
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
607
	}
608
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
609
610
611
}

// Idempotently send DELETE to remote server, returning whether or not we did something
612
func (session *interactiveSession) delete() bool {
613
	if !session.done {
614
		session.transport.Delete()
615
		session.done = true
616
617
618
619
620
		return true
	}
	return false
}

621
func (session *interactiveSession) fail(err *irma.SessionError) {
622
623
624
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		if session.downloaded != nil && !session.downloaded.Empty() {
625
			session.client.handler.UpdateConfiguration(session.downloaded)
626
627
628
629
630
		}
		session.Handler.Failure(session.Action, err)
	}
}

631
func (session *interactiveSession) cancel() {
632
633
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
634
			session.client.handler.UpdateConfiguration(session.downloaded)
635
636
637
638
639
		}
		session.Handler.Cancelled(session.Action)
	}
}

640
func (session *interactiveSession) Dismiss() {
641
642
	session.cancel()
}
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658

type issuanceState struct {
	nonce2   *big.Int
	builders []*gabi.CredentialBuilder
}

func newIssuanceState() (*issuanceState, error) {
	nonce2, err := gabi.RandomBigInt(gabi.DefaultSystemParameters[4096].Lstatzk)
	if err != nil {
		return nil, err
	}
	return &issuanceState{
		nonce2:   nonce2,
		builders: []*gabi.CredentialBuilder{},
	}, nil
}