session.go 18 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()
}

Sietse Ringers's avatar
Sietse Ringers committed
53
type session struct {
Koen van Ingen's avatar
Koen van Ingen committed
54
55
56
	Action  irma.Action
	Handler Handler
	Version irma.Version
57
58
59

	choice      *irma.DisclosureChoice
	client      *Client
Koen van Ingen's avatar
Koen van Ingen committed
60
	downloaded  *irma.IrmaIdentifierSet
61
	irmaSession irma.IrmaSession
62
	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: {2, 1},
77
78
}

79
func calcVersion(qr *irma.Qr) (string, error) {
80
	// Parse range supported by server
81
82
83
84
85
86
87
88
89
90
91
92
	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 {
93
94
95
96
97
		return "", err
	}

	// 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
107
108
109
110
111
112
113
		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)
}

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
124
125
126
127
128
129
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))
	}
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
140
141
142
143
144
145
	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))
	}
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.irmaSession.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
169
170
171
172
173
174
175
176
	return true
}

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

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

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

219
220
221
222
223
224
	session := &session{
		Action:      irma.ActionSigning, // TODO hardcoded for now
		Handler:     handler,
		client:      client,
		Version:     irma.Version("2"), // TODO hardcoded for now
		irmaSession: sigrequest,
Koen van Ingen's avatar
Koen van Ingen committed
225
226
227
228
	}

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

229
	if !session.checkAndUpateConfiguration(client) {
230
231
232
		return
	}

233
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
234
	if len(missing) > 0 {
235
		session.Handler.UnsatisfiableRequest(session.Action, "E-mail request", missing)
236
237
238
		// TODO: session.transport.Delete() on dialog cancel
		return
	}
239
	session.irmaSession.SetCandidates(candidates)
240
241
242
243

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

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

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

266
267
	version, err := calcVersion(qr)
	if err != nil {
268
		session.fail(&irma.SessionError{ErrorType: irma.ErrorProtocolVersionNotSupported, Err: err})
269
		return nil
270
	}
271
	session.Version = irma.Version(version)
272
273
274

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

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

	go session.start()

291
	return session
292
293
}

294
295
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
296
func (session *session) start() {
Koen van Ingen's avatar
Koen van Ingen committed
297
	defer session.panicFailure()
298

299
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
300

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

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

326
	if !session.checkAndUpateConfiguration(session.client) {
327
328
329
		return
	}

330
331
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
Sietse Ringers's avatar
Sietse Ringers committed
332
		for _, credreq := range ir.Credentials {
333
			info, err := credreq.Info(session.client.Configuration)
334
			if err != nil {
335
				session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
336
337
338
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
339
340
341
		}
	}

342
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
343
	if len(missing) > 0 {
344
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
345
		// TODO: session.transport.Delete() on dialog cancel
346
347
		return
	}
348
	session.irmaSession.SetCandidates(candidates)
349

Sietse Ringers's avatar
Sietse Ringers committed
350
	// Ask for permission to execute the session
351
	callback := PermissionHandler(func(proceed bool, choice *irma.DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
352
353
354
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
355
	})
356
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
357
	switch session.Action {
358
	case irma.ActionDisclosing:
359
		session.Handler.RequestVerificationPermission(
360
361
			*session.irmaSession.(*irma.DisclosureRequest), session.jwt.Requestor(), callback)
	case irma.ActionSigning:
362
		session.Handler.RequestSignaturePermission(
363
364
			*session.irmaSession.(*irma.SignatureRequest), session.jwt.Requestor(), callback)
	case irma.ActionIssuing:
365
		session.Handler.RequestIssuancePermission(
366
			*session.irmaSession.(*irma.IssuanceRequest), session.jwt.Requestor(), callback)
367
368
369
370
371
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

372
func (session *session) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
373
	defer session.panicFailure()
374

375
	if !proceed {
376
		session.cancel()
377
378
		return
	}
379
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
380

381
	if !session.irmaSession.Identifiers().Distributed(session.client.Configuration) {
Koen van Ingen's avatar
Koen van Ingen committed
382
		message, err := session.getProof()
Sietse Ringers's avatar
Sietse Ringers committed
383
		if err != nil {
384
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
385
386
387
388
			return
		}
		session.sendResponse(message)
	} else {
Koen van Ingen's avatar
Koen van Ingen committed
389
		builders, err := session.getBuilders()
Sietse Ringers's avatar
Sietse Ringers committed
390
		if err != nil {
391
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
392
		}
393
394
395
396
397
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
398
			session.client.Configuration,
399
			session.client.keyshareServers,
400
			session.client.state,
401
		)
402
	}
Sietse Ringers's avatar
Sietse Ringers committed
403
}
404

