session.go 20 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 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
	Cancelled(action irma.Action)
	Failure(action irma.Action, err *irma.SessionError)
	UnsatisfiableRequest(action irma.Action, missing irma.AttributeDisjunctionList)
34

35
	MissingKeyshareEnrollment(manager irma.SchemeManagerIdentifier)
36
	KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int)
37
	KeyshareRegistrationIncomplete(manager irma.SchemeManagerIdentifier)
38
39
40
41
42

	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
43

44
	RequestPin(remainingAttempts int, callback PinHandler)
45
46
}

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

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

Sietse Ringers's avatar
Sietse Ringers committed
60
type session struct {
Koen van Ingen's avatar
Koen van Ingen committed
61
62
63
	Action  irma.Action
	Handler Handler
	Version irma.Version
64
65
66

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

71
// session implements baseSession
72
73
var _ baseSession = (*session)(nil)

74
75
76
77
// A interactiveSession is an interactive IRMA session
type interactiveSession struct {
	session

78
79
	ServerURL string

Koen van Ingen's avatar
Koen van Ingen committed
80
81
82
83
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
	done      bool
84
85
}

86
// A manualSession is a session started from a request
87
type manualSession struct {
88
	session
89
90
}

91
92
93
94
func (session *session) KeyshareRegistrationIncomplete(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareRegistrationIncomplete(manager)
}

95
// We implement the handler for the keyshare protocol
96
var _ keyshareSessionHandler = (*interactiveSession)(nil)
97
var _ keyshareSessionHandler = (*manualSession)(nil)
98

99
100
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
101
	2: {2, 1},
102
103
}

104
func calcVersion(qr *irma.Qr) (string, error) {
105
	// Parse range supported by server
106
107
108
109
110
111
112
113
114
115
116
117
	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 {
118
119
120
121
122
		return "", err
	}

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

Koen van Ingen's avatar
Koen van Ingen committed
152
	return builders, err
153
154
}

Koen van Ingen's avatar
Koen van Ingen committed
155
156
157
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
158

Koen van Ingen's avatar
Koen van Ingen committed
159
160
161
162
163
164
165
166
	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))
	}
167

Koen van Ingen's avatar
Koen van Ingen committed
168
169
	return message, err
}
170

Koen van Ingen's avatar
Koen van Ingen committed
171
172
// Check if we are enrolled into all involved keyshare servers
func (session *session) checkKeyshareEnrollment() bool {
173
	for id := range session.irmaSession.Identifiers().SchemeManagers {
174
175
176
		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
177
			return false
178
179
180
181
182
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
			session.Handler.MissingKeyshareEnrollment(id)
Koen van Ingen's avatar
Koen van Ingen committed
183
			return false
184
185
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
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
222
223
224
225
226
227
228
	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
	}
229

230
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
231
232
233
234
235
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		// TODO: session.transport.Delete() on dialog cancel
		return
	}
236
	session.irmaSession.SetCandidates(candidates)
237
238
239
240

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
241
		session.irmaSession.SetDisclosureChoice(choice)
Koen van Ingen's avatar
Koen van Ingen committed
242
		go session.do(proceed)
243
244
	})
	session.Handler.RequestSignaturePermission(
Koen van Ingen's avatar
Koen van Ingen committed
245
		*session.irmaSession.(*irma.SignatureRequest), "E-mail request", callback)
246
247
248
}

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

251
	if !proceed {
Koen van Ingen's avatar
Koen van Ingen committed
252
		session.Handler.Cancelled(session.Action)
253
254
255
		return
	}

256
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Koen van Ingen's avatar
Koen van Ingen committed
257
		message, err := session.getProof()
258
259
260
261
		if err != nil {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
			return
		}
262
		session.sendResponse(message)
263
	} else {
Koen van Ingen's avatar
Koen van Ingen committed
264
		builders, err := session.getBuilders()
265
266
267
268
269
270
271
272
		if err != nil {
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
		}

		startKeyshareSession(
			session,
			session.Handler,
			builders,
273
			session.irmaSession,
274
275
276
277
278
279
280
			session.client.Configuration,
			session.client.keyshareServers,
			session.client.state,
		)
	}
}

