server.go 15.8 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
// Package requestorserver is a server allowing IRMA verifiers, issuers or attribute-based signature
Sietse Ringers's avatar
Sietse Ringers committed
2
3
4
// 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
5
package requestorserver
6
7

import (
8
	"bytes"
9
	"context"
Sietse Ringers's avatar
Sietse Ringers committed
10
	"crypto/tls"
11
	"crypto/x509"
Sietse Ringers's avatar
Sietse Ringers committed
12
	"encoding/json"
13
	"encoding/pem"
14
	"fmt"
15
	"io/ioutil"
16
	"log"
17
	"net/http"
18
	"strings"
19
	"time"
20

21
	"github.com/dgrijalva/jwt-go"
22
	"github.com/go-chi/chi"
23
	"github.com/go-chi/chi/middleware"
Sietse Ringers's avatar
Sietse Ringers committed
24
	"github.com/go-chi/cors"
25
	"github.com/go-errors/errors"
26
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
27
	"github.com/privacybydesign/irmago/server"
28
	"github.com/privacybydesign/irmago/server/irmaserver"
29
	"github.com/sirupsen/logrus"
30
31
)

Sietse Ringers's avatar
Sietse Ringers committed
32
// Server is a requestor server instance.
33
type Server struct {
34
35
36
	conf     *Configuration
	irmaserv *irmaserver.Server
	stop     chan struct{}
37
	stopped  chan struct{}
38
}
39

40
// Start the server. If successful then it will not return until Stop() is called.
41
42
43
func (s *Server) Start(config *Configuration) error {
	if s.conf.LogJSON {
		s.conf.Logger.WithField("configuration", s.conf).Debug("Configuration")
44
	} else {
45
46
		bts, _ := json.MarshalIndent(s.conf, "", "   ")
		s.conf.Logger.Debug("Configuration: ", string(bts), "\n")
47
	}
48

49
50
51
52
53
54
55
56
57
	// We start either one or two servers, depending on whether a separate client server is enabled, such that:
	// - if any of them returns, the other is also stopped (neither of them is of use without the other)
	// - if any of them returns an unexpected error (ie. other than http.ErrServerClosed), the error is logged and returned
	// - we have a way of stopping all servers from outside (with Stop())
	// - the function returns only after all servers have been stopped
	// - any unexpected error is dealt with here instead of when stopping using Stop().
	// Inspired by https://dave.cheney.net/practical-go/presentations/qcon-china.html#_never_start_a_goroutine_without_when_it_will_stop

	count := 1
58
	if s.conf.separateClientServer() {
59
		count = 2
60
	}
61
62
	done := make(chan error, count)
	s.stop = make(chan struct{})
63
	s.stopped = make(chan struct{}, count)
64

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
	if s.conf.separateClientServer() {
		go func() {
			done <- s.startClientServer()
		}()
	}
	go func() {
		done <- s.startRequestorServer()
	}()

	var stopped bool
	var err error
	for i := 0; i < cap(done); i++ {
		if err = <-done; err != nil {
			_ = server.LogError(err)
		}
		if !stopped {
			stopped = true
			close(s.stop)
		}
	}

	return err
87
88
}

89
func (s *Server) startRequestorServer() error {
90
	tlsConf, _ := s.conf.tlsConfig()
91
	return s.startServer(s.Handler(), "Server", s.conf.ListenAddress, s.conf.Port, tlsConf)
92
93
}

94
func (s *Server) startClientServer() error {
95
	tlsConf, _ := s.conf.clientTlsConfig()
96
	return s.startServer(s.ClientHandler(), "Client server", s.conf.ClientListenAddress, s.conf.ClientPort, tlsConf)
97
98
}

