server.go 10.6 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 (
Sietse Ringers's avatar
Sietse Ringers committed
5
	"crypto/tls"
6
	"crypto/x509"
Sietse Ringers's avatar
Sietse Ringers committed
7
	"encoding/json"
8
	"encoding/pem"
9
	"fmt"
10
11
	"io/ioutil"
	"net/http"
12
	"time"
13

14
	"github.com/dgrijalva/jwt-go"
15
	"github.com/go-chi/chi"
Sietse Ringers's avatar
Sietse Ringers committed
16
	"github.com/go-chi/cors"
17
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
18
19
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmarequestor"
20
21
)

22
var (
23
24
	serv, clientserv *http.Server
	conf             *Configuration
25
)
26

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

33
34
35
	// Start server(s)
	if conf.separateClientServer() {
		go startClientServer()
36
	}
37
	startRequestorServer()
38

39
	return nil
40
41
}

42
43
func startRequestorServer() {
	serv = &http.Server{}
Sietse Ringers's avatar
Sietse Ringers committed
44
45
	tlsConf, _ := conf.tlsConfig()
	startServer(serv, Handler(), "Server", conf.ListenAddress, conf.Port, tlsConf)
46
47
}

48
49
func startClientServer() {
	clientserv = &http.Server{}
Sietse Ringers's avatar
Sietse Ringers committed
50
51
	tlsConf, _ := conf.clientTlsConfig()
	startServer(clientserv, ClientHandler(), "Client server", conf.ClientListenAddress, conf.ClientPort, tlsConf)
52
53
}

Sietse Ringers's avatar
Sietse Ringers committed
54
func startServer(s *http.Server, handler http.Handler, name, addr string, port int, tlsConf *tls.Config) {
55
56
57
58
	fulladdr := fmt.Sprintf("%s:%d", addr, port)
	conf.Logger.Info(name, " listening at ", fulladdr)
	s.Addr = fulladdr
	s.Handler = handler
Sietse Ringers's avatar
Sietse Ringers committed
59
60
61
	var err error
	if tlsConf != nil {
		s.TLSConfig = tlsConf
62
63
64
		// Disable HTTP/2 (see package documentation of http): it breaks server side events :(
		s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
		conf.Logger.Info(name, " TLS enabled")
Sietse Ringers's avatar
Sietse Ringers committed
65
66
67
68
69
		err = s.ListenAndServeTLS("", "")
	} else {
		err = s.ListenAndServe()
	}
	if err != http.ErrServerClosed {
70
71
72
73
74
75
76
77
78
79
80
81
82
83
		_ = 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
84
	if err1 != nil && err1 != http.ErrServerClosed {
85
86
		return err1
	}
87
	if err2 != nil && err2 != http.ErrServerClosed {
88
89
90
91
92
93
		return err2
	}
	return nil
}

func Initialize(config *Configuration) error {
94
	conf = config
95
	if err := irmarequestor.Initialize(conf.Configuration); err != nil {
96
		return err
97
	}
98
	if err := conf.initialize(); err != nil {
99
		return err
100
	}
Sietse Ringers's avatar
Sietse Ringers committed
101
102
	bts, _ := json.MarshalIndent(config, "", "   ")
	config.Logger.Debug("Configuration: ", string(bts), "\n")
103
104
	return nil
}
105

106
107
108
109
110
111
var corsOptions = cors.Options{
	AllowedOrigins: []string{"*"},
	AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "Cache-Control"},
	AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete},
}

112
func ClientHandler() http.Handler {
113
	router := chi.NewRouter()
114
	router.Use(cors.New(corsOptions).Handler)
Sietse Ringers's avatar
Sietse Ringers committed
115

116
	router.Mount("/irma/", irmarequestor.HttpHandlerFunc())
117
118
119
120
121
122
123
	return router
}

// Handler returns a http.Handler that handles all IRMA requestor messages
// and IRMA client messages.
func Handler() http.Handler {
	router := chi.NewRouter()
124
	router.Use(cors.New(corsOptions).Handler)
125
126
127
128
129

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

131
	// Server routes
132
	router.Post("/session", handleCreate)
133
	router.Delete("/session/{token}", handleDelete)
134
	router.Get("/session/{token}/status", handleStatus)
135
	router.Get("/session/{token}/statusevents", handleStatusEvents)
136
	router.Get("/session/{token}/result", handleResult)
Sietse Ringers's avatar
Sietse Ringers committed
137
138

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

142
143
	router.Get("/publickey", handlePublicKey)

144
	return router
145
146
}

147
148
149
func handleCreate(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
150
151
		conf.Logger.Error("Could not read session request HTTP POST body")
		_ = server.LogError(err)
Sietse Ringers's avatar
Sietse Ringers committed
152
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
153
154
		return
	}
155

Sietse Ringers's avatar
Sietse Ringers committed
156
157
158
	// 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.
159
	var (
160
		rrequest  irma.RequestorRequest
161
162
163
164
165
166
		request   irma.SessionRequest
		requestor string
		rerr      *irma.RemoteError
		applies   bool
	)
	for _, authenticator := range authenticators {
167
		applies, rrequest, requestor, rerr = authenticator.Authenticate(r.Header, body)
168
169
170
171
172
		if applies || rerr != nil {
			break
		}
	}
	if rerr != nil {
173
		_ = server.LogError(rerr)
174
175
176
177
		server.WriteResponse(w, nil, rerr)
		return
	}
	if !applies {
178
179
		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
180
		server.WriteError(w, server.ErrorInvalidRequest, "Request could not be authorized")
181
182
183
		return
	}