281
// NewSession creates and starts a new interactive IRMA session
282
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
283
	session := &interactiveSession{
284
		ServerURL: qr.URL,
285
		transport: irma.NewHTTPTransport(qr.URL),
Koen van Ingen's avatar
Koen van Ingen committed
286
287
288
289
290
		session: session{
			Action:  irma.Action(qr.Type),
			Handler: handler,
			client:  client,
		},
291
	}
292
293
294
295
296
297

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

298
299
	version, err := calcVersion(qr)
	if err != nil {
300
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
301
		return nil
302
	}
303
	session.Version = irma.Version(version)
304
305
306

	// Check if the action is one of the supported types
	switch session.Action {
307
308
309
310
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
311
312
		fallthrough
	default:
313
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
314
		return nil
315
316
317
318
319
320
321
322
	}

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

	go session.start()

323
	return session
324
325
}

326
327
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
328
func (session *interactiveSession) start() {
Koen van Ingen's avatar
Koen van Ingen committed
329
	defer session.panicFailure()
330

331
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
332

Sietse Ringers's avatar
Sietse Ringers committed
333
	// Get the first IRMA protocol message and parse it
334
	session.info = &irma.SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
335
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
336
	if Err != nil {
337
		session.fail(Err.(*irma.SessionError))
338
339
340
		return
	}

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

358
	// Check if we are enrolled into all involved keyshare servers
Koen van Ingen's avatar
Koen van Ingen committed
359
360
	if !session.checkKeyshareEnrollment() {
		return
Sietse Ringers's avatar
Sietse Ringers committed
361
362
	}

Sietse Ringers's avatar
Sietse Ringers committed
363
	// Download missing credential types/issuers/public keys from the scheme manager
364
	if session.downloaded, err = session.client.Configuration.Download(session.irmaSession.Identifiers()); err != nil {
365
366
		session.Handler.Failure(
			session.Action,
367
			&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err},
368
369
370
371
		)
		return
	}

372
373
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
Sietse Ringers's avatar
Sietse Ringers committed
374
		for _, credreq := range ir.Credentials {
375
			info, err := credreq.Info(session.client.Configuration)
376
			if err != nil {
377
				session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
378
379
380
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
381
382
383
		}
	}

384
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
385
386
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
387
		// TODO: session.transport.Delete() on dialog cancel
388
389
		return
	}
390
	session.irmaSession.SetCandidates(candidates)
391

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

414
func (session *interactiveSession) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
415
	defer session.panicFailure()
416

417
	if !proceed {
418
		session.cancel()
419
420
		return
	}
421
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
422

423
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Koen van Ingen's avatar
Koen van Ingen committed
424
		message, err := session.getProof()
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
			return
		}
		session.sendResponse(message)
	} else {
Koen van Ingen's avatar
Koen van Ingen committed
431
		builders, err := session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
432
		if err != nil {
433
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
434
		}
435
436
437
438
439
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
440
			session.client.Configuration,
441
			session.client.keyshareServers,
442
			session.client.state,
443
		)
444
	}
Sietse Ringers's avatar
Sietse Ringers committed
445
}
446

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

451
func (session *manualSession) KeyshareDone(message interface{}) {
Koen van Ingen's avatar
Koen van Ingen committed
452
	messageJson, err := json.Marshal(message)
453
454
455
456
457
	if err != nil {
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
		return
	}
	session.Handler.Success(session.Action, string(messageJson))
458
459
}

460
func (session *interactiveSession) KeyshareCancelled() {
461
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
462
463
}

464
465
466
467
func (session *manualSession) KeyshareCancelled() {
	session.Handler.Cancelled(session.Action)
}

468
469
470
471
472
473
474
func (session *interactiveSession) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
			session.client.handler.UpdateConfiguration(session.downloaded)
		}
	}
	session.Handler.KeyshareBlocked(manager, duration)