99
func (s *Server) startServer(handler http.Handler, name, addr string, port int, tlsConf *tls.Config) error {
100
	fulladdr := fmt.Sprintf("%s:%d", addr, port)
101
	s.conf.Logger.Info(name, " listening at ", fulladdr)
102
103
104
105
106
107
108
109
110

	serv := &http.Server{
		Addr:      fulladdr,
		Handler:   handler,
		TLSConfig: tlsConf,
	}

	go func() {
		<-s.stop
111
112
113
		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
		defer cancel()
		if err := serv.Shutdown(ctx); err != nil {
114
115
			_ = server.LogError(err)
		}
116
		s.stopped <- struct{}{}
117
118
	}()

Sietse Ringers's avatar
Sietse Ringers committed
119
	if tlsConf != nil {
120
		// Disable HTTP/2 (see package documentation of http): it breaks server side events :(
121
122
		serv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
		s.conf.Logger.Info(name, " TLS enabled")
123
		return filterStopError(serv.ListenAndServeTLS("", ""))
Sietse Ringers's avatar
Sietse Ringers committed
124
	} else {
125
		return filterStopError(serv.ListenAndServe())
126
127
128
	}
}

129
130
131
func filterStopError(err error) error {
	if err == http.ErrServerClosed {
		return nil
132
	}
133
134
	return err
}
135

136
137
138
func (s *Server) Stop() {
	s.irmaserv.Stop()
	s.stop <- struct{}{}
139
140
141
142
	<-s.stopped
	if s.conf.separateClientServer() {
		<-s.stopped
	}
143
144
}

145
func New(config *Configuration) (*Server, error) {
146
	irmaserv, err := irmaserver.New(config.Configuration)
147
148
	if err != nil {
		return nil, err
149
	}
150
151
	if err := config.initialize(); err != nil {
		return nil, err
152
	}
153
154
155
156
	return &Server{
		conf:     config,
		irmaserv: irmaserv,
	}, nil
157
}
158

159
160
161
162
163
164
var corsOptions = cors.Options{
	AllowedOrigins: []string{"*"},
	AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "Cache-Control"},
	AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete},
}

165
func (s *Server) ClientHandler() http.Handler {
166
	router := chi.NewRouter()
167
	router.Use(cors.New(corsOptions).Handler)
Sietse Ringers's avatar
Sietse Ringers committed
168

Sietse Ringers's avatar
Sietse Ringers committed
169
	router.Mount("/irma/", s.irmaserv.HandlerFunc())
170
	if s.conf.StaticPath != "" {
171
		router.Mount(s.conf.StaticPrefix, s.StaticFilesHandler())
172
173
	}

174
175
176
177
178
	return router
}

// Handler returns a http.Handler that handles all IRMA requestor messages
// and IRMA client messages.
179
func (s *Server) Handler() http.Handler {
180
	router := chi.NewRouter()
181
	router.Use(cors.New(corsOptions).Handler)
182
	router.Use(s.logHandler)
183

184
	if !s.conf.separateClientServer() {
185
		// Mount server for irmaclient
Sietse Ringers's avatar
Sietse Ringers committed
186
		router.Mount("/irma/", s.irmaserv.HandlerFunc())
187
188
189
		if s.conf.StaticPath != "" {
			router.Mount(s.conf.StaticPrefix, s.StaticFilesHandler())
		}
190
	}
191

192
	// Server routes
193
194
195
196
197
	router.Post("/session", s.handleCreate)
	router.Delete("/session/{token}", s.handleDelete)
	router.Get("/session/{token}/status", s.handleStatus)
	router.Get("/session/{token}/statusevents", s.handleStatusEvents)
	router.Get("/session/{token}/result", s.handleResult)
Sietse Ringers's avatar
Sietse Ringers committed
198
199

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

203
	router.Get("/publickey", s.handlePublicKey)
204

205
	return router
206
207
}

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// Wrapper for logging HTTP traffic
func (s *Server) logHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
		if s.conf.Verbose >= 2 {
			t1 := time.Now()
			respBuf := bytes.NewBufferString(": ") // : for printing format
			ww.Tee(respBuf)                        // Copy result into buffer for logging
			scheme := "http"
			if r.TLS != nil {
				scheme = "https"
			}

			recipient := ""
			// IP addresses of irmaclient users are hidden in logs for privacy
			if !strings.HasPrefix(r.URL.EscapedPath(), "/irma/") {
				recipient = " from " + r.RemoteAddr
			}
			s.conf.Logger.Tracef("=> New message \"%s %s://%s%s %s\"%s",
				r.Method, scheme, r.Host, r.RequestURI, r.Proto, recipient)
			s.conf.Logger.Trace("HTTP headers: ", server.ToJson(r.Header))

			defer func() {
				// Printing response after HTTP is served
				resp := ""
				if ww.BytesWritten() > 0 {
					resp = respBuf.String()
				}
				s.conf.Logger.Tracef("<= Responded with %d in %s%s", ww.Status(), time.Since(t1).String(), resp)
			}()
		}

		next.ServeHTTP(ww, r)
		return
	})
}

