session.go 12.2 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
	// Check if we are registered to all involved keyshare servers
	for id := range session.irmaSession.Identifiers().SchemeManagers {
190
191
192
193
194
195
		manager, ok := session.credManager.ConfigurationStore.SchemeManagers[id]
		if !ok {
			session.fail(&SessionError{ErrorType: ErrorUnknownSchemeManager, Info: id.String()})
			return
		}
		distributed := manager.Distributed()
Sietse Ringers's avatar
Sietse Ringers committed
196
197
198
199
200
201
202
203
		_, registered := session.credManager.keyshareServers[id]
		if distributed && !registered {
			session.transport.Delete()
			session.Handler.MissingKeyshareServer(id)
			return
		}
	}

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

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

225
	candidates, missing := session.credManager.CheckSatisfiability(session.irmaSession.ToDisclose())
226
227
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
228
		// TODO: session.transport.Delete() on dialog cancel
229
230
		return
	}
231
	session.irmaSession.SetCandidates(candidates)
232

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

Sietse Ringers's avatar
Sietse Ringers committed
255
func (session *session) do(proceed bool) {
256
	if !proceed {
257
		session.cancel()
258
259
260
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
261

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

293
294
295
296
297
298
299
300
		startKeyshareSession(
			session,
			session.Handler,
			builders,
			session.irmaSession,
			session.credManager.ConfigurationStore,
			session.credManager.keyshareServers,
		)
301
	}
Sietse Ringers's avatar
Sietse Ringers committed
302
}
303

Sietse Ringers's avatar
Sietse Ringers committed
304
305
306
307
func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

Sietse Ringers's avatar
Sietse Ringers committed
308
func (session *session) KeyshareCancelled() {
309
	session.cancel()
Sietse Ringers's avatar
Sietse Ringers committed
310
311
}

Sietse Ringers's avatar
Sietse Ringers committed
312
func (session *session) KeyshareBlocked(duration int) {
Tomas's avatar
Tomas committed
313
	session.fail(&SessionError{ErrorType: ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
314
315
316
}

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

Sietse Ringers's avatar
Sietse Ringers committed
320
321
type disclosureResponse string

Sietse Ringers's avatar
Sietse Ringers committed
322
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
323
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
324
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
325

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

353
	_ = session.credManager.addLogEntry(log) // TODO err
354
355
356
357
358
359
	if !session.downloaded.Empty() {
		session.credManager.handler.UpdateConfigurationStore(session.downloaded)
	}
	if session.Action == ActionIssuing {
		session.credManager.handler.UpdateAttributes()
	}
360
	session.Handler.Success(session.Action)
361
}
362
363
364
365
366
367
368

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