session.go 18.6 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
40
41
42
43

	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
44

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

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

53
54
55
56
57
58
59
60
61
// getMetadataVersion maps a chosen protocol version to a metadata version that
// the server will use.
func getMetadataVersion(v *irma.ProtocolVersion) byte {
	if v.Below(2, 3) {
		return 0x02 // no support for optional attributes
	}
	return 0x03 // current version
}

Sietse Ringers's avatar
Sietse Ringers committed
62
type session struct {
Koen van Ingen's avatar
Koen van Ingen committed
63
64
	Action  irma.Action
	Handler Handler
65
	Version *irma.ProtocolVersion
66
67
68
69

	choice      *irma.DisclosureChoice
	client      *Client
	irmaSession irma.IrmaSession
70
	done        bool
71

72
	// These are empty on manual sessions
73
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
74
75
76
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
77
78
}

79
// We implement the handler for the keyshare protocol
80
var _ keyshareSessionHandler = (*session)(nil)
81

82
83
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
84
	2: {3, 2, 1},
85
86
}

87
func calcVersion(qr *irma.Qr) (*irma.ProtocolVersion, error) {
88
	// Parse range supported by server
89
90
91
	var minmajor, minminor, maxmajor, maxminor int
	var err error
	if minmajor, err = strconv.Atoi(string(qr.ProtocolVersion[0])); err != nil {
92
		return nil, err
93
94
	}
	if minminor, err = strconv.Atoi(string(qr.ProtocolVersion[2])); err != nil {
95
		return nil, err
96
97
	}
	if maxmajor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[0])); err != nil {
98
		return nil, err
99
100
	}
	if maxminor, err = strconv.Atoi(string(qr.ProtocolMaxVersion[2])); err != nil {
101
		return nil, err
102
103
104
105
	}

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

122
123
124
125
func (session *session) IsInteractive() bool {
	return session.ServerURL != ""
}

Koen van Ingen's avatar
Koen van Ingen committed
126
127
128
129
130
131
132
133
134
135
136
137
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))
	}
138

Koen van Ingen's avatar
Koen van Ingen committed
139
	return builders, err
140
141
}

Koen van Ingen's avatar
Koen van Ingen committed
142
143
144
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
145

Koen van Ingen's avatar
Koen van Ingen committed
146
147
148
149
150
151
152
153
	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))
	}
154

Koen van Ingen's avatar
Koen van Ingen committed
155
156
	return message, err
}
157

158
159
// 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
160
func (session *session) checkKeyshareEnrollment() bool {
161
	for id := range session.irmaSession.Identifiers().SchemeManagers {
162
163
164
		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
165
			return false
166
167
168
169
		}
		distributed := manager.Distributed()
		_, enrolled := session.client.keyshareServers[id]
		if distributed && !enrolled {
170
			session.Handler.KeyshareEnrollmentMissing(id)
Koen van Ingen's avatar
Koen van Ingen committed
171
			return false
172
173
		}
	}
Koen van Ingen's avatar
Koen van Ingen committed
174
175
176
177
178
179
180
181
182
183
184
	return true
}

func (session *session) panicFailure() {
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

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

Sietse Ringers's avatar
Sietse Ringers committed
221
// 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
222
func (client *Client) NewManualSession(sigrequestJSONString string, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
223
224
225
	var err error
	sigrequest := &irma.SignatureRequest{}
	if err = json.Unmarshal([]byte(sigrequestJSONString), sigrequest); err != nil {
Koen van Ingen's avatar
Koen van Ingen committed
226
227
228
229
		handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
		return
	}

230
231
232
233
	session := &session{
		Action:      irma.ActionSigning, // TODO hardcoded for now
		Handler:     handler,
		client:      client,
234
		Version:     irma.NewVersion(2, 0), // TODO hardcoded for now
235
		irmaSession: sigrequest,
Koen van Ingen's avatar
Koen van Ingen committed
236
237
238
239
	}

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

240
	if !session.checkAndUpateConfiguration() {
241
242
243
		return
	}

244
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
245
	if len(missing) > 0 {
246
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
247
248
		return
	}
249
	session.irmaSession.SetCandidates(candidates)
250
251
252
253

	// Ask for permission to execute the session
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
		session.choice = choice
254
		session.irmaSession.SetDisclosureChoice(choice)
Koen van Ingen's avatar
Koen van Ingen committed
255
		go session.do(proceed)
256
257
	})
	session.Handler.RequestSignaturePermission(
Koen van Ingen's avatar
Koen van Ingen committed
258
		*session.irmaSession.(*irma.SignatureRequest), "E-mail request", callback)
259
260
}