245
func (s *Server) StaticFilesHandler() http.Handler {
246
247
248
249
250
251
	if len(s.conf.URL) > 6 {
		url := s.conf.URL[:len(s.conf.URL)-6] + s.conf.StaticPrefix
		s.conf.Logger.Infof("Hosting files at %s under %s", s.conf.StaticPath, url)
	} else { // URL not known, don't log it but otherwise continue
		s.conf.Logger.Infof("Hosting files at %s", s.conf.StaticPath)
	}
252
253
254
255
256
257
258
259
	// Hook up chi middleware logger with our own logger
	middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{
		Logger:  log.New(s.conf.Logger.WriterLevel(logrus.TraceLevel), "static: ", 0),
		NoColor: true,
	})
	return http.StripPrefix(s.conf.StaticPrefix, middleware.Logger(http.FileServer(http.Dir(s.conf.StaticPath))))
}

260
func (s *Server) handleCreate(w http.ResponseWriter, r *http.Request) {
261
262
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
263
		s.conf.Logger.Error("Could not read session request HTTP POST body")
264
		_ = server.LogError(err)
Sietse Ringers's avatar
Sietse Ringers committed
265
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
266
267
		return
	}
268

Sietse Ringers's avatar
Sietse Ringers committed
269
270
271
	// 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.
272
	var (
273
		rrequest  irma.RequestorRequest
274
275
276
277
278
		request   irma.SessionRequest
		requestor string
		rerr      *irma.RemoteError
		applies   bool
	)
Sietse Ringers's avatar
Sietse Ringers committed
279
	for _, authenticator := range authenticators { // rrequest abbreviates "requestor request"
280
		applies, rrequest, requestor, rerr = authenticator.Authenticate(r.Header, body)
281
282
283
284
285
		if applies || rerr != nil {
			break
		}
	}
	if rerr != nil {
286
		_ = server.LogError(rerr)
287
288
289
290
		server.WriteResponse(w, nil, rerr)
		return
	}
	if !applies {
291
		s.conf.Logger.Warnf("Session request uses unknown authentication method, HTTP headers: %s, HTTP POST body: %s",
292
			server.ToJson(r.Header), string(body))
Sietse Ringers's avatar
Sietse Ringers committed
293
		server.WriteError(w, server.ErrorInvalidRequest, "Request could not be authorized")
294
295
296
		return
	}

297
298
	// Authorize request: check if the requestor is allowed to verify or issue
	// the requested attributes or credentials
299
	request = rrequest.SessionRequest()
