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

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

59
60
61
62
	choice  *irma.DisclosureChoice
	client  *Client
	request irma.SessionRequest
	done    bool
63

64
65
66
67
	// State for issuance protocol
	issuerProofNonce *big.Int
	builders         gabi.ProofBuilderList

68
	// These are empty on manual sessions
69
	ServerURL string
Koen van Ingen's avatar
Koen van Ingen committed
70
71
72
	info      *irma.SessionInfo
	jwt       irma.RequestorJwt
	transport *irma.HTTPTransport
73
74
}

75
// We implement the handler for the keyshare protocol
76
var _ keyshareSessionHandler = (*session)(nil)
77

78
79
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
80
	2: {3, 2, 1},
81
82
}

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

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

118
// IsInteractive returns whether this session uses an API server or not.
119
120
121
122
func (session *session) IsInteractive() bool {
	return session.ServerURL != ""
}

123
124
125
// getBuilders computes the builders for disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively).
func (session *session) getBuilders() (gabi.ProofBuilderList, *big.Int, error) {
Koen van Ingen's avatar
Koen van Ingen committed
126
127
128
	var builders gabi.ProofBuilderList
	var err error

129
	var issuerProofNonce *big.Int
Koen van Ingen's avatar
Koen van Ingen committed
130
131
	switch session.Action {
	case irma.ActionSigning:
132
		builders, err = session.client.ProofBuilders(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
133
	case irma.ActionDisclosing:
134
		builders, err = session.client.ProofBuilders(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
135
	case irma.ActionIssuing:
136
		builders, issuerProofNonce, err = session.client.IssuanceProofBuilders(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
137
	}
138

139
	return builders, issuerProofNonce, err
140
141
}

142
143
// getProofs computes the disclosure proofs or secretkey-knowledge proof (in case of disclosure/signing
// and issuing respectively) to be sent to the server.
Koen van Ingen's avatar
Koen van Ingen committed
144
145
146
func (session *session) getProof() (interface{}, error) {
	var message interface{}
	var err error
147

Koen van Ingen's avatar
Koen van Ingen committed
148
149
	switch session.Action {
	case irma.ActionSigning:
150
		message, err = session.client.Proofs(session.choice, session.request, true)
Koen van Ingen's avatar
Koen van Ingen committed
151
	case irma.ActionDisclosing:
152
		message, err = session.client.Proofs(session.choice, session.request, false)
Koen van Ingen's avatar
Koen van Ingen committed
153
	case irma.ActionIssuing:
154
		message, session.builders, err = session.client.IssueCommitments(session.request.(*irma.IssuanceRequest))
Koen van Ingen's avatar
Koen van Ingen committed
155
	}
156

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

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

Sietse Ringers's avatar
Sietse Ringers committed
179
func (session *session) recoverFromPanic() {
Koen van Ingen's avatar
Koen van Ingen committed
180
181
182
183
184
185
186
	if e := recover(); e != nil {
		if session.Handler != nil {
			session.Handler.Failure(session.Action, panicToError(e))
		}
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
302
	go session.getSessionInfo()
303

304
	return session
305
306
}

Sietse Ringers's avatar
Sietse Ringers committed
307
// getSessionInfo retrieves the first message in the IRMA protocol, checks if we can perform
308
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
309
310
func (session *session) getSessionInfo() {
	defer session.recoverFromPanic()
311

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

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

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

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

344
	if session.Action == irma.ActionIssuing {
345
		ir := session.request.(*irma.IssuanceRequest)
Tomas's avatar
Tomas committed
346
347
348
349
350
351
352
		_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownCredentialType, Err: err})
			return
		}

		// Calculate singleton credentials to be removed
Sietse Ringers's avatar
Sietse Ringers committed
353
		for _, credreq := range ir.Credentials {
354
355
356
357
			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())
			}
358
359
360
		}
	}

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

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

390
391
392
// doSession performs the session: it computes all proofs of knowledge, constructs credentials in case of issuance,
// asks for the pin and performs the keyshare session, and finishes the session by either POSTing the result to the
// API server or returning it to the caller (in case of interactive and noninteractive sessions, respectively).
Sietse Ringers's avatar
Sietse Ringers committed
393
394
func (session *session) doSession(proceed bool) {
	defer session.recoverFromPanic()
395

396
	if !proceed {
397
		session.cancel()
398
399
		return
	}
400
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
401

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

427
// Distributed returns whether or not this session involves a keyshare server.
428
429
430
func (session *session) Distributed() bool {
	var smi irma.SchemeManagerIdentifier
	if session.Action == irma.ActionIssuing {
431
		for _, credreq := range session.request.(*irma.IssuanceRequest).Credentials {
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
			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
}

453
func (session *session) KeyshareDone(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
454
455
456
	session.sendResponse(message)
}

457
func (session *session) KeyshareCancelled() {
458
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
459
460
}

Sietse Ringers's avatar
Sietse Ringers committed
461
func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
462
	session.Handler.KeyshareEnrollmentIncomplete(manager)
Sietse Ringers's avatar
Sietse Ringers committed
463
464
}

465
466
467
468
func (session *session) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
	session.Handler.KeyshareEnrollmentDeleted(manager)
}

469
func (session *session) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
470
	session.Handler.KeyshareBlocked(manager, duration)
Sietse Ringers's avatar
Sietse Ringers committed
471
472
}

473
func (session *session) KeyshareError(manager *irma.SchemeManagerIdentifier, err error) {
474
475
476
477
478
479
480
481
	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
482
483
}

Koen van Ingen's avatar
Koen van Ingen committed
484
func (session *session) KeysharePin() {
485
486
	session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
}
Sietse Ringers's avatar
Sietse Ringers committed
487

Koen van Ingen's avatar
Koen van Ingen committed
488
func (session *session) KeysharePinOK() {
489
490
	session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
Sietse Ringers's avatar
Sietse Ringers committed
491

Sietse Ringers's avatar
Sietse Ringers committed
492
493
type disclosureResponse string

494
495
// sendResponse sends the proofs of knowledge of the hidden attributes and/or the secret key, or the constructed
// attribute-based signature, to the API server.
496
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
497
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
498
	var err error
499
500
	var messageJson []byte

501
502
	switch session.Action {
	case irma.ActionSigning:
503
		request, ok := session.request.(*irma.SignatureRequest)
504
505
506
507
508
		if !ok {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

509
510
511
512
513
514
		irmaSignature, err := request.SignatureFromMessage(message)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
			return
		}

515
516
517
518
519
520
521
		messageJson, err = json.Marshal(irmaSignature)
		if err != nil {
			session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
			return
		}

		if session.IsInteractive() {
522
			var response disclosureResponse
523
			if err = session.transport.Post("proofs", &response, irmaSignature); err != nil {
524
525
526
527
528
529
530
				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
531
		}
532
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
	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
		}
550
		if err = session.client.ConstructCredentials(response, session.request.(*irma.IssuanceRequest), session.builders); err != nil {
551
			session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
552
553
			return
		}
554
		log, _ = session.createLogEntry(message) // TODO err
555
556
	}

557
	_ = session.client.addLogEntry(log) // TODO err
558
	if session.Action == irma.ActionIssuing {
559
		session.client.handler.UpdateAttributes()
560
	}
561
	session.done = true
562
563
564
	session.Handler.Success(session.Action, string(messageJson))
}

565
// managerSession performs a "session" in which a new scheme manager is added (asking for permission first).
566
func (session *session) managerSession() {
567
	defer session.recoverFromPanic()
568
569
570
571

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

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

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

// Session lifetime functions

603
604
605
606
607
608
609
610
611
612
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
613
	}
614
	fmt.Println("Panic: " + info)
615
	return &irma.SessionError{ErrorType: irma.ErrorPanic, Info: info}
616
617
618
}

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

630
func (session *session) fail(err *irma.SessionError) {
631
632
633
634
635
636
	if session.delete() {
		err.Err = errors.Wrap(err.Err, 0)
		session.Handler.Failure(session.Action, err)
	}
}

637
func (session *session) cancel() {
638
639
640
641
642
	if session.delete() {
		session.Handler.Cancelled(session.Action)
	}
}

643
func (session *session) Dismiss() {
644
645
	session.cancel()
}