Sietse Ringers's avatar
Sietse Ringers committed
475
476
}

477
478
479
func (session *manualSession) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
	// TODO: fire UpdateConfiguration()?
	session.Handler.KeyshareBlocked(manager, duration)
480
481
}

482
func (session *interactiveSession) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
483
484
485
486
487
488
489
490
	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
491
492
}

493
func (session *manualSession) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
494
495
496
497
498
499
500
501
502
503
	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
504
func (session *session) KeysharePin() {
505
506
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
507

Koen van Ingen's avatar
Koen van Ingen committed
508
func (session *session) KeysharePinOK() {
509
510
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
511

Sietse Ringers's avatar
Sietse Ringers committed
512
513
type disclosureResponse string

514
func (session *interactiveSession) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
515
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
516
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
517

Sietse Ringers's avatar
Sietse Ringers committed
518
	switch session.Action {
519
	case irma.ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
520
		fallthrough
521
	case irma.ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
522
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
523
		if err = session.transport.Post("proofs", &response, message); err != nil {
524
			session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
525
526
527
			return
		}
		if response != "VALID" {
528
			session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
529
530
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
531
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
532
	case irma.ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
533
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
534
		if err = session.transport.Post("commitments", &response, message); err != nil {
535
			session.fail(err.(*irma.SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
536
537
			return
		}
538
539
		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
540
541
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
542
		log, _ = session.createLogEntry(message) // TODO err
543
544
	}

545
	_ = session.client.addLogEntry(log) // TODO err
546
	if !session.downloaded.Empty() {
547
		session.client.handler.UpdateConfiguration(session.downloaded)
548
	}
549
	if session.Action == irma.ActionIssuing {
550
		session.client.handler.UpdateAttributes()
551
	}
552
	session.done = true
553
	session.Handler.Success(session.Action, "")
554
}
555

556
557
558
559
560
561
562
563
564
565
566
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() {
567
	defer func() {
568
		if e := recover(); e != nil {
569
			if session.Handler != nil {
570
				session.Handler.Failure(session.Action, panicToError(e))
571
			}
572
		}
573
574
575
576
577
	}()

	// 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.
578
	manager, err := session.client.Configuration.DownloadSchemeManager(session.ServerURL)
579
	if err != nil {
580
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
581
582
		return
	}
583

584
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
585
		if !proceed {
586
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
587
588
			return
		}
589
		if err := session.client.Configuration.AddSchemeManager(manager); err != nil {
590
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
591
592
			return
		}
593
594

		// Update state and inform user of success
595
		if manager.Distributed() {
596
			session.client.UnenrolledSchemeManagers = session.client.unenrolledSchemeManagers()
597
		}
598
		session.client.handler.UpdateConfiguration(
599
600
601
602
			&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
603
604
			},
		)
605
		session.Handler.Success(session.Action, "")
606
607
608
	})
	return
}
609
610
611

// Session lifetime functions

612
613
614
615
616
617
618
619
620
621
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
622
	}
623
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
624
625
626
}

// Idempotently send DELETE to remote server, returning whether or not we did something
627
func (session *interactiveSession) delete() bool {
628
	if !session.done {
629
		session.transport.Delete()
630
		session.done = true
631
632
633
634
635
		return true
	}
	return false
}

636
func (session *interactiveSession) fail(err *irma.SessionError) {
637
638
639
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		if session.downloaded != nil && !session.downloaded.Empty() {
640
			session.client.handler.UpdateConfiguration(session.downloaded)
641
642
643
644
645
		}
		session.Handler.Failure(session.Action, err)
	}
}

646
func (session *interactiveSession) cancel() {
647
648
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
649
			session.client.handler.UpdateConfiguration(session.downloaded)
650
651
652
653
654
		}
		session.Handler.Cancelled(session.Action)
	}
}

655
func (session *interactiveSession) Dismiss() {
656
657
	session.cancel()
}
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673

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
}