300
	if request.Action() == irma.ActionIssuing {
301
		allowed, reason := s.conf.CanIssue(requestor, request.(*irma.IssuanceRequest).Credentials)
302
		if !allowed {
303
			s.conf.Logger.WithFields(logrus.Fields{"requestor": requestor, "id": reason}).
304
				Warn("Requestor not authorized to issue credential; full request: ", server.ToJson(request))
305
306
307
308
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}
309
310
311
	condiscon := request.Disclosure().Disclose
	if len(condiscon) > 0 {
		allowed, reason := s.conf.CanVerifyOrSign(requestor, request.Action(), condiscon)
312
		if !allowed {
313
			s.conf.Logger.WithFields(logrus.Fields{"requestor": requestor, "id": reason}).
314
				Warn("Requestor not authorized to verify attribute; full request: ", server.ToJson(request))
315
316
317
318
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}
319
320
	if rrequest.Base().CallbackUrl != "" && s.conf.jwtPrivateKey == nil {
		s.conf.Logger.WithFields(logrus.Fields{"requestor": requestor}).Warn("Requestor provided callbackUrl but no JWT private key is installed")
321
322
323
		server.WriteError(w, server.ErrorUnsupported, "")
		return
	}
324
325

	// Everything is authenticated and parsed, we're good to go!
326
	qr, token, err := s.irmaserv.StartSession(rrequest, s.doResultCallback)
327
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
328
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
329
330
331
		return
	}

332
333
334
335
	server.WriteJson(w, server.SessionPackage{
		SessionPtr: qr,
		Token:      token,
	})
336
337
}

338
339
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
	res := s.irmaserv.GetSessionResult(chi.URLParam(r, "token"))
340
	if res == nil {
Sietse Ringers's avatar
Sietse Ringers committed
341
		server.WriteError(w, server.ErrorSessionUnknown, "")
342
343
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
344
	server.WriteJson(w, res.Status)
345
346
}

347
func (s *Server) handleStatusEvents(w http.ResponseWriter, r *http.Request) {
348
	token := chi.URLParam(r, "token")
349
	s.conf.Logger.WithFields(logrus.Fields{"session": token}).Debug("new client subscribed to server sent events")
350
	if err := s.irmaserv.SubscribeServerSentEvents(w, r, token, true); err != nil {
351
352
353
354
355
		server.WriteResponse(w, nil, &irma.RemoteError{
			Status:      server.ErrorUnsupported.Status,
			ErrorName:   string(server.ErrorUnsupported.Type),
			Description: server.ErrorUnsupported.Description,
		})
356
357
358
	}
}

359
360
func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) {
	err := s.irmaserv.CancelSession(chi.URLParam(r, "token"))
361
362
363
364
365
	if err != nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
	}
}

366
367
func (s *Server) handleResult(w http.ResponseWriter, r *http.Request) {
	res := s.irmaserv.GetSessionResult(chi.URLParam(r, "token"))
368
	if res == nil {
Sietse Ringers's avatar
Sietse Ringers committed
369
		server.WriteError(w, server.ErrorSessionUnknown, "")
370
371
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
372
	server.WriteJson(w, res)
373
374
}

375
376
377
func (s *Server) handleJwtResult(w http.ResponseWriter, r *http.Request) {
	if s.conf.jwtPrivateKey == nil {
		s.conf.Logger.Warn("Session result JWT requested but no JWT private key is configured")
378
379
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
380
	}
381

382
	sessiontoken := chi.URLParam(r, "token")
383
	res := s.irmaserv.GetSessionResult(sessiontoken)
384
385
386
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
387
	}
388

389
	j, err := s.resultJwt(res)
390
	if err != nil {
391
		s.conf.Logger.Error("Failed to sign session result JWT")
392
		_ = server.LogError(err)
393
394
395
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
396
	server.WriteString(w, j)
397
398
}

399
400
401
func (s *Server) handleJwtProofs(w http.ResponseWriter, r *http.Request) {
	if s.conf.jwtPrivateKey == nil {
		s.conf.Logger.Warn("Session result JWT requested but no JWT private key is configured")
402
403
404
405
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
	}

406
	sessiontoken := chi.URLParam(r, "token")
407
	res := s.irmaserv.GetSessionResult(sessiontoken)
408
409
410
411
412
413
414
415
416
417
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
	}

	claims := jwt.MapClaims{}

	// Fill standard claims
	switch res.Type {
	case irma.ActionDisclosing:
418
		claims["sub"] = "disclosure_result"
419
	case irma.ActionSigning:
420
		claims["sub"] = "abs_result"
421
	default:
422
423
		server.WriteError(w, server.ErrorInvalidRequest, "")
		return
424
425
	}
	claims["iat"] = time.Now().Unix()
426
427
	if s.conf.JwtIssuer != "" {
		claims["iss"] = s.conf.JwtIssuer
428
	}
429
	claims["status"] = res.ProofStatus
430
	validity := s.irmaserv.GetRequest(sessiontoken).Base().ResultJwtValidity
431
432
433
	if validity != 0 {
		claims["exp"] = time.Now().Unix() + int64(validity)
	}
434
435
436

	// Disclosed credentials and possibly signature
	m := make(map[irma.AttributeTypeIdentifier]string, len(res.Disclosed))
437
438
439
440
	for _, set := range res.Disclosed {
		for _, attr := range set {
			m[attr.Identifier] = attr.Value[""]
		}
441
442
443
444
445
446
447
448
	}
	claims["attributes"] = m
	if res.Signature != nil {
		claims["signature"] = res.Signature
	}

	// Sign the jwt and return it
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
449
	resultJwt, err := token.SignedString(s.conf.jwtPrivateKey)
450
	if err != nil {
451
		s.conf.Logger.Error("Failed to sign session result JWT")
452
		_ = server.LogError(err)
453
454
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
455
	}
456
	server.WriteString(w, resultJwt)
457
}
458

