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 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
	Cancelled(action irma.Action)
	Failure(action irma.Action, err *irma.SessionError)
33
	UnsatisfiableRequest(action irma.Action, ServerName string, 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()
}

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

	choice      *irma.DisclosureChoice
	client      *Client
Koen van Ingen's avatar
Koen van Ingen committed
59
	downloaded  *irma.IrmaIdentifierSet
60
	irmaSession irma.IrmaSession
61
	done        bool
62

63
	// These are empty on manual sessions
64
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
65
66
67
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
68
69
}

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

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

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

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
97
	for k := range supportedVersions {
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
		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)
}

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

Koen van Ingen's avatar
Koen van Ingen committed
117
118
119
120
121
122
123
124
125
126
127
128
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))
	}
129

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

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

Koen van Ingen's avatar
Koen van Ingen committed
137
138
139
140
141
142
143
144
	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))
	}
145

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

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

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

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

218
219
220
221
222
223
	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
224
225
226
227
	}

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

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

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

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

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

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

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

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

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

	go session.start()

290
	return session
291
292
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

441
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
442
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
443
	var err error
444
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
	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
472
		}
473
474
475
476
	} 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
477
478
			return
		}
479
480
	}

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

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

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

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

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

// Session lifetime functions

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

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

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

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

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

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
}