session.go 8.53 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
// 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
15
type PermissionHandler func(proceed bool, choice *DisclosureChoice)
16
17
18
19
20
21

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

Sietse Ringers's avatar
Sietse Ringers committed
25
26
27
	AskIssuancePermission(request IssuanceRequest, ServerName string, callback PermissionHandler)
	AskVerificationPermission(request DisclosureRequest, ServerName string, callback PermissionHandler)
	AskSignaturePermission(request SignatureRequest, ServerName string, callback PermissionHandler)
Sietse Ringers's avatar
Sietse Ringers committed
28

Sietse Ringers's avatar
Sietse Ringers committed
29
	AskPin(remainingAttempts int, callback func(proceed bool, pin string))
30
31
}

Sietse Ringers's avatar
Sietse Ringers committed
32
33
// A session is an IRMA session.
type session struct {
34
35
36
37
38
	Action    Action
	Version   Version
	ServerURL string
	Handler   Handler

Sietse Ringers's avatar
Sietse Ringers committed
39
	info        *SessionInfo
40
	credManager *CredentialManager
Sietse Ringers's avatar
Sietse Ringers committed
41
	jwt         RequestorJwt
42
	irmaSession IrmaSession
Sietse Ringers's avatar
Sietse Ringers committed
43
44
	transport   *HTTPTransport
	choice      *DisclosureChoice
45
46
}

47
48
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
49
	2: {2, 1},
50
51
52
53
}

