session.go 8.5 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

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

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

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

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

86
// NewSession creates and starts a new IRMA session.
87
func NewSession(credManager *CredentialManager, qr *Qr, handler Handler) {
Sietse Ringers's avatar
Sietse Ringers committed
88
	session := &session{
89
90
91
92
93
		Action:      Action(qr.Type),
		ServerURL:   qr.URL,
		Handler:     handler,
		transport:   NewHTTPTransport(qr.URL),
		credManager: credManager,
94
	}
95
96
97
98
99
100
	version, err := calcVersion(qr)
	if err != nil {
		session.fail(&Error{ErrorCode: ErrorProtocolVersionNotSupported, Err: err})
		return
	}
	session.Version = Version(version)
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:
110
		session.fail(&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
125
126
127
128
func (session *session) fail(err *Error) {
	session.transport.Delete()
	err.Err = errors.Wrap(err.Err, 0)
	session.Handler.Failure(session.Action, err)
}

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

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

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

167
	missing := session.credManager.CheckSatisfiability(session.irmaSession.DisjunctionList())
168
169
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
170
		// TODO: session.transport.Delete() on dialog cancel
171
172
173
		return
	}

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

Sietse Ringers's avatar
Sietse Ringers committed
193
func (session *session) do(proceed bool) {
194
	if !proceed {
195
		session.transport.Delete()
196
197
198
199
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
200

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

232
		startKeyshareSession(session.credManager, session.irmaSession, builders, session, session.Handler)
233
	}
Sietse Ringers's avatar
Sietse Ringers committed
234
}
235

Sietse Ringers's avatar
Sietse Ringers committed
236
237
238
239
func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

Sietse Ringers's avatar
Sietse Ringers committed
240
func (session *session) KeyshareCancelled() {
241
	session.transport.Delete()
Sietse Ringers's avatar
Sietse Ringers committed
242
243
244
	session.Handler.Cancelled(session.Action)
}

Sietse Ringers's avatar
Sietse Ringers committed
245
func (session *session) KeyshareBlocked(duration int) {
246
	session.fail(&Error{ErrorCode: ErrorKeyshareBlocked, Info: strconv.Itoa(duration)})
Sietse Ringers's avatar
Sietse Ringers committed
247
248
249
}

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

Sietse Ringers's avatar
Sietse Ringers committed
253
254
type disclosureResponse string

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

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