session.go 12.1 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
package irmago
2
3

import (
Sietse Ringers's avatar
Sietse Ringers committed
4
5
	"fmt"
	"sort"
6
	"strconv"
7
8
	"strings"

9
	"github.com/go-errors/errors"
10
	"github.com/mhe/gabi"
11
12
)

Sietse Ringers's avatar
Sietse Ringers committed
13
14
15
// 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
16
17
// PermissionHandler is a callback for providing permission for an IRMA session
// and specifying the attributes to be disclosed.
Sietse Ringers's avatar
Sietse Ringers committed
18
type PermissionHandler func(proceed bool, choice *DisclosureChoice)
19
20
21
22
23
24

// A Handler contains callbacks for communication to the user.
type Handler interface {
	StatusUpdate(action Action, status Status)
	Success(action Action)
	Cancelled(action Action)
Tomas's avatar
Tomas committed
25
	Failure(action Action, err *SessionError)
Sietse Ringers's avatar
Sietse Ringers committed
26
	UnsatisfiableRequest(action Action, missing AttributeDisjunctionList)
27

28
29
30
	RequestIssuancePermission(request IssuanceRequest, ServerName string, callback PermissionHandler)
	RequestVerificationPermission(request DisclosureRequest, ServerName string, callback PermissionHandler)
	RequestSignaturePermission(request SignatureRequest, ServerName string, callback PermissionHandler)
31
	RequestSchemeManagerPermission(manager *SchemeManager, callback func(proceed bool))
Sietse Ringers's avatar
Sietse Ringers committed
32

33
	RequestPin(remainingAttempts int, callback func(proceed bool, pin string))
34
35
}

Sietse Ringers's avatar
Sietse Ringers committed
36
37
// A session is an IRMA session.
type session struct {
38
39
40
41
42
	Action    Action
	Version   Version
	ServerURL string
	Handler   Handler

Sietse Ringers's avatar
Sietse Ringers committed
43
	info        *SessionInfo
44
	credManager *CredentialManager
Sietse Ringers's avatar
Sietse Ringers committed
45
	jwt         RequestorJwt
46
	irmaSession IrmaSession
Sietse Ringers's avatar
Sietse Ringers committed
47
48
	transport   *HTTPTransport
	choice      *DisclosureChoice
49
	newmanager  *SchemeManager
50
	downloaded  *IrmaIdentifierSet
51
52
}

53
54
55
// We implement the handler for the keyshare protocol
var _ keyshareSessionHandler = (*session)(nil)

56
57
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
58
	2: {2, 1},
59
60
61
62
}

