server.go 17.8 KB
Newer Older
1
package keyshareserver
2
3

import (
4
	"context"
5
6
7
	"fmt"
	"net/http"
	"strings"
8
	"time"
9

10
11
	"github.com/go-errors/errors"
	"github.com/hashicorp/go-multierror"
12
	"github.com/jasonlvhit/gocron"
13
14
15
16
17
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
	irma "github.com/privacybydesign/irmago"
	"github.com/sirupsen/logrus"

18
	"github.com/privacybydesign/irmago/internal/common"
19
	"github.com/privacybydesign/irmago/internal/keysharecore"
20
21
22
23
24
25
26
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmaserver"

	"github.com/go-chi/chi"
)

type Server struct {
27
	// configuration
28
29
	conf *Configuration

30
	// external components
31
	core          *keysharecore.Core
32
	sessionserver *irmaserver.Server
33
	db            DB
34
35

	// Scheduler used to clean sessions
36
37
	scheduler     *gocron.Scheduler
	stopScheduler chan<- bool
38

39
	// Session data, keeping track of current keyshare protocol session state for each user
40
	store sessionStore
41
42
43
44
45
}

func New(conf *Configuration) (*Server, error) {
	var err error
	s := &Server{
46
		conf:      conf,
47
		store:     newMemorySessionStore(10 * time.Second),
48
		scheduler: gocron.NewScheduler(),
49
50
	}

51
52
	// Setup IRMA session server
	s.sessionserver, err = irmaserver.New(conf.Configuration)
53
54
55
56
	if err != nil {
		return nil, err
	}

57
58
	// Process configuration and create keyshare core
	s.core, err = processConfiguration(conf)
59
60
61
62
	if err != nil {
		return nil, err
	}

63
64
	// Load neccessary idemix keys into core, and ensure that future updates
	// to them are processed
65
	if err = s.loadIdemixKeys(conf.IrmaConfiguration); err != nil {
66
67
68
		return nil, err
	}
	conf.IrmaConfiguration.UpdateListeners = append(conf.IrmaConfiguration.UpdateListeners, func(conf *irma.Configuration) {
69
		if err := s.loadIdemixKeys(conf); err != nil {
70
71
72
73
			// run periodically; can only log the error here
			_ = server.LogError(err)
		}
	})
74

75
76
	// Setup DB
	s.db = conf.DB
77

78
	// Setup session cache clearing
79
	s.scheduler.Every(10).Seconds().Do(s.store.flush)
80
81
	s.stopScheduler = s.scheduler.Start()

82
83
84
	return s, nil
}

85
86
87
88
89
func (s *Server) Stop() {
	s.stopScheduler <- true
	s.sessionserver.Stop()
}

90
91
func (s *Server) Handler() http.Handler {
	router := chi.NewRouter()
92

93
	router.Group(func(router chi.Router) {
94
95
96
		router.Use(server.SizeLimitMiddleware)
		router.Use(server.TimeoutMiddleware(nil, server.WriteTimeout))

97
98
99
100
101
		if s.conf.Verbose >= 2 {
			opts := server.LogOptions{Response: true, Headers: true, From: false, EncodeBinary: true}
			router.Use(server.LogMiddleware("keyshareserver", opts))
		}

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
		// Registration
		router.Post("/client/register", s.handleRegister)

		// Pin logic
		router.Post("/users/verify/pin", s.handleVerifyPin)
		router.Post("/users/change/pin", s.handleChangePin)

		// Keyshare sessions
		router.Group(func(router chi.Router) {
			router.Use(s.userMiddleware)
			router.Use(s.authorizationMiddleware)
			router.Post("/users/isAuthorized", s.handleValidate)
			router.Post("/prove/getCommitments", s.handleCommitments)
			router.Post("/prove/getResponse", s.handleResponse)
		})
117
	})
118

119
	// IRMA server for issuing myirma credential during registration
120
121
122
123
	router.Mount("/irma/", s.sessionserver.HandlerFunc())
	return router
}