261
// NewSession creates and starts a new interactive IRMA session
262
func (client *Client) NewSession(qr *irma.Qr, handler Handler) SessionDismisser {
263
	session := &session{
264
		ServerURL: qr.URL,
265
		transport: irma.NewHTTPTransport(qr.URL),
266
267
268
		Action:    irma.Action(qr.Type),
		Handler:   handler,
		client:    client,
269
	}
270
271
272
273
274
275

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

276
277
	version, err := calcVersion(qr)
	if err != nil {
278
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
279
		return nil
280
	}
281
282
	session.Version = version
	session.transport.SetHeader("X-IRMA-ProtocolVersion", version.String())
283
284
285

	// Check if the action is one of the supported types
	switch session.Action {
286
287
288
289
	case irma.ActionDisclosing: // nop
	case irma.ActionSigning: // nop
	case irma.ActionIssuing: // nop
	case irma.ActionUnknown:
290
291
		fallthrough
	default:
292
		session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownAction, Info: string(session.Action)})
293
		return nil
294
295
296
297
298
299
300
301
	}

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

	go session.start()

302
	return session
303
304
}

305
306
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
307
func (session *session) start() {
Koen van Ingen's avatar
Koen van Ingen committed
308
	defer session.panicFailure()
309

310
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
311

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

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

338
	if !session.checkAndUpateConfiguration() {
339
340
341
		return
	}

342
343
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
Sietse Ringers's avatar
Sietse Ringers committed
344
		for _, credreq := range ir.Credentials {
345
			info, err := credreq.Info(session.client.Configuration, getMetadataVersion(session.Version))
346
			if err != nil {
347
				session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
348
349
350
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
351
352
353
		}
	}

354
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
355
	if len(missing) > 0 {
356
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
357
358
		return
	}
359
	session.irmaSession.SetCandidates(candidates)
360

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

383
func (session *session) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
384
	defer session.panicFailure()
385

386
	if !proceed {
387
		session.cancel()
388
389
		return
	}
390
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
391

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

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

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

445
func (session *session) KeyshareCancelled() {
446
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
447
448
}

Sietse Ringers's avatar
Sietse Ringers committed
449
func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
450
	session.Handler.KeyshareEnrollmentIncomplete(manager)
Sietse Ringers's avatar
Sietse Ringers committed
451
452
}

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

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

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

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

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

478
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
479
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
480
	var err error
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
	var messageJson []byte

	if session.IsInteractive() {
		switch session.Action {
		case irma.ActionSigning:
			fallthrough
		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
			}
			if err = session.client.ConstructCredentials(response, session.irmaSession.(*irma.IssuanceRequest)); err != nil {
				session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
				return
			}
			log, _ = session.createLogEntry(message) // TODO err
Sietse Ringers's avatar
Sietse Ringers committed
509
		}
510
511
512
513
	} else {
		messageJson, err = json.Marshal(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
514
515
			return
		}
516
517
	}

518
	_ = session.client.addLogEntry(log) // TODO err
519
	if session.Action == irma.ActionIssuing {
520
		session.client.handler.UpdateAttributes()
521
	}
522
	session.done = true
523
524
525
	session.Handler.Success(session.Action, string(messageJson))
}

526
func (session *session) managerSession() {
527
	defer func() {
528
		if e := recover(); e != nil {
529
			if session.Handler != nil {
530
				session.Handler.Failure(session.Action, panicToError(e))
531
			}
532
		}
533
534
535
536
537
	}()

	// 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.
538
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
539
	if err != nil {
540
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
541
542
		return
	}
543

544
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
545
		if !proceed {
546
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
547
548
			return
		}
549
		if err := session.client.Configuration.InstallSchemeManager(manager); err != nil {
550
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
551
552
			return
		}
553
554

		// Update state and inform user of success
555
		if manager.Distributed() {
556
			session.client.UnenrolledSchemeManagers = session.client.unenrolledSchemeManagers()
557
		}
558
		session.client.handler.UpdateConfiguration(
559
560
561
562
			&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
563
564
			},
		)
565
		session.Handler.Success(session.Action, "")
566
567
568
	})
	return
}
569
570
571

// Session lifetime functions

572
573
574
575
576
577
578
579
580
581
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
582
	}
583
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
584
585
586
}

// Idempotently send DELETE to remote server, returning whether or not we did something
587
func (session *session) delete() bool {
588
	if !session.done {
589
590
591
		if session.IsInteractive() {
			session.transport.Delete()
		}
592
		session.done = true
593
594
595
596
597
		return true
	}
	return false
}

598
func (session *session) fail(err *irma.SessionError) {
599
600
601
602
603
604
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		session.Handler.Failure(session.Action, err)
	}
}

605
func (session *session) cancel() {
606
607
608
609
610
	if session.delete() {
		session.Handler.Cancelled(session.Action)
	}
}

611
func (session *session) Dismiss() {
612
613
	session.cancel()
}
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629

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
}