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

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

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

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

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

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

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
311
312
type disclosureResponse string

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

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

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

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