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
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)
Sietse Ringers's avatar
Sietse Ringers committed
27
	MissingKeyshareServer(manager SchemeManagerIdentifier)
28

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
44
	info        *SessionInfo
45
	credManager *CredentialManager
Sietse Ringers's avatar
Sietse Ringers committed
46
	jwt         RequestorJwt
47
	irmaSession IrmaSession
Sietse Ringers's avatar
Sietse Ringers committed
48
49
	transport   *HTTPTransport
	choice      *DisclosureChoice
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
	var err error
170
	session.jwt, err = parseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
171
	if err != nil {
Tomas's avatar
Tomas committed
172
		session.fail(&SessionError{ErrorType: ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
173
174
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
175
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
176
177
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
178
	if session.Action == ActionIssuing {
179
		ir := session.irmaSession.(*IssuanceRequest)
180
		// Store which public keys the server will use
181
		for _, credreq := range ir.Credentials {
182
			credreq.KeyCounter = session.info.Keys[credreq.CredentialTypeID.IssuerIdentifier()]
183
184
185
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
186
187
188
189
190
191
192
193
194
195
196
	// 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
197
	// Download missing credential types/issuers/public keys from the scheme manager
198
	if session.downloaded, err = session.credManager.ConfigurationStore.Download(session.irmaSession.Identifiers()); err != nil {
199
200
201
202
203
204
205
		session.Handler.Failure(
			session.Action,
			&SessionError{ErrorType: ErrorConfigurationStoreDownload, Err: err},
		)
		return
	}

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

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

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

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

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

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
313
314
type disclosureResponse string

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

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

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

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