server.go 8.8 KB
Newer Older
1
// Package irmaserver is a server allowing IRMA verifiers, issuers or attribute-based signature applications (the requestor) to perform IRMA sessions with irmaclient instances (i.e. the IRMA app). It exposes a RESTful protocol with which the requestor can start and manage the session as well as HTTP endpoints for the irmaclient.
Sietse Ringers's avatar
Sietse Ringers committed
2
package irmaserver
3
4

import (
5
	"fmt"
6
7
	"io/ioutil"
	"net/http"
8
	"time"
9

10
	"github.com/dgrijalva/jwt-go"
11
	"github.com/go-chi/chi"
Sietse Ringers's avatar
Sietse Ringers committed
12
	"github.com/go-chi/cors"
13
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
14
15
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmarequestor"
16
17
)

18
var (
19
20
	serv, clientserv *http.Server
	conf             *Configuration
21
)
22

23
// Start the server. If successful then it will not return until Stop() is called.
24
func Start(config *Configuration) error {
25
	if err := Initialize(config); err != nil {
26
27
28
		return err
	}

29
30
31
	// Start server(s)
	if conf.separateClientServer() {
		go startClientServer()
32
	}
33
	startRequestorServer()
34

35
	return nil
36
37
}

38
39
40
func startRequestorServer() {
	serv = &http.Server{}
	startServer(serv, Handler(), "Server", conf.ListenAddress, conf.Port)
41
42
}

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
func startClientServer() {
	clientserv = &http.Server{}
	startServer(clientserv, ClientHandler(), "Client server", conf.ClientListenAddress, conf.ClientPort)
}

func startServer(s *http.Server, handler http.Handler, name, addr string, port int) {
	fulladdr := fmt.Sprintf("%s:%d", addr, port)
	conf.Logger.Info(name, " listening at ", fulladdr)
	s.Addr = fulladdr
	s.Handler = handler
	if err := s.ListenAndServe(); err != http.ErrServerClosed {
		_ = server.LogFatal(err)
	}
}

func Stop() error {
	var err1, err2 error

	// Even if closing serv fails, we want to try closing clientserv
	err1 = serv.Close()
	if clientserv != nil {
		err2 = clientserv.Close()
	}

	// Now check errors
	if err1 != nil {
		return err1
	}
	if err2 != nil {
		return err2
	}
	return nil
}

func Initialize(config *Configuration) error {
78
	conf = config
79
	if err := irmarequestor.Initialize(conf.Configuration); err != nil {
80
		return err
81
	}
82
	if err := conf.initialize(); err != nil {
83
		return err
84
	}
85
86
	return nil
}
87

88
func ClientHandler() http.Handler {
89
	router := chi.NewRouter()
Sietse Ringers's avatar
Sietse Ringers committed
90
91
92
	router.Use(cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
Sietse Ringers's avatar
Sietse Ringers committed
93
		AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete},
Sietse Ringers's avatar
Sietse Ringers committed
94
95
	}).Handler)

96
	router.Mount("/irma/", irmarequestor.HttpHandlerFunc())
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
	return router
}

// Handler returns a http.Handler that handles all IRMA requestor messages
// and IRMA client messages.
func Handler() http.Handler {
	router := chi.NewRouter()
	router.Use(cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
		AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete},
	}).Handler)

	if !conf.separateClientServer() {
		// Mount server for irmaclient
		router.Mount("/irma/", irmarequestor.HttpHandlerFunc())
	}
114

115
	// Server routes
116
	router.Post("/session", handleCreate)
117
	router.Delete("/session/{token}", handleDelete)
118
119
	router.Get("/session/{token}/status", handleStatus)
	router.Get("/session/{token}/result", handleResult)
Sietse Ringers's avatar
Sietse Ringers committed
120
121

	// Routes for getting signed JWTs containing the session result. Only work if configuration has a private key
122
123
	router.Get("/session/{token}/result-jwt", handleJwtResult)
	router.Get("/session/{token}/getproof", handleJwtProofs) // irma_api_server-compatible JWT
124

125
	return router
126
127
}

128
129
130
func handleCreate(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
131
132
		conf.Logger.Error("Could not read session request HTTP POST body")
		_ = server.LogError(err)
Sietse Ringers's avatar
Sietse Ringers committed
133
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
134
135
		return
	}
136

Sietse Ringers's avatar
Sietse Ringers committed
137
138
139
	// Authenticate request: check if the requestor is known and allowed to submit requests.
	// We do this by feeding the HTTP POST details to all known authenticators, and see if
	// one of them is applicable and able to authenticate the request.