func calcVersion(qr *Qr) (string, error) {
	// Parse range supported by server
54
55
56
57
58
59
60
61
62
63
64
65
	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 {
66
67
68
69
70
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
71
	for k := range supportedVersions {
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
		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)
}

87
// NewSession creates and starts a new IRMA session.
88
func NewSession(credManager *CredentialManager, qr *Qr, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
89
	session := &session{
90
91
92
93
94
		Action:      Action(qr.Type),
		ServerURL:   qr.URL,
		Handler:     handler,
		transport:   NewHTTPTransport(qr.URL),
		credManager: credManager,
95
	}
96
97
98
99
100
101
	version, err := calcVersion(qr)
	if err != nil {
		session.fail(&Error{ErrorCode: ErrorProtocolVersionNotSupported, Err: err})
		return
	}
	session.Version = Version(version)
102
103
104
105
106
107
108
109
110

	// Check if the action is one of the supported types
	switch session.Action {
	case ActionDisclosing: // nop
	case ActionSigning: // nop
	case ActionIssuing: // nop
	case ActionUnknown:
		fallthrough
	default:
111
		session.fail(&Error{ErrorCode: ErrorUnknownAction, Err: nil, Info: string(session.Action)})
Sietse Ringers's avatar
Sietse Ringers committed
112
		return
113
114
115
116
117
118
119
120
	}

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

	go session.start()

Sietse Ringers's avatar
Sietse Ringers committed
121
	return
122
123
}

124
125
126
127
128
129
func (session *session) fail(err *Error) {
	session.transport.Delete()
	err.Err = errors.Wrap(err.Err, 0)
	session.Handler.Failure(session.Action, err)
}

130
131
// 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
132
func (session *session) start() {
133
134
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

Sietse Ringers's avatar
Sietse Ringers committed
135
	// Get the first IRMA protocol message and parse it
Sietse Ringers's avatar
Sietse Ringers committed
136
137
	session.info = &SessionInfo{}
	Err := session.transport.Get("jwt", session.info)
Sietse Ringers's avatar
Sietse Ringers committed
138
	if Err != nil {
139
		session.fail(Err.(*Error))
140
141
142
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
143
144
145
	var server string
	var err error
	session.jwt, server, err = parseRequestorJwt(session.Action, session.info.Jwt)
Sietse Ringers's avatar
Sietse Ringers committed
146
	if err != nil {
147
		session.fail(&Error{ErrorCode: ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
148
149
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
150
	session.irmaSession = session.jwt.IrmaSession()
Sietse Ringers's avatar
Sietse Ringers committed
151
152
	session.irmaSession.SetContext(session.info.Context)
	session.irmaSession.SetNonce(session.info.Nonce)
153
154
	if session.Action == ActionIssuing {
		// Store which public keys the server will use
Sietse Ringers's avatar
Sietse Ringers committed
155
		for _, credreq := range session.irmaSession.(*IssuanceRequest).Credentials {
Sietse Ringers's avatar
Sietse Ringers committed
156
			credreq.KeyCounter = session.info.Keys[credreq.Credential.IssuerIdentifier()]
157
158
159
		}
	}

160
	missing := session.credManager.CheckSatisfiability(session.irmaSession.DisjunctionList())
161
162
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
163
		// TODO: session.transport.Delete() on dialog cancel
164
165
166
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
167
	// Ask for permission to execute the session
Sietse Ringers's avatar
Sietse Ringers committed
168
	callback := PermissionHandler(func(proceed bool, choice *DisclosureChoice) {
Sietse Ringers's avatar
Sietse Ringers committed
169
170
171
		session.choice = choice
		session.irmaSession.SetDisclosureChoice(choice)
		go session.do(proceed)
172
173
174
175
	})
	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
176
		session.Handler.AskVerificationPermission(*session.irmaSession.(*DisclosureRequest), server, callback)
177
	case ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
178
		session.Handler.AskSignaturePermission(*session.irmaSession.(*SignatureRequest), server, callback)
179
	case ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
180
		session.Handler.AskIssuancePermission(*session.irmaSession.(*IssuanceRequest), server, callback)
181
182
183
184
185
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

Sietse Ringers's avatar
Sietse Ringers committed
186
func (session *session) do(proceed bool) {
187
	if !proceed {
188
		session.transport.Delete()
189
190
191
192
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
193

194
	if !session.irmaSession.Distributed(session.credManager.ConfigurationStore) {
Sietse Ringers's avatar
Sietse Ringers committed
195
196
197
198
		var message interface{}
		var err error
		switch session.Action {
		case ActionSigning:
199
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, true)
Sietse Ringers's avatar
Sietse Ringers committed
200
		case ActionDisclosing:
201
			message, err = session.credManager.Proofs(session.choice, session.irmaSession, false)
Sietse Ringers's avatar
Sietse Ringers committed
202
		case ActionIssuing:
203
			message, err = session.credManager.IssueCommitments(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
204
205
		}
		if err != nil {
206
			session.fail(&Error{ErrorCode: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
207
208
209
210
			return
		}
		session.sendResponse(message)
	} else {
211
		var builders gabi.ProofBuilderList
Sietse Ringers's avatar
Sietse Ringers committed
212
213
214
215
216
		var err error
		switch session.Action {
		case ActionSigning:
			fallthrough
		case ActionDisclosing:
217
			builders, err = session.credManager.ProofBuilders(session.choice)
Sietse Ringers's avatar
Sietse Ringers committed
218
		case ActionIssuing:
219
			builders, err = session.credManager.IssuanceProofBuilders(session.irmaSession.(*IssuanceRequest))
Sietse Ringers's avatar
Sietse Ringers committed
220
221
		}
		if err != nil {
222
			session.fail(&Error{ErrorCode: ErrorCrypto, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
223
224
		}

225
		startKeyshareSession(session.credManager, session.irmaSession, builders, session, session.Handler)
226
	}
Sietse Ringers's avatar
Sietse Ringers committed
227
}
228

Sietse Ringers's avatar
Sietse Ringers committed
229
230
231
232
func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

Sietse Ringers's avatar
Sietse Ringers committed
233
func (session *session) KeyshareCancelled() {
234
	session.transport.Delete()
Sietse Ringers's avatar
Sietse Ringers committed
235
236
237
	session.Handler.Cancelled(session.Action)
}

Sietse Ringers's avatar
Sietse Ringers committed
238
func (session *session) KeyshareBlocked(duration int) {
239
	session.fail(&Error{ErrorCode: ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
240
241
242
}

func (session *session) KeyshareError(err error) {
243
	session.fail(&Error{ErrorCode: ErrorKeyshare, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
244
245
}

Sietse Ringers's avatar
Sietse Ringers committed
246
247
type disclosureResponse string

Sietse Ringers's avatar
Sietse Ringers committed
248
func (session *session) sendResponse(message interface{}) {
Sietse Ringers's avatar
Sietse Ringers committed
249
	var log *LogEntry
Sietse Ringers's avatar
Sietse Ringers committed
250
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
251

Sietse Ringers's avatar
Sietse Ringers committed
252
253
254
255
	switch session.Action {
	case ActionSigning:
		fallthrough
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
256
		var response disclosureResponse
Sietse Ringers's avatar
Sietse Ringers committed
257
		if err = session.transport.Post("proofs", &response, message); err != nil {
258
			session.fail(err.(*Error))
Sietse Ringers's avatar
Sietse Ringers committed
259
260
261
			return
		}
		if response != "VALID" {
262
			session.fail(&Error{ErrorCode: ErrorRejected, Info: string(response)})
Sietse Ringers's avatar
Sietse Ringers committed
263
264
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
265
		log, err = session.createLogEntry(message.(gabi.ProofList)) // TODO err
Sietse Ringers's avatar
Sietse Ringers committed
266
267
	case ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
Sietse Ringers's avatar
Sietse Ringers committed
268
		if err = session.transport.Post("commitments", &response, message); err != nil {
269
			session.fail(err.(*Error))
Sietse Ringers's avatar
Sietse Ringers committed
270
271
			return
		}
272
		if err = session.credManager.ConstructCredentials(response, session.irmaSession.(*IssuanceRequest)); err != nil {
273
			session.fail(&Error{Err: err, ErrorCode: ErrorCrypto})
Sietse Ringers's avatar
Sietse Ringers committed
274
275
			return
		}
Sietse Ringers's avatar
Sietse Ringers committed
276
		log, err = session.createLogEntry(message) // TODO err
277
278
	}

279
	session.credManager.addLogEntry(log, true) // TODO err
280
	session.Handler.Success(session.Action)
281
}