459
460
func (s *Server) handlePublicKey(w http.ResponseWriter, r *http.Request) {
	if s.conf.jwtPrivateKey == nil {
461
462
463
464
		server.WriteError(w, server.ErrorUnsupported, "")
		return
	}

465
	bts, err := x509.MarshalPKIXPublicKey(&s.conf.jwtPrivateKey.PublicKey)
466
467
468
469
470
471
472
473
474
475
	if err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
	pubBytes := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: bts,
	})
	_, _ = w.Write(pubBytes)
}
476

477
func (s *Server) resultJwt(sessionresult *server.SessionResult) (string, error) {
478
479
480
481
482
	claims := struct {
		jwt.StandardClaims
		*server.SessionResult
	}{
		StandardClaims: jwt.StandardClaims{
483
			Issuer:   s.conf.JwtIssuer,
484
485
486
487
488
			IssuedAt: time.Now().Unix(),
			Subject:  string(sessionresult.Type) + "_result",
		},
		SessionResult: sessionresult,
	}
489
	validity := s.irmaserv.GetRequest(sessionresult.Token).Base().ResultJwtValidity
490
491
492
493
494
495
	if validity != 0 {
		claims.ExpiresAt = time.Now().Unix() + int64(validity)
	}

	// Sign the jwt and return it
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
496
	return token.SignedString(s.conf.jwtPrivateKey)
497
498
}

499
500
501
func (s *Server) doResultCallback(result *server.SessionResult) {
	callbackUrl := s.irmaserv.GetRequest(result.Token).Base().CallbackUrl
	if callbackUrl == "" || s.conf.jwtPrivateKey == nil {
502
503
		return
	}
504
	s.conf.Logger.WithFields(logrus.Fields{"session": result.Token, "callbackUrl": callbackUrl}).Debug("POSTing session result")
505

506
	j, err := s.resultJwt(result)
507
508
509
510
511
512
513
514
	if err != nil {
		_ = server.LogError(errors.WrapPrefix(err, "Failed to create JWT for result callback", 0))
		return
	}

	var x string // dummy for the server's return value that we don't care about
	if err := irma.NewHTTPTransport(callbackUrl).Post("", &x, j); err != nil {
		// not our problem, log it and go on
515
		s.conf.Logger.Warn(errors.WrapPrefix(err, "Failed to POST session result to callback URL", 0))
516
517
	}
}