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

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

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

49
50
51
52
53
type baseSession interface {
	sendResponse(message interface{})
	cancel(action irma.Action)
}

Sietse Ringers's avatar
Sietse Ringers committed
54
type session struct {
55
56
57
58
59
60
61
62
63
64
65
66
67
68
	baseSession
	Action      irma.Action
	Handler     Handler
	Version     irma.Version

	choice      *irma.DisclosureChoice
	client      *Client
	irmaSession irma.IrmaSession
}

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

69
70
	ServerURL string

71
72
73
74
75
	info        *irma.SessionInfo
	jwt         irma.RequestorJwt
	transport   *irma.HTTPTransport
	choice      *irma.DisclosureChoice
	downloaded  *irma.IrmaIdentifierSet
76
	done        bool
77
78
}

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

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

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

93
func calcVersion(qr *irma.Qr) (string, error) {
94
	// Parse range supported by server
95
96
97
98
99
100
101
102
103
104
105
106
	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 {
107
108
109
110
111
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
112
	for k := range supportedVersions {
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
		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)
}

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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
	}

144
145
146
147
148
149
150
151
152
	session := &manualSession{}
	// TODO: for some reason: we can't define these above in constructor?
	session.Action = irma.ActionSigning// TODO hardcoded for now
	session.Handler = handler
	session.client = client // TODO hardcoded for now
	session.Version = irma.Version("2") // TODO hardcoded for now
	session.irmaSession = sigrequest

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

	// Check if we are enrolled into all involved keyshare servers
155
	for id := range session.irmaSession.Identifiers().SchemeManagers {
156
157
158
159
160
161
162
163
164
165
166
167
168
		manager, ok := session.client.Configuration.SchemeManagers[id]
		if !ok {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorUnknownSchemeManager, Info: id.String()})
			return
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.MissingKeyshareEnrollment(id)
			return
		}
	}

169
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
170
171
172
173
174
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		// TODO: session.transport.Delete() on dialog cancel
		return
	}
175
	session.irmaSession.SetCandidates(candidates)
176
177
178
179

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
180
		session.irmaSession.SetDisclosureChoice(choice)
181
182
183
184
		fmt.Println("Starting session.do()")
		 go session.do(proceed)
	})
	session.Handler.RequestSignaturePermission(
185
		*session.irmaSession.(*irma.SignatureRequest), "IRMA Signature App", callback)
186
187
188
189
190

	fmt.Println(session)
}

func (session *manualSession) do(proceed bool) {
191
192
193
194
195
196
197
198
	defer func() {
		if e := recover(); e != nil {
			if session.Handler != nil {
				session.Handler.Failure(session.Action, panicToError(e))
			}
		}
	}()

199
	if !proceed {
200
		session.cancel()
201
202
		return
	}
203
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
204

205
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
206
207
208
		var message interface{}
		var err error

209
210
211
212
213
214
215
216
		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))
		}
217
218
219
220
		if err != nil {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
			return
		}
221
		session.sendResponse(message)
222
223
224
225
	} else {
		var builders gabi.ProofBuilderList
		var err error

226
227
228
229
230
231
232
233
		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))
		}
234
235
236
237
238
239
240
241
		if err != nil {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
		}

		startKeyshareSession(
			session,
			session.Handler,
			builders,
242
			session.irmaSession,
243
244
245
246
247
248
249
			session.client.Configuration,
			session.client.keyshareServers,
			session.client.state,
		)
	}
}

250
// NewSession creates and starts a new interactive IRMA session
251
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
252
	session := &interactiveSession{
253
		ServerURL: qr.URL,
254
		transport: irma.NewHTTPTransport(qr.URL),
255
	}
256

257
258
259
260
261
	// TODO: for some reason: we can't define these above in constructor?
	session.Action = irma.Action(qr.Type)
	session.Handler = handler
	session.client = client

262
263
264
265
266
	if session.Action == irma.ActionSchemeManager {
		go session.managerSession()
		return session
	}

