session.go 8.6 KB
Newer Older
1
2
3
4
package protocol

import (
	"math/big"
5
	"strconv"
6
7
	"strings"

8
9
10
11
	"sort"

	"fmt"

12
13
14
15
	"encoding/json"

	"encoding/base64"

16
	"github.com/credentials/irmago"
17
	"github.com/mhe/gabi"
18
19
)

20
type PermissionHandler func(proceed bool, choice *irmago.DisclosureChoice)
21
22
23
24
25
26

// A Handler contains callbacks for communication to the user.
type Handler interface {
	StatusUpdate(action Action, status Status)
	Success(action Action)
	Cancelled(action Action)
27
	Failure(action Action, err *Error)
28
29
	UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList)

30
31
32
	AskIssuancePermission(request irmago.IssuanceRequest, ServerName string, choice PermissionHandler)
	AskVerificationPermission(request irmago.DisclosureRequest, ServerName string, choice PermissionHandler)
	AskSignaturePermission(request irmago.SignatureRequest, ServerName string, choice PermissionHandler)
33
34
35
36
37
38
39
40
41
42
}

// A Session is an IRMA session.
type Session struct {
	Action    Action
	Version   Version
	ServerURL string
	Handler   Handler

	request   irmago.DisjunctionListContainer
43
44
45
	spRequest *ServiceProviderJwt
	ipRequest *IdentityProviderJwt
	ssRequest *SignatureServerJwt
46

47
48
49
50
51
	transport *HTTPTransport
	nonce     *big.Int
	context   *big.Int
}

52
53
54
55
56
57
58
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
	2: []int{2, 1},
}

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

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

92
// NewSession creates and starts a new IRMA session.
93
func NewSession(qr *Qr, handler Handler) *Session {
94
95
	version, err := calcVersion(qr)
	if err != nil {
96
		handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorProtocolVersionNotSupported, error: err})
97
98
99
100
		return nil
	}

	session := &Session{
101
		Version:   Version(version),
102
103
104
105
106
107
108
109
110
111
112
113
114
115
		Action:    Action(qr.Type),
		ServerURL: qr.URL,
		Handler:   handler,
		transport: NewHTTPTransport(qr.URL),
	}

	// 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:
116
		handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorUnknownAction, error: nil, info: string(session.Action)})
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
		return nil
	}

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

	go session.start()

	return session
}

// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
func (session *Session) start() {
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

	// Get the first IRMA protocol message
	info := &SessionInfo{}
	err := session.transport.Get("jwt", info)
	if err != nil {
138
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr})
139
140
141
142
143
144
145
		return
	}

	session.nonce = info.Nonce
	session.context = info.Context
	jwtparts := strings.Split(info.Jwt, ".")
	if jwtparts == nil || len(jwtparts) < 2 {
146
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
147
148
		return
	}
149
150

	headerbytes, err := base64.RawStdEncoding.DecodeString(jwtparts[0])
151
152
153
154
	if err != nil {
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
		return
	}
155
156
	bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
	if err != nil {
157
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
158
159
160
		return
	}

161
162
163
	var header struct {
		Server string `json:"iss"`
	}
Sietse Ringers's avatar
Sietse Ringers committed
164
165
	err = json.Unmarshal([]byte(headerbytes), &header)
	if err != nil {
166
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
Sietse Ringers's avatar
Sietse Ringers committed
167
168
		return
	}
169
170
171

	switch session.Action {
	case ActionDisclosing:
172
		session.spRequest = &ServiceProviderJwt{}
Sietse Ringers's avatar
Sietse Ringers committed
173
		err = json.Unmarshal([]byte(bodybytes), session.spRequest)
174
175
		session.spRequest.Request.Request.Context = session.context
		session.spRequest.Request.Request.Nonce = session.nonce
176
		session.request = session.spRequest
177
	case ActionSigning:
178
		session.ssRequest = &SignatureServerJwt{}
Sietse Ringers's avatar
Sietse Ringers committed
179
		err = json.Unmarshal([]byte(bodybytes), session.ssRequest)
180
181
		session.ssRequest.Request.Request.Context = session.context
		session.ssRequest.Request.Request.Nonce = session.nonce
182
		session.request = session.ssRequest
183
	case ActionIssuing:
184
		session.ipRequest = &IdentityProviderJwt{}
Sietse Ringers's avatar
Sietse Ringers committed
185
		err = json.Unmarshal([]byte(bodybytes), session.ipRequest)
186
187
		session.ipRequest.Request.Request.Context = session.context
		session.ipRequest.Request.Request.Nonce = session.nonce
188
		session.request = session.ipRequest
189
190
191
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
Sietse Ringers's avatar
Sietse Ringers committed
192
	if err != nil {
193
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
Sietse Ringers's avatar
Sietse Ringers committed
194
195
		return
	}
196

197
198
	if session.Action == ActionIssuing {
		// Store which public keys the server will use
199
		for _, credreq := range session.ipRequest.Request.Request.Credentials {
200
201
202
203
204
205
206
207
208
209
			credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
		}
	}

	missing := irmago.Manager.CheckSatisfiability(session.request)
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		return
	}

210
	callback := PermissionHandler(func(proceed bool, choice *irmago.DisclosureChoice) {
211
212
213
214
215
216
		go session.do(proceed, choice)
	})

	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
217
		session.Handler.AskVerificationPermission(*session.spRequest.Request.Request, header.Server, callback)
218
	case ActionSigning:
219
		session.Handler.AskSignaturePermission(*session.ssRequest.Request.Request, header.Server, callback)
220
	case ActionIssuing:
221
		session.Handler.AskIssuancePermission(*session.ipRequest.Request.Request, header.Server, callback)
222
223
224
225
226
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

227
228
229
230
231
232
func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
	if !proceed {
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
233

Sietse Ringers's avatar
Sietse Ringers committed
234
	var message interface{}
235
236
237
	var err error
	switch session.Action {
	case ActionSigning:
238
		message, err = irmago.Manager.Proofs(choice, session.ssRequest.Request.Request, true)
239
	case ActionDisclosing:
240
		message, err = irmago.Manager.Proofs(choice, session.spRequest.Request.Request, false)
241
	case ActionIssuing:
242
		message, err = irmago.Manager.IssueCommitments(choice, session.ipRequest.Request.Request)
243
244
	}
	if err != nil {
245
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
246
247
248
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
249
250
251
252
253
254
255
256
	switch session.Action {
	case ActionSigning:
		fallthrough
	case ActionDisclosing:
		response := ""
		err = session.transport.Post("proofs", &response, message)
		if err != nil {
			session.Handler.Failure(session.Action,
257
				&Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr, info: err.Error(), error: err})
Sietse Ringers's avatar
Sietse Ringers committed
258
259
260
261
262
263
264
265
266
267
268
			return
		}
		if response != "VALID" {
			session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorRejected, info: response})
			return
		}
	case ActionIssuing:
		response := []*gabi.IssueSignatureMessage{}
		err = session.transport.Post("commitments", &response, message)
		if err != nil {
			session.Handler.Failure(session.Action,
269
				&Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr, info: err.Error(), error: err})
Sietse Ringers's avatar
Sietse Ringers committed
270
271
272
			return
		}

273
		err = irmago.Manager.ConstructCredentials(response, session.ipRequest.Request.Request)
Sietse Ringers's avatar
Sietse Ringers committed
274
275
276
277
		if err != nil {
			session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
			return
		}
278
279
280
	}

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