405
func (session *session) KeyshareDone(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
406
407
408
	session.sendResponse(message)
}

409
func (session *session) KeyshareCancelled() {
410
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
411
412
}

Sietse Ringers's avatar
Sietse Ringers committed
413
func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
414
	session.Handler.KeyshareEnrollmentIncomplete(manager)
Sietse Ringers's avatar
Sietse Ringers committed
415
416
}

417
func (session *session) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
418
	session.Handler.KeyshareBlocked(manager, duration)
Sietse Ringers's avatar
Sietse Ringers committed
419
420
}

421
func (session *session) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
422
423
424
425
426
427
428
429
	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
430
431
}

Koen van Ingen's avatar
Koen van Ingen committed
432
func (session *session) KeysharePin() {
433
434
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
435

Koen van Ingen's avatar
Koen van Ingen committed
436
func (session *session) KeysharePinOK() {
437
438
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
439

Sietse Ringers's avatar
Sietse Ringers committed
440
441
type disclosureResponse string

442
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
443
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
444
	var err error
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
	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
473
		}
474
475
476
477
	} 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
478
479
			return
		}
480
481
	}

482
	_ = session.client.addLogEntry(log) // TODO err
483
	if !session.downloaded.Empty() {
484
		session.client.handler.UpdateConfiguration(session.downloaded)
485
	}
486
	if session.Action == irma.ActionIssuing {
487
		session.client.handler.UpdateAttributes()
488
	}
489
	session.done = true
490
491
492
	session.Handler.Success(session.Action, string(messageJson))
}

493
func (session *session) managerSession() {
494
	defer func() {
495
		if e := recover(); e != nil {
496
			if session.Handler != nil {
497
				session.Handler.Failure(session.Action, panicToError(e))
498
			}
499
		}
500
501
502
503
504
	}()

	// 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.
505
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
506
	if err != nil {
507
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
508
509
		return
	}
510

511
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
512
		if !proceed {
513
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
514
515
			return
		}
516
		if err := session.client.Configuration.InstallSchemeManager(manager); err != nil {
517
			session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
518
519
			return
		}
520
521

		// Update state and inform user of success
522
		if manager.Distributed() {
523
			session.client.UnenrolledSchemeManagers = session.client.unenrolledSchemeManagers()
524
		}
525
		session.client.handler.UpdateConfiguration(
526
527
528
529
			&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
530
531
			},
		)
532
		session.Handler.Success(session.Action, "")
533
534
535
	})
	return
}
536
537
538

// Session lifetime functions

539
540
541
542
543
544
545
546
547
548
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
549
	}
550
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
551
552
553
}

// Idempotently send DELETE to remote server, returning whether or not we did something
554
func (session *session) delete() bool {
555
	if !session.done {
556
557
558
		if session.IsInteractive() {
			session.transport.Delete()
		}
559
		session.done = true
560
561
562
563
564
		return true
	}
	return false
}

565
func (session *session) fail(err *irma.SessionError) {
566
567
568
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		if session.downloaded != nil && !session.downloaded.Empty() {
569
			session.client.handler.UpdateConfiguration(session.downloaded)
570
571
572
573
574
		}
		session.Handler.Failure(session.Action, err)
	}
}

575
func (session *session) cancel() {
576
577
	if session.delete() {
		if session.downloaded != nil && !session.downloaded.Empty() {
578
			session.client.handler.UpdateConfiguration(session.downloaded)
579
580
581
582
583
		}
		session.Handler.Cancelled(session.Action)
	}
}

584
func (session *session) Dismiss() {
585
586
	session.cancel()
}
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602

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
}