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

import (
Koen van Ingen's avatar
Koen van Ingen committed
4
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
5
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 {
Koen van Ingen's avatar
Koen van Ingen committed
58
59
60
	Action  irma.Action
	Handler Handler
	Version irma.Version
61
62
63

	choice      *irma.DisclosureChoice
	client      *Client
Koen van Ingen's avatar
Koen van Ingen committed
64
	downloaded  *irma.IrmaIdentifierSet
65
66
67
	irmaSession irma.IrmaSession
}

68
69
70
// We implement baseSessino for a session
var _ baseSession = (*session)(nil)

71
72
73
74
// A interactiveSession is an interactive IRMA session
type interactiveSession struct {
	session

75
76
	ServerURL string

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

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

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

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

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

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
116
	for k := range supportedVersions {
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
		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
132
133
134
135
136
137
138
139
140
141
142
143
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))
	}
144

Koen van Ingen's avatar
Koen van Ingen committed
145
	return builders, err
146
147
}

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

Koen van Ingen's avatar
Koen van Ingen committed
152
153
154
155
156
157
158
159
	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))
	}
160

Koen van Ingen's avatar
Koen van Ingen committed
161
162
	return message, err
}
163

Koen van Ingen's avatar
Koen van Ingen committed
164
165
// Check if we are enrolled into all involved keyshare servers
func (session *session) checkKeyshareEnrollment() bool {
166
	for id := range session.irmaSession.Identifiers().SchemeManagers {
167
168
169
		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
170
			return false
171
172
173
174
175
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.MissingKeyshareEnrollment(id)
Koen van Ingen's avatar
Koen van Ingen committed
176
			return false
177
178
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
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
220
221
	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
	}
222

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

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

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

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

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

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

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

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

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

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

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

	go session.start()

316
	return session
317
318
}

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

324
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
325

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

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

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

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

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

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

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

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

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

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

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

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

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

457
458
459
460
func (session *manualSession) KeyshareCancelled() {
	session.Handler.Cancelled(session.Action)
}

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

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

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

480
481
482
483
484
485
486
487
488
489
490
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
491
func (session *session) KeysharePin() {
492
493
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
494

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

Sietse Ringers's avatar
Sietse Ringers committed
499
500
type disclosureResponse string

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

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

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

543
544
545
546
547
548
549
550
551
552
553
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() {
554
	defer func() {
555
		if e := recover(); e != nil {
556
			if session.Handler != nil {
557
				session.Handler.Failure(session.Action, panicToError(e))
558
			}
559
		}
560
561
562
563
564
	}()

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

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

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

// Session lifetime functions

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

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

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

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

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

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
}