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/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
	// These are empty on manual sessions
65
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
66
67
68
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
69
70
}

71
// We implement the handler for the keyshare protocol
72
var _ keyshareSessionHandler = (*session)(nil)
73

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

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

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
98
	for k := range supportedVersions {
99
100
101
102
103
104
105
106
		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 {
107
				return irma.NewVersion(major, minor), nil
108
109
110
			}
		}
	}
111
	return nil, fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion, qr.ProtocolMaxVersion)
112
113
}

114
115
116
117
func (session *session) IsInteractive() bool {
	return session.ServerURL != ""
}

Koen van Ingen's avatar
Koen van Ingen committed
118
119
120
121
122
123
func (session *session) getBuilders() (gabi.ProofBuilderList, error) {
	var builders gabi.ProofBuilderList
	var err error

	switch session.Action {
	case irma.ActionSigning:
124
		builders, err = session.client.ProofBuilders(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
125
	case irma.ActionDisclosing:
126
		builders, err = session.client.ProofBuilders(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
127
	case irma.ActionIssuing:
128
		builders, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
129
	}
130

Koen van Ingen's avatar
Koen van Ingen committed
131
	return builders, err
132
133
}

Koen van Ingen's avatar
Koen van Ingen committed
134
135
136
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
137

Koen van Ingen's avatar
Koen van Ingen committed
138
139
	switch session.Action {
	case irma.ActionSigning:
140
		message, err = session.client.Proofs(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
141
	case irma.ActionDisclosing:
142
		message, err = session.client.Proofs(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
143
	case irma.ActionIssuing:
144
		message, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
145
	}
146

Koen van Ingen's avatar
Koen van Ingen committed
147
148
	return message, err
}
149

150
151
// 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
152
func (session *session) checkKeyshareEnrollment() bool {
153
	for id := range session.request.Identifiers().SchemeManagers {
154
155
156
		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
157
			return false
158
159
160
161
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
162
			session.Handler.KeyshareEnrollmentMissing(id)
Koen van Ingen's avatar
Koen van Ingen committed
163
			return false
164
165
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
166
167
168
	return true
}

Sietse Ringers's avatar
Sietse Ringers committed
169
func (session *session) recoverFromPanic() {
Koen van Ingen's avatar
Koen van Ingen committed
170
171
172
173
174
175
176
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

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

Sietse Ringers's avatar
Sietse Ringers committed
213
// 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
214
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
215
216
217
	var err error
	sigrequest := &irma.SignatureRequest{}
	if err = json.Unmarshal([]byte(sigrequestJSONString), sigrequest); err != nil {
Koen van Ingen's avatar
Koen van Ingen committed
218
219
220
221
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

222
	session := &session{
223
224
225
226
227
		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
228
229
230
231
	}

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

232
	if !session.checkAndUpateConfiguration() {
233
234
235
		return
	}

236
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
237
	if len(missing) > 0 {
238
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
239
240
		return
	}
241
	session.request.SetCandidates(candidates)
242
243
244
245

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
246
		session.request.SetDisclosureChoice(choice)
Sietse Ringers's avatar
Sietse Ringers committed
247
		go session.doSession(proceed)
248
249
	})
	session.Handler.RequestSignaturePermission(
250
		*session.request.(*irma.SignatureRequest), "E-mail request", callback)
251
252
}

253
// NewSession creates and starts a new interactive IRMA session
254
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
255
	session := &session{
256
		ServerURL: qr.URL,
257
		transport: irma.NewHTTPTransport(qr.URL),
258
259
260
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
261
	}
262
263
264
265
266
267

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

268
269
	version, err := calcVersion(qr)
	if err != nil {
270
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
271
		return nil
272
	}
273
274
	session.Version = version
	session.transport.SetHeader("X-IRMA-ProtocolVersion", version.String())
275
276
277

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

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

Sietse Ringers's avatar
Sietse Ringers committed
292
	go session.getSessionInfo()
293

294
	return session
295
296
}

Sietse Ringers's avatar
Sietse Ringers committed
297
// getSessionInfo retrieves the first message in the IRMA protocol, checks if we can perform
298
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
299
300
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
301

302
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
303

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

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

330
	if !session.checkAndUpateConfiguration() {
331
332
333
		return
	}

334
	if session.Action == irma.ActionIssuing {
335
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
336
337
338
339
340
341
342
		_, 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
343
		for _, credreq := range ir.Credentials {
344
345
346
347
			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())
			}
348
349
350
		}
	}

351
	candidates, missing := session.client.CheckSatisfiability(session.request.ToDisclose())
352
	if len(missing) > 0 {
353
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
354
355
		return
	}
356
	session.request.SetCandidates(candidates)
357

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

Sietse Ringers's avatar
Sietse Ringers committed
380
381
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
382

383
	if !proceed {
384
		session.cancel()
385
386
		return
	}
387
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
388

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

413
414
415
func (session *session) Distributed() bool {
	var smi irma.SchemeManagerIdentifier
	if session.Action == irma.ActionIssuing {
416
		for _, credreq := range session.request.(*irma.IssuanceRequest).Credentials {
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
			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
}

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

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

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

450
451
452
453
func (session *session) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareEnrollmentDeleted(manager)
}

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

458
func (session *session) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
459
460
461
462
463
464
465
466
	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
467
468
}

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

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

Sietse Ringers's avatar
Sietse Ringers committed
477
478
type disclosureResponse string

479
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
480
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
481
	var err error
482
483
	var messageJson []byte

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

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

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

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

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

548
func (session *session) managerSession() {
549
	defer func() {
550
		if e := recover(); e != nil {
551
			if session.Handler != nil {
552
				session.Handler.Failure(session.Action, panicToError(e))
553
			}
554
		}
555
556
557
558
559
	}()

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

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

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

// Session lifetime functions

591
592
593
594
595
596
597
598
599
600
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
601
	}
602
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
603
604
605
}

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

617
func (session *session) fail(err *irma.SessionError) {
618
619
620
621
622
623
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		session.Handler.Failure(session.Action, err)
	}
}

624
func (session *session) cancel() {
625
626
627
628
629
	if session.delete() {
		session.Handler.Cancelled(session.Action)
	}
}

630
func (session *session) Dismiss() {
631
632
	session.cancel()
}
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648

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
}