func calcVersion(qr *Qr) (string, error) {
	// Parse range supported by server
63
64
65
66
67
68
69
70
71
72
73
74
	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 {
75
76
77
78
79
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
80
	for k := range supportedVersions {
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
		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)
}

96
// NewSession creates and starts a new IRMA session.
97
func (cm *CredentialManager) NewSession(qr *Qr, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
98
	session := &session{
99
100
101
102
		Action:      Action(qr.Type),
		ServerURL:   qr.URL,
		Handler:     handler,
		transport:   NewHTTPTransport(qr.URL),
103
		credManager: cm,
104
	}
105
106
	version, err := calcVersion(qr)
	if err != nil {
Tomas's avatar
Tomas committed
107
		session.fail(&SessionError{ErrorType: ErrorProtocolVersionNotSupported, Err: err})
108
109
110
		return
	}
	session.Version = Version(version)
111
112
113
114
115
116

	// Check if the action is one of the supported types
	switch session.Action {
	case ActionDisclosing: // nop
	case ActionSigning: // nop
	case ActionIssuing: // nop
117
	//case ActionSchemeManager: // nop
118
119
120
	case ActionUnknown:
		fallthrough
	default:
Tomas's avatar
Tomas committed
121
		session.fail(&SessionError{ErrorType: ErrorUnknownAction, Info: string(session.Action)})
Sietse Ringers's avatar
Sietse Ringers committed
122
		return
123
124
125
126
127
128
129
130
	}

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

	go session.start()

Sietse Ringers's avatar
Sietse Ringers committed
131
	return
132
133
}

Tomas's avatar
Tomas committed
134
func (session *session) fail(err *SessionError) {
135
136
	session.transport.Delete()
	err.Err = errors.Wrap(err.Err, 0)
Sietse Ringers's avatar
Sietse Ringers committed
137
	if session.downloaded != nil && !session.downloaded.Empty() {
138
139
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
140
141
142
	session.Handler.Failure(session.Action, err)
}

143
144
func (session *session) cancel() {
	session.transport.Delete()
Sietse Ringers's avatar
Sietse Ringers committed
145
	if session.downloaded != nil && !session.downloaded.Empty() {
146
147
148
149
150
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
	session.Handler.Cancelled(session.Action)
}

151
152
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
Sietse Ringers's avatar
Sietse Ringers committed
153
func (session *session) start() {
154
155
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

156
157
158
159
160
	if session.Action == ActionSchemeManager {
		session.managerSession()
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
161
	// Get the first IRMA protocol message and parse it
Sietse Ringers's avatar
Sietse Ringers committed
162
163
	session.info = &SessionInfo{}
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
164
	if Err != nil {
Tomas's avatar
Tomas committed
165
		session.fail(Err.(*SessionError))
166
167
168
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
169
170
171
	var server string
	var err error
	session.jwt, server, err = parseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
172
	if err != nil {
Tomas's avatar
Tomas committed
173
		session.fail(&SessionError{ErrorType: ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
174
175
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
176
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
177
178
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
179
	if session.Action == ActionIssuing {
180
		ir := session.irmaSession.(*IssuanceRequest)
181
		// Store which public keys the server will use
182
		for _, credreq := range ir.Credentials {
183
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
184
185
186
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
187
	// Download missing credential types/issuers/public keys from the scheme manager
188
	if session.downloaded, err = session.credManager.ConfigurationStore.Download(session.irmaSession.Identifiers()); err != nil {
189
190
191
192
193
194
195
		session.Handler.Failure(
			session.Action,
			&SessionError{ErrorType: ErrorConfigurationStoreDownload, Err: err},
		)
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
196
197
198
	if session.Action == ActionIssuing {
		ir := session.irmaSession.(*IssuanceRequest)
		for _, credreq := range ir.Credentials {
199
200
201
202
203
204
			info, err := credreq.Info(session.credManager.ConfigurationStore)
			if err != nil {
				session.fail(&SessionError{ErrorType: ErrorUnknownCredentialType, Err: err})
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
205
206
207
		}
	}

208
	candidates, missing := session.credManager.CheckSatisfiability(session.irmaSession.ToDisclose())
209
210
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
211
		// TODO: session.transport.Delete() on dialog cancel
212
213
		return
	}
214
	session.irmaSession.SetCandidates(candidates)
215

Sietse Ringers's avatar
Sietse Ringers committed
216
	// Ask for permission to execute the session
Sietse Ringers's avatar
Sietse Ringers committed
217
	callback := PermissionHandler(func(proceed bool, choice *DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
218
219
220
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
221
222
223
224
	})
	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
225
		session.Handler.RequestVerificationPermission(*session.irmaSession.(*DisclosureRequest), server, callback)
226
	case ActionSigning:
227
		session.Handler.RequestSignaturePermission(*session.irmaSession.(*SignatureRequest), server, callback)
228
	case ActionIssuing:
229
		session.Handler.RequestIssuancePermission(*session.irmaSession.(*IssuanceRequest), server, callback)
230
231
232
233
234
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
235
func (session *session) do(proceed bool) {
236
	if !proceed {
237
		session.cancel()
238
239
240
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
241

242
	if !session.irmaSession.Identifiers().Distributed(session.credManager.ConfigurationStore) {
Sietse Ringers's avatar
Sietse Ringers committed
243
244
245
246
		var message interface{}
		var err error
		switch session.Action {
		case ActionSigning:
247
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, true)
Sietse Ringers's avatar
Sietse Ringers committed
248
		case ActionDisclosing:
249
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, false)
Sietse Ringers's avatar
Sietse Ringers committed
250
		case ActionIssuing:
251
			message, err = session.credManager.IssueCommitments(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
252
253
		}
		if err != nil {
Tomas's avatar
Tomas committed
254
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
255
256
257
258
			return
		}
		session.sendResponse(message)
	} else {
259
		var builders gabi.ProofBuilderList
Sietse Ringers's avatar
Sietse Ringers committed
260
261
262
263
264
		var err error
		switch session.Action {
		case ActionSigning:
			fallthrough
		case ActionDisclosing:
265
			builders, err = session.credManager.ProofBuilders(session.choice)
Sietse Ringers's avatar
Sietse Ringers committed
266
		case ActionIssuing:
267
			builders, err = session.credManager.IssuanceProofBuilders(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
268
269
		}
		if err != nil {
Tomas's avatar
Tomas committed
270
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
271
272
		}

273
274
275
276
277
278
279
280
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
			session.credManager.ConfigurationStore,
			session.credManager.keyshareServers,
		)
281
	}
Sietse Ringers's avatar
Sietse Ringers committed
282
}
283

Sietse Ringers's avatar
Sietse Ringers committed
284
285
286
287
func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

Sietse Ringers's avatar
Sietse Ringers committed
288
func (session *session) KeyshareCancelled() {
289
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
290
291
}

Sietse Ringers's avatar
Sietse Ringers committed
292
func (session *session) KeyshareBlocked(duration int) {
Tomas's avatar
Tomas committed
293
	session.fail(&SessionError{ErrorType: ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
294
295
296
}

func (session *session) KeyshareError(err error) {
Tomas's avatar
Tomas committed
297
	session.fail(&SessionError{ErrorType: ErrorKeyshare, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
298
299
}

Sietse Ringers's avatar
Sietse Ringers committed
300
301
type disclosureResponse string

Sietse Ringers's avatar
Sietse Ringers committed
302
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
303
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
304
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
305

Sietse Ringers's avatar
Sietse Ringers committed
306
307
308
309
	switch session.Action {
	case ActionSigning:
		fallthrough
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
310
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
311
		if err = session.transport.Post("proofs", &response, message); err != nil {
Tomas's avatar
Tomas committed
312
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
313
314
315
			return
		}
		if response != "VALID" {
Tomas's avatar
Tomas committed
316
			session.fail(&SessionError{ErrorType: ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
317
318
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
319
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
Sietse Ringers's avatar
Sietse Ringers committed
320
321
	case ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
322
		if err = session.transport.Post("commitments", &response, message); err != nil {
Tomas's avatar
Tomas committed
323
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
324
325
			return
		}
326
		if err = session.credManager.ConstructCredentials(response, session.irmaSession.(*IssuanceRequest)); err != nil {
Tomas's avatar
Tomas committed
327
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
328
329
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
330
		log, _ = session.createLogEntry(message) // TODO err
331
332
	}

333
	_ = session.credManager.addLogEntry(log) // TODO err
334
335
336
337
338
339
	if !session.downloaded.Empty() {
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
	if session.Action == ActionIssuing {
		session.credManager.handler.UpdateAttributes()
	}
340
	session.Handler.Success(session.Action)
341
}
342
343
344
345
346
347
348

func (session *session) managerSession() {
	manager, err := session.credManager.ConfigurationStore.DownloadSchemeManager(session.ServerURL)
	if err != nil {
		session.Handler.Failure(session.Action, &SessionError{Err: err}) // TODO
		return
	}
349
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
350
		if !proceed {
351
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
352
353
354
355
356
357
358
359
360
361
362
363
364
			return
		}
		session.newmanager = manager
		if manager.Distributed() {
			session.credManager.KeyshareEnroll(manager, KeyshareHandler(session))
		} else {
			session.RegistrationSuccess()
		}
	})
	return
}

func (session *session) StartRegistration(manager *SchemeManager, callback func(email, pin string)) {
365
	session.credManager.handler.StartRegistration(manager, callback)
366
367
368
369
}

func (session *session) RegistrationError(err error) {
	session.Handler.Failure(session.Action, &SessionError{Err: err}) // TODO
370
	session.credManager.handler.RegistrationError(err)
371
372
373
374
375
376
377
}

func (session *session) RegistrationSuccess() {
	if err := session.credManager.ConfigurationStore.AddSchemeManager(session.newmanager); err != nil {
		session.Handler.Failure(session.Action, &SessionError{})
		return
	}
378
379
380
381
382
383
384
	session.credManager.handler.UpdateConfigurationStore(
		&IrmaIdentifierSet{
			SchemeManagers:  map[SchemeManagerIdentifier]struct{}{session.newmanager.Identifier(): {}},
			Issuers:         map[IssuerIdentifier]struct{}{},
			CredentialTypes: map[CredentialTypeIdentifier]struct{}{},
		},
	)
385
	session.Handler.Success(session.Action)
386
387
388
	if session.newmanager.Distributed() {
		session.credManager.handler.RegistrationSuccess()
	}
389
}