267
268
	version, err := calcVersion(qr)
	if err != nil {
269
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
270
		return nil
271
	}
272
	session.Version = irma.Version(version)
273
274
275

	// Check if the action is one of the supported types
	switch session.Action {
276
277
278
279
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
280
281
		fallthrough
	default:
282
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
283
		return nil
284
285
286
287
288
289
290
291
	}

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

	go session.start()

292
	return session
293
294
}

295
296
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
297
func (session *interactiveSession) start() {
298
	defer func() {
299
		if e := recover(); e != nil {
300
			if session.Handler != nil {
301
				session.Handler.Failure(session.Action, panicToError(e))
302
			}
303
		}
304
305
	}()

306
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
307

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

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

333
	// Check if we are enrolled into all involved keyshare servers
Sietse Ringers's avatar
Sietse Ringers committed
334
	for id := range session.irmaSession.Identifiers().SchemeManagers {
335
		manager, ok := session.client.Configuration.SchemeManagers[id]
336
		if !ok {
337
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownSchemeManager, Info: id.String()})
338
339
340
			return
		}
		distributed := manager.Distributed()
341
		_, enrolled := session.client.keyshareServers[id]
342
		if distributed && !enrolled {
343
			session.delete()
344
			session.Handler.MissingKeyshareEnrollment(id)
Sietse Ringers's avatar
Sietse Ringers committed
345
346
347
348
			return
		}
	}

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

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

370
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
371
372
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
373
		// TODO: session.transport.Delete() on dialog cancel
374
375
		return
	}
376
	session.irmaSession.SetCandidates(candidates)
377

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

400
func (session *interactiveSession) do(proceed bool) {
401
	defer func() {
402
		if e := recover(); e != nil {
403
			if session.Handler != nil {
404
				session.Handler.Failure(session.Action, panicToError(e))
405
			}
406
		}
407
408
	}()

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

415
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Sietse Ringers's avatar
Sietse Ringers committed
416
417
		var message interface{}
		var err error
418

Sietse Ringers's avatar
Sietse Ringers committed
419
		switch session.Action {
420
		case irma.ActionSigning:
421
			message, err = session.client.Proofs(session.choice, session.irmaSession, true)
422
		case irma.ActionDisclosing:
423
			message, err = session.client.Proofs(session.choice, session.irmaSession, false)
424
425
		case irma.ActionIssuing:
			message, err = session.client.IssueCommitments(session.irmaSession.(*irma.IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
426
427
		}
		if err != nil {
428
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
429
430
431
432
			return
		}
		session.sendResponse(message)
	} else {
433
		var builders gabi.ProofBuilderList
Sietse Ringers's avatar
Sietse Ringers committed
434
		var err error
435

Sietse Ringers's avatar
Sietse Ringers committed
436
		switch session.Action {
437
		case irma.ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
438
			fallthrough
439
		case irma.ActionDisclosing:
440
			builders, err = session.client.ProofBuilders(session.choice)
441
442
		case irma.ActionIssuing:
			builders, err = session.client.IssuanceProofBuilders(session.irmaSession.(*irma.IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
443
444
		}
		if err != nil {
445
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
446
447
		}

448
449
450
451
452
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
453
			session.client.Configuration,
454
			session.client.keyshareServers,
455
			session.client.state,
456
		)
457
	}
Sietse Ringers's avatar
Sietse Ringers committed
458
}
459

460
func (session *interactiveSession) KeyshareDone(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
461
462
463
	session.sendResponse(message)
}

464
func (session *manualSession) KeyshareDone(message interface{}) {
465
466
467
468
469
470
	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))
471
472
}

473
func (session *interactiveSession) KeyshareCancelled() {
474
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
475
476
}

477
478
479
480
func (session *manualSession) KeyshareCancelled() {
	session.Handler.Cancelled(session.Action)
}