140
141
142
143
144
145
146
147
148
149
150
151
152
	var (
		request   irma.SessionRequest
		requestor string
		rerr      *irma.RemoteError
		applies   bool
	)
	for _, authenticator := range authenticators {
		applies, request, requestor, rerr = authenticator.Authenticate(r.Header, body)
		if applies || rerr != nil {
			break
		}
	}
	if rerr != nil {
153
		_ = server.LogError(rerr)
154
155
156
157
		server.WriteResponse(w, nil, rerr)
		return
	}
	if !applies {
158
159
		conf.Logger.Warnf("Session request uses unknown authentication method, HTTP headers: %s, HTTP POST body: %s",
			server.ToJson(r.Header), string(body))
Sietse Ringers's avatar
Sietse Ringers committed
160
		server.WriteError(w, server.ErrorInvalidRequest, "Request could not be authorized")
161
162
163
		return
	}

164
165
166
167
168
	// Authorize request: check if the requestor is allowed to verify or issue
	// the requested attributes or credentials
	if request.Action() == irma.ActionIssuing {
		allowed, reason := conf.CanIssue(requestor, request.(*irma.IssuanceRequest).Credentials)
		if !allowed {
169
170
			conf.Logger.Warn("Requestor %s tried to issue credential %s but it is not authorized to; full request: %s",
				requestor, reason, server.ToJson(request))
171
172
173
174
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}
Sietse Ringers's avatar
Sietse Ringers committed
175
	disjunctions := request.ToDisclose()
176
177
178
	if len(disjunctions) > 0 {
		allowed, reason := conf.CanVerifyOrSign(requestor, request.Action(), disjunctions)
		if !allowed {
179
180
			conf.Logger.Warn("Requestor %s tried to verify attribute %s but it is not authorized to; full request: %s",
				requestor, reason, server.ToJson(request))
181
182
183
184
185
186
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}

	// Everything is authenticated and parsed, we're good to go!
187
188
	qr, _, err := irmarequestor.StartSession(request, nil)
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
189
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
190
191
192
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
193
	server.WriteJson(w, qr)
194
195
196
197
198
}

func handleStatus(w http.ResponseWriter, r *http.Request) {
	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
Sietse Ringers's avatar
Sietse Ringers committed
199
		server.WriteError(w, server.ErrorSessionUnknown, "")
200
201
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
202
	server.WriteJson(w, res.Status)
203
204
}

205
206
207
208
209
210
211
func handleDelete(w http.ResponseWriter, r *http.Request) {
	err := irmarequestor.CancelSession(chi.URLParam(r, "token"))
	if err != nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
	}
}

212
213
214
func handleResult(w http.ResponseWriter, r *http.Request) {
	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
Sietse Ringers's avatar
Sietse Ringers committed
215
		server.WriteError(w, server.ErrorSessionUnknown, "")
216
217
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
218
	server.WriteJson(w, res)
219
220
}

221
func handleJwtResult(w http.ResponseWriter, r *http.Request) {
222
	if conf.jwtPrivateKey == nil {
223
		conf.Logger.Warn("Session result JWT requested but no JWT private key is configured")
224
225
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
226
	}
227
228
229
230
231

	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
232
	}
233
234
235
236
237
238
239
240
241
242
243
244
245

	claims := struct {
		jwt.StandardClaims
		*server.SessionResult
	}{
		SessionResult: res,
	}
	claims.Issuer = conf.JwtIssuer
	claims.IssuedAt = time.Now().Unix()
	claims.Subject = string(res.Type) + "_result"

	// Sign the jwt and return it
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
246
	resultJwt, err := token.SignedString(conf.jwtPrivateKey)
247
	if err != nil {
248
249
		conf.Logger.Error("Failed to sign session result JWT")
		_ = server.LogError(err)
250
251
252
253
254
255
256
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
	server.WriteString(w, resultJwt)
}

func handleJwtProofs(w http.ResponseWriter, r *http.Request) {
257
	if conf.jwtPrivateKey == nil {
258
		conf.Logger.Warn("Session result JWT requested but no JWT private key is configured")
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
	}

	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
	}

	claims := jwt.MapClaims{}

	// Fill standard claims
	switch res.Type {
	case irma.ActionDisclosing:
		claims["subject"] = "verification_result"
	case irma.ActionSigning:
		claims["subject"] = "abs_result"
	default:
		if res == nil {
			server.WriteError(w, server.ErrorInvalidRequest, "")
			return
		}
	}
	claims["iat"] = time.Now().Unix()
	if conf.JwtIssuer != "" {
		claims["iss"] = conf.JwtIssuer
	}
	claims["status"] = res.Status

	// Disclosed credentials and possibly signature
	m := make(map[irma.AttributeTypeIdentifier]string, len(res.Disclosed))
	for _, attr := range res.Disclosed {
		m[attr.Identifier] = attr.Value[""]
	}
	claims["attributes"] = m
	if res.Signature != nil {
		claims["signature"] = res.Signature
	}

	// Sign the jwt and return it
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
301
	resultJwt, err := token.SignedString(conf.jwtPrivateKey)
302
	if err != nil {
303
304
		conf.Logger.Error("Failed to sign session result JWT")
		_ = server.LogError(err)
305
306
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
307
	}
308
	server.WriteString(w, resultJwt)
309
}