124
// On configuration changes, inform the keyshare core of any
125
// new IRMA issuer public keys.
126
func (s *Server) loadIdemixKeys(conf *irma.Configuration) error {
127
	errs := multierror.Error{}
128
	for _, issuer := range conf.Issuers {
129
		keyIDs, err := conf.PublicKeyIndices(issuer.Identifier())
130
		if err != nil {
131
			errs.Errors = append(errs.Errors, errors.Errorf("issuer %v: could not find key IDs: %v", issuer, err))
132
133
			continue
		}
134
		for _, id := range keyIDs {
135
136
			key, err := conf.PublicKey(issuer.Identifier(), id)
			if err != nil {
137
				errs.Errors = append(errs.Errors, server.LogError(errors.Errorf("key %v-%v: could not fetch public key: %v", issuer, id, err)))
138
139
				continue
			}
140
			s.core.DangerousAddTrustedPublicKey(irma.PublicKeyIdentifier{Issuer: issuer.Identifier(), Counter: id}, key)
141
142
		}
	}
143
	return errs.ErrorOrNil()
144
145
}

146
// /prove/getCommitments
147
func (s *Server) handleCommitments(w http.ResponseWriter, r *http.Request) {
148
	// Fetch from context
149
	user := r.Context().Value("user").(*User)
150
151
	authorization := r.Context().Value("authorization").(string)

152
	// Read keys
153
	var keys []irma.PublicKeyIdentifier
154
	if err := server.ParseBody(w, r, &keys); err != nil {
155
156
157
158
159
160
161
162
163
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}
	if len(keys) == 0 {
		s.conf.Logger.Info("Malformed request: no keys over which to commit specified")
		server.WriteError(w, server.ErrorInvalidRequest, "No key specified")
		return
	}

164
	commitments, err := s.generateCommitments(user, authorization, keys)
165
	if err != nil && (err == keysharecore.ErrInvalidChallenge || err == keysharecore.ErrInvalidJWT) {
166
167
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
168
	}
169
	if err != nil {
170
171
		// already logged
		server.WriteError(w, server.ErrorInternal, err.Error())
172
173
174
		return
	}

175
176
177
	server.WriteJson(w, commitments)
}

178
func (s *Server) generateCommitments(user *User, authorization string, keys []irma.PublicKeyIdentifier) (*irma.ProofPCommitmentMap, error) {
179
	// Generate commitments
180
	commitments, commitID, err := s.core.GenerateCommitments(user.Secrets, authorization, keys)
181
182
	if err != nil {
		s.conf.Logger.WithField("error", err).Warn("Could not generate commitments for request")
183
		return nil, err
184
185
186
	}

	// Prepare output message format
187
	mappedCommitments := map[irma.PublicKeyIdentifier]*gabi.ProofPCommitment{}
188
	for i, keyID := range keys {
189
		mappedCommitments[keyID] = commitments[i]
190
191
192
	}

	// Store needed data for later requests.
193
194
195
196
	// Of all keys involved in the current session, store the ID of the first one to be used when
	// the user comes back later to retrieve her response. gabi.ProofP.P will depend on this public
	// key, which is used only during issuance. Thus, this assumes that during issuance, the user
	// puts the key ID of the credential(s) being issued at index 0.
197
	s.store.add(user.Username, &session{
198
199
		KeyID:    keys[0],
		CommitID: commitID,
200
	})
201
202

	// And send response
203
	return &irma.ProofPCommitmentMap{Commitments: mappedCommitments}, nil
204
205
}