184
185
	// Authorize request: check if the requestor is allowed to verify or issue
	// the requested attributes or credentials
186
	request = rrequest.SessionRequest()
187
188
189
	if request.Action() == irma.ActionIssuing {
		allowed, reason := conf.CanIssue(requestor, request.(*irma.IssuanceRequest).Credentials)
		if !allowed {
190
191
			conf.Logger.Warn("Requestor %s tried to issue credential %s but it is not authorized to; full request: %s",
				requestor, reason, server.ToJson(request))
192
193
194
195
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}
Sietse Ringers's avatar
Sietse Ringers committed
196
	disjunctions := request.ToDisclose()
197
198
199
	if len(disjunctions) > 0 {
		allowed, reason := conf.CanVerifyOrSign(requestor, request.Action(), disjunctions)
		if !allowed {
200
201
			conf.Logger.Warn("Requestor %s tried to verify attribute %s but it is not authorized to; full request: %s",
				requestor, reason, server.ToJson(request))
202
203
204
205
206
207
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}

	// Everything is authenticated and parsed, we're good to go!
208
	qr, _, err := irmarequestor.StartSession(rrequest, nil)
209
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
210
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
211
212
213
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
214
	server.WriteJson(w, qr)
215
216
217
218
219
}

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
220
		server.WriteError(w, server.ErrorSessionUnknown, "")
221
222
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
223
	server.WriteJson(w, res.Status)
224
225
}

226
227
228
229
230
231
232
233
func handleStatusEvents(w http.ResponseWriter, r *http.Request) {
	token := chi.URLParam(r, "token")
	conf.Logger.Debug("new client subscribed to server sent events of session " + token)
	if err := irmarequestor.SubscribeServerSentEvents(w, r, token); err != nil {
		server.WriteError(w, server.ErrorUnexpectedRequest, err.Error())
	}
}

234
235
236
237
238
239
240
func handleDelete(w http.ResponseWriter, r *http.Request) {
	err := irmarequestor.CancelSession(chi.URLParam(r, "token"))
	if err != nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
	}
}

241
242
243
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
244
		server.WriteError(w, server.ErrorSessionUnknown, "")
245
246
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
247
	server.WriteJson(w, res)
248
249
}

250
func handleJwtResult(w http.ResponseWriter, r *http.Request) {
251
	if conf.jwtPrivateKey == nil {
252
		conf.Logger.Warn("Session result JWT requested but no JWT private key is configured")
253
254
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
255
	}
256

257
258
	sessiontoken := chi.URLParam(r, "token")
	res := irmarequestor.GetSessionResult(sessiontoken)
259
260
261
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
262
	}
263
264
265
266
267
268
269
270
271
272

	claims := struct {
		jwt.StandardClaims
		*server.SessionResult
	}{
		SessionResult: res,
	}
	claims.Issuer = conf.JwtIssuer
	claims.IssuedAt = time.Now().Unix()
	claims.Subject = string(res.Type) + "_result"
273
274
275
276
	validity := irmarequestor.GetRequest(sessiontoken).Base().ResultJwtValidity
	if validity != 0 {
		claims.ExpiresAt = time.Now().Unix() + int64(validity)
	}
277
278
279

	// Sign the jwt and return it
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
280
	resultJwt, err := token.SignedString(conf.jwtPrivateKey)
281
	if err != nil {
282
283
		conf.Logger.Error("Failed to sign session result JWT")
		_ = server.LogError(err)
284
285
286
287
288
289
290
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
	server.WriteString(w, resultJwt)
}

func handleJwtProofs(w http.ResponseWriter, r *http.Request) {
291
	if conf.jwtPrivateKey == nil {
292
		conf.Logger.Warn("Session result JWT requested but no JWT private key is configured")
293
294
295
296
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
	}

297
298
	sessiontoken := chi.URLParam(r, "token")
	res := irmarequestor.GetSessionResult(sessiontoken)
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
	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
323
324
325
326
	validity := irmarequestor.GetRequest(sessiontoken).Base().ResultJwtValidity
	if validity != 0 {
		claims["exp"] = time.Now().Unix() + int64(validity)
	}
327
328
329
330
331
332
333
334
335
336
337
338
339

	// 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)
340
	resultJwt, err := token.SignedString(conf.jwtPrivateKey)
341
	if err != nil {
342
343
		conf.Logger.Error("Failed to sign session result JWT")
		_ = server.LogError(err)
344
345
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
346
	}
347
	server.WriteString(w, resultJwt)
348
}
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366

func handlePublicKey(w http.ResponseWriter, r *http.Request) {
	if conf.jwtPrivateKey == nil {
		server.WriteError(w, server.ErrorUnsupported, "")
		return
	}

	bts, err := x509.MarshalPKIXPublicKey(&conf.jwtPrivateKey.PublicKey)
	if err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
	pubBytes := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: bts,
	})
	_, _ = w.Write(pubBytes)
}