session.go 8.25 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
28

	AskPin(remainingAttempts int, callback func(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

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

44
45
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
Sietse Ringers's avatar
Sietse Ringers committed
46
	2: {2, 1},
47
48
49
50
}

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

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

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

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

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

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

	go session.start()

Sietse Ringers's avatar
Sietse Ringers committed
118
	return
119
120
121
122
}

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
159
	missing := Manager.CheckSatisfiability(session.irmaSession.DisjunctionList())
160
161
162
163
164
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		return
	}

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
222
		StartKeyshareSession(session.irmaSession, builders, session)
223
	}
Sietse Ringers's avatar
Sietse Ringers committed
224
}
225

Sietse Ringers's avatar
Sietse Ringers committed
226
227
228
229
230
231
232
233
234
235
236
func (session *session) AskPin(remainingAttempts int, callback func(pin string)) {
	session.Handler.AskPin(remainingAttempts, callback)
}

func (session *session) KeyshareDone(message interface{}) {
	session.sendResponse(message)
}

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

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

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

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