session.go 12 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
type PinHandler func(proceed bool, pin string)

22
23
24
25
26
// 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
27
	Failure(action Action, err *SessionError)
Sietse Ringers's avatar
Sietse Ringers committed
28
	UnsatisfiableRequest(action Action, missing AttributeDisjunctionList)
Sietse Ringers's avatar
Sietse Ringers committed
29
	MissingKeyshareServer(manager SchemeManagerIdentifier)
30

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

36
	RequestPin(remainingAttempts int, callback PinHandler)
37
38
}

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

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

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

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

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

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

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

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

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

	go session.start()

Sietse Ringers's avatar
Sietse Ringers committed
133
	return
134
135
}

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

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

153
154
// 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
155
func (session *session) start() {
156
157
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

158
159
160
161
162
	if session.Action == ActionSchemeManager {
		session.managerSession()
		return
	}

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

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

Sietse Ringers's avatar
Sietse Ringers committed
188
189
190
191
192
193
194
195
196
197
198
	// Check if we are registered to all involved keyshare servers
	for id := range session.irmaSession.Identifiers().SchemeManagers {
		distributed := session.credManager.ConfigurationStore.SchemeManagers[id].Distributed()
		_, registered := session.credManager.keyshareServers[id]
		if distributed && !registered {
			session.transport.Delete()
			session.Handler.MissingKeyshareServer(id)
			return
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
199
	// Download missing credential types/issuers/public keys from the scheme manager
200
	if session.downloaded, err = session.credManager.ConfigurationStore.Download(session.irmaSession.Identifiers()); err != nil {
201
202
203
204
205
206
207
		session.Handler.Failure(
			session.Action,
			&SessionError{ErrorType: ErrorConfigurationStoreDownload, Err: err},
		)
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
	if session.Action == ActionIssuing {
		ir := session.irmaSession.(*IssuanceRequest)
		for _, credreq := range ir.Credentials {
211
212
213
214
215
216
			info, err := credreq.Info(session.credManager.ConfigurationStore)
			if err != nil {
				session.fail(&SessionError{ErrorType: ErrorUnknownCredentialType, Err: err})
				return
			}
			ir.CredentialInfoList = append(ir.CredentialInfoList, info)
217
218
219
		}
	}

220
	candidates, missing := session.credManager.CheckSatisfiability(session.irmaSession.ToDisclose())
221
222
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
223
		// TODO: session.transport.Delete() on dialog cancel
224
225
		return
	}
226
	session.irmaSession.SetCandidates(candidates)
227

Sietse Ringers's avatar
Sietse Ringers committed
228
	// Ask for permission to execute the session
Sietse Ringers's avatar
Sietse Ringers committed
229
	callback := PermissionHandler(func(proceed bool, choice *DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
230
231
232
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
233
234
235
236
	})
	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
237
238
		session.Handler.RequestVerificationPermission(
			*session.irmaSession.(*DisclosureRequest), session.jwt.Requestor(), callback)
239
	case ActionSigning:
240
241
		session.Handler.RequestSignaturePermission(
			*session.irmaSession.(*SignatureRequest), session.jwt.Requestor(), callback)
242
	case ActionIssuing:
243
244
		session.Handler.RequestIssuancePermission(
			*session.irmaSession.(*IssuanceRequest), session.jwt.Requestor(), callback)
245
246
247
248
249
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
250
func (session *session) do(proceed bool) {
251
	if !proceed {
252
		session.cancel()
253
254
255
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
256

257
	if !session.irmaSession.Identifiers().Distributed(session.credManager.ConfigurationStore) {
Sietse Ringers's avatar
Sietse Ringers committed
258
259
260
261
		var message interface{}
		var err error
		switch session.Action {
		case ActionSigning:
262
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, true)
Sietse Ringers's avatar
Sietse Ringers committed
263
		case ActionDisclosing:
264
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, false)
Sietse Ringers's avatar
Sietse Ringers committed
265
		case ActionIssuing:
266
			message, err = session.credManager.IssueCommitments(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
267
268
		}
		if err != nil {
Tomas's avatar
Tomas committed
269
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
270
271
272
273
			return
		}
		session.sendResponse(message)
	} else {
274
		var builders gabi.ProofBuilderList
Sietse Ringers's avatar
Sietse Ringers committed
275
276
277
278
279
		var err error
		switch session.Action {
		case ActionSigning:
			fallthrough
		case ActionDisclosing:
280
			builders, err = session.credManager.ProofBuilders(session.choice)
Sietse Ringers's avatar
Sietse Ringers committed
281
		case ActionIssuing:
282
			builders, err = session.credManager.IssuanceProofBuilders(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
283
284
		}
		if err != nil {
Tomas's avatar
Tomas committed
285
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
286
287
		}

288
289
290
291
292
293
294
295
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
			session.credManager.ConfigurationStore,
			session.credManager.keyshareServers,
		)
296
	}
Sietse Ringers's avatar
Sietse Ringers committed
297
}
298

Sietse Ringers's avatar
Sietse Ringers committed
299
300
301
302
func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

Sietse Ringers's avatar
Sietse Ringers committed
303
func (session *session) KeyshareCancelled() {
304
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
305
306
}

Sietse Ringers's avatar
Sietse Ringers committed
307
func (session *session) KeyshareBlocked(duration int) {
Tomas's avatar
Tomas committed
308
	session.fail(&SessionError{ErrorType: ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
309
310
311
}

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

Sietse Ringers's avatar
Sietse Ringers committed
315
316
type disclosureResponse string

Sietse Ringers's avatar
Sietse Ringers committed
317
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
318
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
319
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
320

Sietse Ringers's avatar
Sietse Ringers committed
321
322
323
324
	switch session.Action {
	case ActionSigning:
		fallthrough
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
325
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
326
		if err = session.transport.Post("proofs", &response, message); err != nil {
Tomas's avatar
Tomas committed
327
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
328
329
330
			return
		}
		if response != "VALID" {
Tomas's avatar
Tomas committed
331
			session.fail(&SessionError{ErrorType: ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
332
333
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
334
		log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
Sietse Ringers's avatar
Sietse Ringers committed
335
336
	case ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
337
		if err = session.transport.Post("commitments", &response, message); err != nil {
Tomas's avatar
Tomas committed
338
			session.fail(err.(*SessionError))
Sietse Ringers's avatar
Sietse Ringers committed
339
340
			return
		}
341
		if err = session.credManager.ConstructCredentials(response, session.irmaSession.(*IssuanceRequest)); err != nil {
Tomas's avatar
Tomas committed
342
			session.fail(&SessionError{ErrorType: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
343
344
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
345
		log, _ = session.createLogEntry(message) // TODO err
346
347
	}

348
	_ = session.credManager.addLogEntry(log) // TODO err
349
350
351
352
353
354
	if !session.downloaded.Empty() {
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
	if session.Action == ActionIssuing {
		session.credManager.handler.UpdateAttributes()
	}
355
	session.Handler.Success(session.Action)
356
}
357
358
359
360
361
362
363

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
	}
364
	session.Handler.RequestSchemeManagerPermission(manager, func(proceed bool) {
365
		if !proceed {
366
			session.Handler.Cancelled(session.Action) // No need to DELETE session here
367
368
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
369
370
371
372
		if err := session.credManager.ConfigurationStore.AddSchemeManager(manager); err != nil {
			session.Handler.Failure(session.Action, &SessionError{})
			return
		}
373
		if manager.Distributed() {
Sietse Ringers's avatar
Sietse Ringers committed
374
			session.credManager.UnenrolledKeyshareServers = session.credManager.unenrolledKeyshareServers()
375
		}
Sietse Ringers's avatar
Sietse Ringers committed
376
377
378
379
380
381
382
383
		session.credManager.handler.UpdateConfigurationStore(
			&IrmaIdentifierSet{
				SchemeManagers:  map[SchemeManagerIdentifier]struct{}{manager.Identifier(): {}},
				Issuers:         map[IssuerIdentifier]struct{}{},
				CredentialTypes: map[CredentialTypeIdentifier]struct{}{},
			},
		)
		session.Handler.Success(session.Action)
384
385
386
	})
	return
}