481
func (session *interactiveSession) KeyshareBlocked(duration int) {
482
	session.fail(&irma.SessionError{ErrorType: irma.ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
483
484
}

485
486
487
488
func (session *manualSession) KeyshareBlocked(duration int) {
	session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
}

489
func (session *interactiveSession) KeyshareError(err error) {
490
491
492
493
494
495
496
497
	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
498
499
}

500
501
502
503
504
505
506
507
508
509
510
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)
}

511
func (session *interactiveSession) KeysharePin() {
Sietse Ringers's avatar
Sietse Ringers committed
512
513
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
514
515
516
func (session *manualSession) KeysharePin() {
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
517

518
func (session *interactiveSession) KeysharePinOK() {
Sietse Ringers's avatar
Sietse Ringers committed
519
520
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
521
522
523
func (session *manualSession) KeysharePinOK() {
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
524

Sietse Ringers's avatar
Sietse Ringers committed
525
526
type disclosureResponse string

527
func (session *interactiveSession) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
528
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
529
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
530

Sietse Ringers's avatar
Sietse Ringers committed
531
	switch session.Action {
532
	case irma.ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
533
		fallthrough
534
	case irma.ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
535
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
536
		if err = session.transport.Post("proofs", &response, message); err != nil {
537
			session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
538
539
540
			return
		}
		if response != "VALID" {
541
			session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
542
543
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
544
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
545
	case irma.ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
546
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
547
		if err = session.transport.Post("commitments", &response, message); err != nil {
548
			session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
549
550
			return
		}
551
552
		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
553
554
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
555
		log, _ = session.createLogEntry(message) // TODO err
556
557
	}

558
	_ = session.client.addLogEntry(log) // TODO err
559
	if !session.downloaded.Empty() {
560
		session.client.handler.UpdateConfiguration(session.downloaded)
561
	}
562
	if session.Action == irma.ActionIssuing {
563
		session.client.handler.UpdateAttributes()
564
	}
565
	session.done = true
566
	session.Handler.Success(session.Action, "")
567
}
568

569
570
571
572
573
574
575
576
577
578
579
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() {
580
	defer func() {
581
		if e := recover(); e != nil {
582
			if session.Handler != nil {
583
				session.Handler.Failure(session.Action, panicToError(e))
584
			}
585
		}
586
587
588
589
590
	}()

	// 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.
591
	manager, err := session.client.Configuration.DownloadSchemeManager(session.ServerURL)
592
	if err != nil {
593
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
594
595
		return
	}
596

597
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
598
		if !proceed {
599
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
600
601
			return
		}
602
		if err := session.client.Configuration.AddSchemeManager(manager); err != nil {
603
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
604
605
			return
		}
606
607

		// Update state and inform user of success
608
		if manager.Distributed() {
609
			session.client.UnenrolledSchemeManagers = session.client.unenrolledSchemeManagers()
610
		}
611
		session.client.handler.UpdateConfiguration(
612
613
614
615
			&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
616
617
			},
		)
618
		session.Handler.Success(session.Action, "")
619
620
621
	})
	return
}
622
623
624

// Session lifetime functions

625
626
627
628
629
630
631
632
633
634
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
635
	}
636
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
637
638
639
}

// Idempotently send DELETE to remote server, returning whether or not we did something
640
func (session *interactiveSession) delete() bool {
641
	if !session.done {
642
		session.transport.Delete()
643
		session.done = true
644
645
646
647
648
		return true
	}
	return false
}

649
func (session *interactiveSession) fail(err *irma.SessionError) {
650
651
652
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		if session.downloaded != nil && !session.downloaded.Empty() {
653
			session.client.handler.UpdateConfiguration(session.downloaded)
654
655
656
657
658
		}
		session.Handler.Failure(session.Action, err)
	}
}

659
func (session *interactiveSession) cancel() {
660
661
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
662
			session.client.handler.UpdateConfiguration(session.downloaded)
663
664
665
666
667
		}
		session.Handler.Cancelled(session.Action)
	}
}

668
669
670
671
672
func (session *manualSession) cancel() {
	session.Handler.Cancelled(session.Action)
}

func (session *interactiveSession) Dismiss() {
673
674
	session.cancel()
}
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690

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
}