206
// /prove/getResponse
207
func (s *Server) handleResponse(w http.ResponseWriter, r *http.Request) {
208
	// Fetch from context
209
	user := r.Context().Value("user").(*User)
210
211
	authorization := r.Context().Value("authorization").(string)

212
213
	// Read challenge
	challenge := new(big.Int)
214
	if err := server.ParseBody(w, r, challenge); err != nil {
215
216
217
218
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}

219
220
221
222
223
	// verify access (avoids leaking whether there is a session ongoing to unauthorized callers)
	if !r.Context().Value("hasValidAuthorization").(bool) {
		s.conf.Logger.Warn("Could not generate keyshare response due to invalid authorization")
		server.WriteError(w, server.ErrorInvalidRequest, "Invalid authorization")
		return
224
	}
225

226
	// Get data from session
227
228
	sessionData := s.store.get(user.Username)
	if sessionData == nil {
229
230
		s.conf.Logger.Warn("Request for response without previous call to get commitments")
		server.WriteError(w, server.ErrorInvalidRequest, "Missing previous call to getCommitments")
231
232
233
		return
	}

234
	// And do the actual responding
235
	proofResponse, err := s.generateResponses(user, authorization, challenge, sessionData.CommitID, sessionData.KeyID)
236
	if err != nil && (err == keysharecore.ErrInvalidChallenge || err == keysharecore.ErrInvalidJWT) {
237
238
239
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}
240
241
242
243
244
245
246
247
	if err != nil {
		// already logged
		server.WriteError(w, server.ErrorInternal, err.Error())
		return
	}

	server.WriteString(w, proofResponse)
}
248

249
func (s *Server) generateResponses(user *User, authorization string, challenge *big.Int, commitID uint64, keyID irma.PublicKeyIdentifier) (string, error) {
David Venhoek's avatar
David Venhoek committed
250
	// Indicate activity on user account
251
	err := s.db.setSeen(user)
David Venhoek's avatar
David Venhoek committed
252
253
254
255
256
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not mark user as seen recently")
		// Do not send to user
	}

257
	// Make log entry
258
	err = s.db.addLog(user, eventTypeIRMASession, nil)
259
260
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
261
		return "", err
262
263
	}

264
	proofResponse, err := s.core.GenerateResponse(user.Secrets, authorization, commitID, challenge, keyID)
265
266
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not generate response for request")
267
		return "", err
268
269
	}

270
	return proofResponse, nil
271
272
}

273
// /users/isAuthorized
274
func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) {
275
	if r.Context().Value("hasValidAuthorization").(bool) {
276
		server.WriteJson(w, &irma.KeyshareAuthorization{Status: "authorized", Candidates: []string{"pin"}})
277
	} else {
278
		server.WriteJson(w, &irma.KeyshareAuthorization{Status: "expired", Candidates: []string{"pin"}})
279
280
281
	}
}

282
// /users/verify/pin
283
284
func (s *Server) handleVerifyPin(w http.ResponseWriter, r *http.Request) {
	// Extract request
285
	var msg irma.KeysharePinMessage
286
	if err := server.ParseBody(w, r, &msg); err != nil {
287
288
289
290
291
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}

	// Fetch user
292
	user, err := s.db.user(msg.Username)
293
294
295
296
297
298
	if err != nil {
		s.conf.Logger.WithFields(logrus.Fields{"username": msg.Username, "error": err}).Warn("Could not find user in db")
		server.WriteError(w, server.ErrorUserNotRegistered, "")
		return
	}

299
	// and verify pin
300
	result, err := s.verifyPin(user, msg.Pin)
301
	if err != nil {
302
		// already logged
303
304
305
		server.WriteError(w, server.ErrorInternal, err.Error())
		return
	}
306
307
308
309

	server.WriteJson(w, result)
}

310
func (s *Server) verifyPin(user *User, pin string) (irma.KeysharePinStatus, error) {
311
312
	// Check whether pin check is currently allowed
	ok, tries, wait, err := s.reservePinCheck(user, pin)
313
	if err != nil {
314
		return irma.KeysharePinStatus{}, err
315
	}
316
	if !ok {
317
		return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
318
	}
319

320
	// At this point, we are allowed to do an actual check (we have successfully reserved a spot for it), so do it.
321
	jwtt, err := s.core.ValidatePin(user.Secrets, pin)
322
323
324
	if err != nil && err != keysharecore.ErrInvalidPin {
		// Errors other than invalid pin are real errors
		s.conf.Logger.WithField("error", err).Error("Could not validate pin")
325
		return irma.KeysharePinStatus{}, err
326
	}
327

328
	if err == keysharecore.ErrInvalidPin {
329
		// Handle invalid pin
330
		err = s.db.addLog(user, eventTypePinCheckFailed, tries)
331
332
		if err != nil {
			s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
333
			return irma.KeysharePinStatus{}, err
334
		}
335
		if tries == 0 {
336
			err = s.db.addLog(user, eventTypePinCheckBlocked, wait)
337
338
			if err != nil {
				s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
339
				return irma.KeysharePinStatus{}, err
340
			}
341
			return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
342
		} else {
343
			return irma.KeysharePinStatus{Status: "failure", Message: fmt.Sprintf("%v", tries)}, nil
344
		}
345
	}
346

347
	// Handle success
348
	err = s.db.resetPinTries(user)
349
350
351
352
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reset users pin check logic")
		// Do not send to user
	}
