session.go 8.51 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/mhe/gabi"
10
11
)

Sietse Ringers's avatar
Sietse Ringers committed
12
13
// 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
14
type PermissionHandler func(proceed bool, choice *DisclosureChoice)
15
16
17
18
19
20

// 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
21
22
	Failure(action Action, err *Error)
	UnsatisfiableRequest(action Action, missing AttributeDisjunctionList)
23

Sietse Ringers's avatar
Sietse Ringers committed
24
25
26
	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
27

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

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

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

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

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

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

85
// NewSession creates and starts a new IRMA session.
86
func NewSession(credManager *CredentialManager, qr *Qr, handler Handler) {
87
88
	version, err := calcVersion(qr)
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
89
		handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorProtocolVersionNotSupported, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
90
		return
91
92
	}

Sietse Ringers's avatar
Sietse Ringers committed
93
	session := &session{
94
95
96
97
98
99
		Version:     Version(version),
		Action:      Action(qr.Type),
		ServerURL:   qr.URL,
		Handler:     handler,
		transport:   NewHTTPTransport(qr.URL),
		credManager: credManager,
100
101
102
103
104
105
106
107
108
109
	}

	// 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:
Sietse Ringers's avatar
Sietse Ringers committed
110
		handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorUnknownAction, Err: nil, Info: string(session.Action)})
Sietse Ringers's avatar
Sietse Ringers committed
111
		return
112
113
114
115
116
117
118
119
	}

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

	go session.start()

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

// 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
125
func (session *session) start() {
126
127
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

Sietse Ringers's avatar
Sietse Ringers committed
128
	// Get the first IRMA protocol message and parse it
129
	info := &SessionInfo{}
Sietse Ringers's avatar
Sietse Ringers committed
130
131
	Err := session.transport.Get("jwt", info)
	if Err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
132
		session.Handler.Failure(session.Action, Err.(*Error))
133
134
135
136
137
		return
	}

	switch session.Action {
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
138
		session.jwt = &ServiceProviderJwt{}
139
	case ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
140
		session.jwt = &SignatureRequestorJwt{}
141
	case ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
142
		session.jwt = &IdentityProviderJwt{}
143
144
145
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
Sietse Ringers's avatar
Sietse Ringers committed
146
	server, err := jwtDecode(info.Jwt, session.jwt)
Sietse Ringers's avatar
Sietse Ringers committed
147
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
148
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
149
150
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
151
152
153
	session.irmaSession = session.jwt.IrmaSession()
	session.irmaSession.SetContext(info.Context)
	session.irmaSession.SetNonce(info.Nonce)
154
155
	if session.Action == ActionIssuing {
		// Store which public keys the server will use
Sietse Ringers's avatar
Sietse Ringers committed
156
		for _, credreq := range session.irmaSession.(*IssuanceRequest).Credentials {
157
158
159
160
			credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
		}
	}

161
	missing := session.credManager.CheckSatisfiability(session.irmaSession.DisjunctionList())
162
163
164
165
166
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		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
188
189
190
191
	if !proceed {
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
192

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

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

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

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

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

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

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

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

	session.Handler.Success(session.Action)
277
}