session.go 19.9 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
	KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier)
40
41
42
43
44

	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
45

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

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

54
55
56
57
58
59
60
61
62
// 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
63
type session struct {
Koen van Ingen's avatar
Koen van Ingen committed
64
65
	Action  irma.Action
	Handler Handler
66
	Version *irma.ProtocolVersion
67
68
69
70

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

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

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

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

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

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

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

Koen van Ingen's avatar
Koen van Ingen committed
127
128
129
130
131
132
func (session *session) getBuilders() (gabi.ProofBuilderList, error) {
	var builders gabi.ProofBuilderList
	var err error

	switch session.Action {
	case irma.ActionSigning:
133
		builders, err = session.client.ProofBuilders(session.choice, session.irmaSession, true)
Koen van Ingen's avatar
Koen van Ingen committed
134
	case irma.ActionDisclosing:
135
		builders, err = session.client.ProofBuilders(session.choice, session.irmaSession, false)
Koen van Ingen's avatar
Koen van Ingen committed
136
137
138
	case irma.ActionIssuing:
		builders, err = session.client.IssuanceProofBuilders(session.irmaSession.(*irma.IssuanceRequest))
	}
139

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	go session.start()

303
	return session
304
305
}

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

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

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

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

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

343
344
	if session.Action == irma.ActionIssuing {
		ir := session.irmaSession.(*irma.IssuanceRequest)
345
		ir.RemovalCredentialInfoList = irma.CredentialInfoList{}
Sietse Ringers's avatar
Sietse Ringers committed
346
		for _, credreq := range ir.Credentials {
347
			info, err := credreq.Info(session.client.Configuration, getMetadataVersion(session.Version))
348
			if err != nil {
349
				session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
350
351
352
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
353
354
355
356
			preexistingCredentials := session.client.attrs(*credreq.CredentialTypeID)
			if len(preexistingCredentials) != 0 && preexistingCredentials[0].IsValid() && preexistingCredentials[0].CredentialType().IsSingleton {
				ir.RemovalCredentialInfoList = append(ir.RemovalCredentialInfoList, preexistingCredentials[0].Info())
			}
357
358
359
		}
	}

360
	candidates, missing := session.client.CheckSatisfiability(session.irmaSession.ToDisclose())
361
	if len(missing) > 0 {
362
		session.Handler.UnsatisfiableRequest(session.Action, session.jwt.Requestor(), missing)
363
364
		return
	}
365
	session.irmaSession.SetCandidates(candidates)
366

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

389
func (session *session) do(proceed bool) {
Koen van Ingen's avatar
Koen van Ingen committed
390
	defer session.panicFailure()
391

392
	if !proceed {
393
		session.cancel()
394
395
		return
	}
396
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
397

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

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
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
}

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

451
func (session *session) KeyshareCancelled() {
452
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
453
454
}

Sietse Ringers's avatar
Sietse Ringers committed
455
func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
456
	session.Handler.KeyshareEnrollmentIncomplete(manager)
Sietse Ringers's avatar
Sietse Ringers committed
457
458
}

459
460
461
462
func (session *session) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareEnrollmentDeleted(manager)
}

463
func (session *session) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
464
	session.Handler.KeyshareBlocked(manager, duration)
Sietse Ringers's avatar
Sietse Ringers committed
465
466
}

467
func (session *session) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
468
469
470
471
472
473
474
475
	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
476
477
}

Koen van Ingen's avatar
Koen van Ingen committed
478
func (session *session) KeysharePin() {
479
480
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
481

Koen van Ingen's avatar
Koen van Ingen committed
482
func (session *session) KeysharePinOK() {
483
484
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
485

Sietse Ringers's avatar
Sietse Ringers committed
486
487
type disclosureResponse string

488
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
489
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
490
	var err error
491
492
	var messageJson []byte

493
494
	switch session.Action {
	case irma.ActionSigning:
495
		request, ok := session.irmaSession.(*irma.SignatureRequest)
496
497
498
499
500
		if !ok {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

501
502
503
504
505
506
		irmaSignature, err := request.SignatureFromMessage(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

507
508
509
510
511
512
513
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
514
			var response disclosureResponse
515
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
516
517
518
519
520
521
522
				session.fail(err.(*irma.SessionError))
				return
			}
			if response != "VALID" {
				session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
				return
			}
Sietse Ringers's avatar
Sietse Ringers committed
523
		}
524
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
	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})
Sietse Ringers's avatar
Sietse Ringers committed
544
545
			return
		}
546
		log, _ = session.createLogEntry(message) // TODO err
547
548
	}

549
	_ = session.client.addLogEntry(log) // TODO err
550
	if session.Action == irma.ActionIssuing {
551
		session.client.handler.UpdateAttributes()
552
	}
553
	session.done = true
554
555
556
	session.Handler.Success(session.Action, string(messageJson))
}

557
func (session *session) managerSession() {
558
	defer func() {
559
		if e := recover(); e != nil {
560
			if session.Handler != nil {
561
				session.Handler.Failure(session.Action, panicToError(e))
562
			}
563
		}
564
565
566
567
568
	}()

	// 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.
569
	manager, err := irma.DownloadSchemeManager(session.ServerURL)
570
	if err != nil {
571
		session.Handler.Failure(session.Action, &irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
572
573
		return
	}
574

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

		// Update state and inform user of success
586
		session.client.handler.UpdateConfiguration(
587
588
589
590
			&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
591
592
			},
		)
593
		session.Handler.Success(session.Action, "")
594
595
596
	})
	return
}
597
598
599

// Session lifetime functions

600
601
602
603
604
605
606
607
608
609
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
610
	}
611
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
612
613
614
}

// Idempotently send DELETE to remote server, returning whether or not we did something
615
func (session *session) delete() bool {
616
	if !session.done {
617
618
619
		if session.IsInteractive() {
			session.transport.Delete()
		}
620
		session.done = true
621
622
623
624
625
		return true
	}
	return false
}

626
func (session *session) fail(err *irma.SessionError) {
627
628
629
630
631
632
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		session.Handler.Failure(session.Action, err)
	}
}

633
func (session *session) cancel() {
634
635
636
637
638
	if session.delete() {
		session.Handler.Cancelled(session.Action)
	}
}

639
func (session *session) Dismiss() {
640
641
	session.cancel()
}
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657

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
}