353
	err = s.db.setSeen(user)
354
355
356
357
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not indicate user activity")
		// Do not send to user
	}
358
	err = s.db.addLog(user, eventTypePinCheckSuccess, nil)
359
360
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
361
		return irma.KeysharePinStatus{}, err
362
	}
363

364
	return irma.KeysharePinStatus{Status: "success", Message: jwtt}, err
365
366
}

367
// /users/change/pin
368
369
func (s *Server) handleChangePin(w http.ResponseWriter, r *http.Request) {
	// Extract request
370
	var msg irma.KeyshareChangePin
371
	if err := server.ParseBody(w, r, &msg); err != nil {
372
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
373
374
375
376
		return
	}

	// Fetch user
377
	user, err := s.db.user(msg.Username)
378
379
380
381
382
383
	if err != nil {
		s.conf.Logger.WithFields(logrus.Fields{"username": msg.Username, "error": err}).Warn("Could not find user in db")
		server.WriteError(w, server.ErrorUserNotRegistered, "")
		return
	}

384
	result, err := s.updatePin(user, msg.OldPin, msg.NewPin)
385
	if err != nil {
386
		// already logged
387
		server.WriteError(w, server.ErrorInternal, err.Error())
388
389
		return
	}
390
391
392
	server.WriteJson(w, result)
}

393
func (s *Server) updatePin(user *User, oldPin, newPin string) (irma.KeysharePinStatus, error) {
394
	// Check whether pin check is currently allowed
395
	ok, tries, wait, err := s.reservePinCheck(user, oldPin)
396
	if err != nil {
397
		return irma.KeysharePinStatus{}, err
398
	}
399
	if !ok {
400
		return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
401
	}
402
403

	// Try to do the update
404
	user.Secrets, err = s.core.ChangePin(user.Secrets, oldPin, newPin)
405
	if err == keysharecore.ErrInvalidPin {
406
		if tries == 0 {
407
			return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
408
		} else {
409
			return irma.KeysharePinStatus{Status: "failure", Message: fmt.Sprintf("%v", tries)}, nil
410
411
412
		}
	} else if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not change pin")
413
		return irma.KeysharePinStatus{}, err
414
	}
415
416

	// Mark pincheck as success, resetting users wait and count
417
	err = s.db.resetPinTries(user)
418
419
420
421
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reset users pin check logic")
		// Do not send to user
	}
422
423

	// Write user back
424
	err = s.db.updateUser(user)
425
426
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not write updated user to database")
427
		return irma.KeysharePinStatus{}, err
428
429
	}

430
	return irma.KeysharePinStatus{Status: "success"}, nil
431
432
}

433
// /client/register
434
435
func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
	// Extract request
436
	var msg irma.KeyshareEnrollment
437
	if err := server.ParseBody(w, r, &msg); err != nil {
438
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
439
440
441
		return
	}

442
	sessionptr, err := s.register(msg)
443
	if err != nil && err == keysharecore.ErrPinTooLong {
444
445
446
447
448
449
450
451
452
453
454
455
		// Too long pin is not an internal error
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}
	if err != nil {
		// Already logged
		server.WriteError(w, server.ErrorInternal, err.Error())
		return
	}
	server.WriteJson(w, sessionptr)
}

