session.go 19.7 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
38
39
40
41

	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
42

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

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

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

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

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

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

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

77
78
	ServerURL string

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

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

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

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

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

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

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

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

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

Koen van Ingen's avatar
Koen van Ingen committed
163
164
	return message, err
}
165

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

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

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

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

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

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

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

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

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

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

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

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

	go session.start()

318
	return session
319
320
}

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

326
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
327

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

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

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

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

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

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

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

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

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

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

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

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

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

459
460
461
462
func (session *manualSession) KeyshareCancelled() {
	session.Handler.Cancelled(session.Action)
}

463
464
465
466
467
468
469
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
470
471
}

472
473
474
func (session *manualSession) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
	// TODO: fire UpdateConfiguration()?
	session.Handler.KeyshareBlocked(manager, duration)
475
476
}

477
func (session *interactiveSession) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
478
479
480
481
482
483
484
485
	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
486
487
}

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

Koen van Ingen's avatar
Koen van Ingen committed
503
func (session *session) KeysharePinOK() {
504
505
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
506

Sietse Ringers's avatar
Sietse Ringers committed
507
508
type disclosureResponse string

509
func (session *interactiveSession) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
510
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
511
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
512

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

540
	_ = session.client.addLogEntry(log) // TODO err
541
	if !session.downloaded.Empty() {
542
		session.client.handler.UpdateConfiguration(session.downloaded)
543
	}
544
	if session.Action == irma.ActionIssuing {
545
		session.client.handler.UpdateAttributes()
546
	}
547
	session.done = true
548
	session.Handler.Success(session.Action, "")
549
}
550

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

	// 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.
573
	manager, err := session.client.Configuration.DownloadSchemeManager(session.ServerURL)
574
	if err != nil {
575
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
576
577
		return
	}
578

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

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

// Session lifetime functions

607
608
609
610
611
612
613
614
615
616
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
617
	}
618
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
619
620
621
}

// Idempotently send DELETE to remote server, returning whether or not we did something
622
func (session *interactiveSession) delete() bool {
623
	if !session.done {
624
		session.transport.Delete()
625
		session.done = true
626
627
628
629
630
		return true
	}
	return false
}

631
func (session *interactiveSession) fail(err *irma.SessionError) {
632
633
634
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		if session.downloaded != nil && !session.downloaded.Empty() {
635
			session.client.handler.UpdateConfiguration(session.downloaded)
636
637
638
639
640
		}
		session.Handler.Failure(session.Action, err)
	}
}

641
func (session *interactiveSession) cancel() {
642
643
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
644
			session.client.handler.UpdateConfiguration(session.downloaded)
645
646
647
648
649
		}
		session.Handler.Cancelled(session.Action)
	}
}

650
func (session *interactiveSession) Dismiss() {
651
652
	session.cancel()
}
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668

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
}