session.go 8.61 KB
Newer Older
1
2
3
package protocol

import (
Sietse Ringers's avatar
Sietse Ringers committed
4
5
	"fmt"
	"sort"
6
	"strconv"
7
8
9
	"strings"

	"github.com/credentials/irmago"
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.
15
type PermissionHandler func(proceed bool, choice *irmago.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)
22
	Failure(action Action, err *irmago.Error)
23
24
	UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList)

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

	AskPin(remainingAttempts int, callback func(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
40
	jwt         RequestorJwt
	irmaSession irmago.Session
41
	transport   *irmago.HTTPTransport
Sietse Ringers's avatar
Sietse Ringers committed
42
	choice      *irmago.DisclosureChoice
43
44
}

45
46
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
Sietse Ringers's avatar
Sietse Ringers committed
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.
Sietse Ringers's avatar
Sietse Ringers committed
86
func NewSession(qr *Qr, handler Handler) {
87
88
	version, err := calcVersion(qr)
	if err != nil {
89
		handler.Failure(ActionUnknown, &irmago.Error{ErrorCode: irmago.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
		Version:   Version(version),
95
96
97
		Action:    Action(qr.Type),
		ServerURL: qr.URL,
		Handler:   handler,
98
		transport: irmago.NewHTTPTransport(qr.URL),
99
100
101
102
103
104
105
106
107
108
	}

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

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

	go session.start()

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

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

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

	switch session.Action {
	case ActionDisclosing:
Sietse Ringers's avatar
Sietse Ringers committed
137
		session.jwt = &ServiceProviderJwt{}
138
	case ActionSigning:
Sietse Ringers's avatar
Sietse Ringers committed
139
		session.jwt = &SignatureRequestorJwt{}
140
	case ActionIssuing:
Sietse Ringers's avatar
Sietse Ringers committed
141
		session.jwt = &IdentityProviderJwt{}
142
143
144
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
Sietse Ringers's avatar
Sietse Ringers committed
145
	server, err := irmago.JwtDecode(info.Jwt, session.jwt)
Sietse Ringers's avatar
Sietse Ringers committed
146
	if err != nil {
147
		session.Handler.Failure(session.Action, &irmago.Error{ErrorCode: irmago.ErrorInvalidJWT, Err: err})
Sietse Ringers's avatar
Sietse Ringers committed
148
149
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
150
151
152
	session.irmaSession = session.jwt.IrmaSession()
	session.irmaSession.SetContext(info.Context)
	session.irmaSession.SetNonce(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.(*irmago.IssuanceRequest).Credentials {
156
157
158
159
			credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
		}
	}

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
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,
		&irmago.Error{ErrorCode: irmago.ErrorKeyshareBlocked, Info: strconv.Itoa(duration)},
	)
}

func (session *session) KeyshareError(err error) {
	session.Handler.Failure(session.Action, &irmago.Error{ErrorCode: irmago.ErrorKeyshare, Err: err})
}

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

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