456
func (s *Server) register(msg irma.KeyshareEnrollment) (*irma.Qr, error) {
457
	// Generate keyshare server account
458
459
460
	username := common.NewSessionToken() // TODO use newRandomString() for this when shoulder-surf is merged
	username = username[:12]

461
	secrets, err := s.core.NewUserSecrets(msg.Pin)
462
463
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not register user")
464
		return nil, err
465
	}
466
	user := &User{Username: username, Language: msg.Language, Secrets: secrets}
467
	err = s.db.AddUser(user)
468
469
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not store new user in database")
470
		return nil, err
471
472
	}

473
	// Send email if user specified email address
474
	if msg.Email != nil && *msg.Email != "" && s.conf.EmailServer != "" {
475
		err = s.sendRegistrationEmail(user, msg.Language, *msg.Email)
476
		if err != nil {
477
478
			// already logged in sendRegistrationEmail
			return nil, err
479
		}
480
481
	}

482
483
484
	// Setup and return issuance session for keyshare credential.
	request := irma.NewIssuanceRequest([]*irma.CredentialRequest{
		{
485
			CredentialTypeID: s.conf.KeyshareAttribute.CredentialTypeIdentifier(),
486
			Attributes: map[string]string{
487
				s.conf.KeyshareAttribute.Name(): username,
488
489
490
491
492
			},
		}})
	sessionptr, _, err := s.sessionserver.StartSession(request, nil)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not start keyshare credential issuance sessions")
493
		return nil, err
494
	}
495
496
497
	return sessionptr, nil
}

498
func (s *Server) sendRegistrationEmail(user *User, language, email string) error {
499
500
501
502
	// Generate token
	token := common.NewSessionToken()

	// Add it to the database
503
	err := s.db.addEmailVerification(user, email, token)
504
	if err != nil {
505
		s.conf.Logger.WithField("error", err).Error("Could not generate email verification mail record")
506
507
508
		return err
	}

509
510
511
	verificationBaseURL := s.conf.TranslateString(s.conf.VerificationURL, language)
	return s.conf.SendEmail(
		s.conf.registrationEmailTemplates,
512
		s.conf.RegistrationEmailSubjects,
513
514
515
516
		map[string]string{"VerificationURL": verificationBaseURL + token},
		[]string{email},
		language,
	)
517
518
}

519
520
521
522
523
524
func (s *Server) userMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Extract username from request
		username := r.Header.Get("X-IRMA-Keyshare-Username")

		// and fetch its information
525
		user, err := s.db.user(username)
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
		if err != nil {
			s.conf.Logger.WithFields(logrus.Fields{"username": username, "error": err}).Warn("Could not find user in db")
			server.WriteError(w, server.ErrorUserNotRegistered, err.Error())
			return
		}

		next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "user", user)))
	})
}

func (s *Server) authorizationMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Extract authorization from request
		authorization := r.Header.Get("Authorization")
		if strings.HasPrefix(authorization, "Bearer ") {
			authorization = authorization[7:]
		}

		// verify access
		ctx := r.Context()
546
		err := s.core.ValidateJWT(ctx.Value("user").(*User).Secrets, authorization)
547
		hasValidAuthorization := err == nil
548
549
550
551
552
553
554
555
556

		// Construct new context with both authorization and its validity
		nextContext := context.WithValue(
			context.WithValue(ctx, "authorization", authorization),
			"hasValidAuthorization", hasValidAuthorization)

		next.ServeHTTP(w, r.WithContext(nextContext))
	})
}
557

558
func (s *Server) reservePinCheck(user *User, pin string) (bool, int, int64, error) {
559
	ok, tries, wait, err := s.db.reservePinTry(user)
560
561
562
563
564
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reserve pin check slot")
		return false, 0, 0, err
	}
	if !ok {
565
		err = s.db.addLog(user, eventTypePinCheckRefused, nil)
566
567
568
569
570
571
572
573
		if err != nil {
			s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
			return false, 0, 0, err
		}
		return false, tries, wait, nil
	}
	return true, tries, wait, nil
}