server.go 17.9 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
32
33
	core     *keysharecore.Core
	irmaserv *irmaserver.Server
	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
var errMissingCommitment = errors.New("missing previous call to getCommitments")

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

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

59
	// Process configuration and create keyshare core
60
61
62
63
64
65
66
67
68
69
70
71
72
	err = validateConf(conf)
	if err != nil {
		return nil, err
	}
	if conf.DB != nil {
		s.db = conf.DB
	} else {
		s.db, err = setupDatabase(conf)
		if err != nil {
			return nil, err
		}
	}
	s.core, err = setupCore(conf)
73
74
75
76
	if err != nil {
		return nil, err
	}

77
	// Load Idemix keys into core, and ensure that new keys added in the future will be loaded as well.
78
	if err = s.loadIdemixKeys(conf.IrmaConfiguration); err != nil {
79
80
		return nil, err
	}
81
82
	conf.IrmaConfiguration.UpdateListeners = append(conf.IrmaConfiguration.UpdateListeners, func(c *irma.Configuration) {
		if err := s.loadIdemixKeys(c); err != nil {
83
84
85
86
			// run periodically; can only log the error here
			_ = server.LogError(err)
		}
	})
87

88
	// Setup session cache clearing
89
	s.scheduler.Every(10).Seconds().Do(s.store.flush)
90
91
	s.stopScheduler = s.scheduler.Start()

92
93
94
	return s, nil
}

95
96
func (s *Server) Stop() {
	s.stopScheduler <- true
97
	s.irmaserv.Stop()
98
99
}

100
101
func (s *Server) Handler() http.Handler {
	router := chi.NewRouter()
102

103
	router.Group(func(router chi.Router) {
104
105
106
		router.Use(server.SizeLimitMiddleware)
		router.Use(server.TimeoutMiddleware(nil, server.WriteTimeout))

107
108
109
110
111
		if s.conf.Verbose >= 2 {
			opts := server.LogOptions{Response: true, Headers: true, From: false, EncodeBinary: true}
			router.Use(server.LogMiddleware("keyshareserver", opts))
		}

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
		// 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)
		})
127
	})
128

129
	// IRMA server for issuing myirma credential during registration
130
	router.Mount("/irma/", s.irmaserv.HandlerFunc())
131
132
133
	return router
}

134
// On configuration changes, update the keyshare core with all current public keys of the IRMA issuers.
135
func (s *Server) loadIdemixKeys(conf *irma.Configuration) error {
136
	errs := multierror.Error{}
137
	for _, issuer := range conf.Issuers {
138
		keyIDs, err := conf.PublicKeyIndices(issuer.Identifier())
139
		if err != nil {
140
			errs.Errors = append(errs.Errors, errors.Errorf("issuer %v: could not find key IDs: %v", issuer, err))
141
142
			continue
		}
143
		for _, id := range keyIDs {
144
145
			key, err := conf.PublicKey(issuer.Identifier(), id)
			if err != nil {
146
				errs.Errors = append(errs.Errors, server.LogError(errors.Errorf("key %v-%v: could not fetch public key: %v", issuer, id, err)))
147
148
				continue
			}
149
			s.core.DangerousAddTrustedPublicKey(irma.PublicKeyIdentifier{Issuer: issuer.Identifier(), Counter: id}, key)
150
151
		}
	}
152
	return errs.ErrorOrNil()
153
154
}

155
// /prove/getCommitments
156
func (s *Server) handleCommitments(w http.ResponseWriter, r *http.Request) {
157
	// Fetch from context
158
	user := r.Context().Value("user").(*User)
159
160
	authorization := r.Context().Value("authorization").(string)

161
	// Read keys
162
	var keys []irma.PublicKeyIdentifier
163
	if err := server.ParseBody(r, &keys); err != nil {
164
165
166
167
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}
	if len(keys) == 0 {
168
		s.conf.Logger.Info("Malformed request: no keys for commitment specified")
169
170
171
172
		server.WriteError(w, server.ErrorInvalidRequest, "No key specified")
		return
	}

173
	commitments, err := s.generateCommitments(user, authorization, keys)
174
	if err != nil && (err == keysharecore.ErrInvalidChallenge || err == keysharecore.ErrInvalidJWT) {
175
176
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
177
	}
178
	if err != nil {
179
180
		// already logged
		server.WriteError(w, server.ErrorInternal, err.Error())
181
182
183
		return
	}

184
185
186
	server.WriteJson(w, commitments)
}

187
func (s *Server) generateCommitments(user *User, authorization string, keys []irma.PublicKeyIdentifier) (*irma.ProofPCommitmentMap, error) {
188
	// Generate commitments
189
	commitments, commitID, err := s.core.GenerateCommitments(user.Secrets, authorization, keys)
190
191
	if err != nil {
		s.conf.Logger.WithField("error", err).Warn("Could not generate commitments for request")
192
		return nil, err
193
194
195
	}

	// Prepare output message format
196
	mappedCommitments := map[irma.PublicKeyIdentifier]*gabi.ProofPCommitment{}
197
	for i, keyID := range keys {
198
		mappedCommitments[keyID] = commitments[i]
199
200
201
	}

	// Store needed data for later requests.
202
203
204
205
	// 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.
206
	s.store.add(user.Username, &session{
207
208
		KeyID:    keys[0],
		CommitID: commitID,
209
	})
210
211

	// And send response
212
	return &irma.ProofPCommitmentMap{Commitments: mappedCommitments}, nil
213
214
}

215
// /prove/getResponse
216
func (s *Server) handleResponse(w http.ResponseWriter, r *http.Request) {
217
	// Fetch from context
218
	user := r.Context().Value("user").(*User)
219
220
	authorization := r.Context().Value("authorization").(string)

221
222
	// Read challenge
	challenge := new(big.Int)
223
	if err := server.ParseBody(r, challenge); err != nil {
224
225
226
227
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}

228
229
230
231
232
	// 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
233
	}
234

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

	server.WriteString(w, proofResponse)
}
252

253
254
255
256
257
258
259
260
func (s *Server) generateResponse(user *User, authorization string, challenge *big.Int) (string, error) {
	// Get data from session
	sessionData := s.store.get(user.Username)
	if sessionData == nil {
		s.conf.Logger.Warn("Request for response without previous call to get commitments")
		return "", errMissingCommitment
	}

David Venhoek's avatar
David Venhoek committed
261
	// Indicate activity on user account
262
	err := s.db.setSeen(user)
David Venhoek's avatar
David Venhoek committed
263
264
265
266
267
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not mark user as seen recently")
		// Do not send to user
	}

268
	// Make log entry
269
	err = s.db.addLog(user, eventTypeIRMASession, nil)
270
271
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
272
		return "", err
273
274
	}

275
	proofResponse, err := s.core.GenerateResponse(user.Secrets, authorization, sessionData.CommitID, challenge, sessionData.KeyID)
276
277
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not generate response for request")
278
		return "", err
279
280
	}

281
	return proofResponse, nil
282
283
}

284
// /users/isAuthorized
285
func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) {
286
	if r.Context().Value("hasValidAuthorization").(bool) {
287
		server.WriteJson(w, &irma.KeyshareAuthorization{Status: "authorized", Candidates: []string{"pin"}})
288
	} else {
289
		server.WriteJson(w, &irma.KeyshareAuthorization{Status: "expired", Candidates: []string{"pin"}})
290
291
292
	}
}

293
// /users/verify/pin
294
295
func (s *Server) handleVerifyPin(w http.ResponseWriter, r *http.Request) {
	// Extract request
296
	var msg irma.KeysharePinMessage
297
	if err := server.ParseBody(r, &msg); err != nil {
298
299
300
301
302
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}

	// Fetch user
303
	user, err := s.db.user(msg.Username)
304
305
306
307
308
309
	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
	}

310
	// and verify pin
311
	result, err := s.verifyPin(user, msg.Pin)
312
	if err != nil {
313
		// already logged
314
315
316
		server.WriteError(w, server.ErrorInternal, err.Error())
		return
	}
317
318
319
320

	server.WriteJson(w, result)
}

321
func (s *Server) verifyPin(user *User, pin string) (irma.KeysharePinStatus, error) {
322
	// Check whether pin check is currently allowed
323
	ok, tries, wait, err := s.reservePinCheck(user)
324
	if err != nil {
325
		return irma.KeysharePinStatus{}, err
326
	}
327
	if !ok {
328
		return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
329
	}
330

331
	// At this point, we are allowed to do an actual check (we have successfully reserved a spot for it), so do it.
332
	jwtt, err := s.core.ValidatePin(user.Secrets, pin)
333
334
335
	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")
336
		return irma.KeysharePinStatus{}, err
337
	}
338

339
	if err == keysharecore.ErrInvalidPin {
340
		// Handle invalid pin
341
		err = s.db.addLog(user, eventTypePinCheckFailed, tries)
342
343
		if err != nil {
			s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
344
			return irma.KeysharePinStatus{}, err
345
		}
346
		if tries == 0 {
347
			err = s.db.addLog(user, eventTypePinCheckBlocked, wait)
348
349
			if err != nil {
				s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
350
				return irma.KeysharePinStatus{}, err
351
			}
352
			return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
353
		} else {
354
			return irma.KeysharePinStatus{Status: "failure", Message: fmt.Sprintf("%v", tries)}, nil
355
		}
356
	}
357

358
	// Handle success
359
	err = s.db.resetPinTries(user)
360
361
362
363
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reset users pin check logic")
		// Do not send to user
	}
364
	err = s.db.setSeen(user)
365
366
367
368
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not indicate user activity")
		// Do not send to user
	}
369
	err = s.db.addLog(user, eventTypePinCheckSuccess, nil)
370
371
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
372
		return irma.KeysharePinStatus{}, err
373
	}
374

375
	return irma.KeysharePinStatus{Status: "success", Message: jwtt}, err
376
377
}

378
// /users/change/pin
379
380
func (s *Server) handleChangePin(w http.ResponseWriter, r *http.Request) {
	// Extract request
381
	var msg irma.KeyshareChangePin
382
	if err := server.ParseBody(r, &msg); err != nil {
383
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
384
385
386
387
		return
	}

	// Fetch user
388
	user, err := s.db.user(msg.Username)
389
390
391
392
393
394
	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
	}

395
	result, err := s.updatePin(user, msg.OldPin, msg.NewPin)
396
	if err != nil {
397
		// already logged
398
		server.WriteError(w, server.ErrorInternal, err.Error())
399
400
		return
	}
401
402
403
	server.WriteJson(w, result)
}

404
func (s *Server) updatePin(user *User, oldPin, newPin string) (irma.KeysharePinStatus, error) {
405
	// Check whether pin check is currently allowed
406
	ok, tries, wait, err := s.reservePinCheck(user)
407
	if err != nil {
408
		return irma.KeysharePinStatus{}, err
409
	}
410
	if !ok {
411
		return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
412
	}
413
414

	// Try to do the update
415
	user.Secrets, err = s.core.ChangePin(user.Secrets, oldPin, newPin)
416
	if err == keysharecore.ErrInvalidPin {
417
		if tries == 0 {
418
			return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
419
		} else {
420
			return irma.KeysharePinStatus{Status: "failure", Message: fmt.Sprintf("%v", tries)}, nil
421
422
423
		}
	} else if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not change pin")
424
		return irma.KeysharePinStatus{}, err
425
	}
426
427

	// Mark pincheck as success, resetting users wait and count
428
	err = s.db.resetPinTries(user)
429
430
431
432
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reset users pin check logic")
		// Do not send to user
	}
433
434

	// Write user back
435
	err = s.db.updateUser(user)
436
437
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not write updated user to database")
438
		return irma.KeysharePinStatus{}, err
439
440
	}

441
	return irma.KeysharePinStatus{Status: "success"}, nil
442
443
}

444
// /client/register
445
446
func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
	// Extract request
447
	var msg irma.KeyshareEnrollment
448
	if err := server.ParseBody(r, &msg); err != nil {
449
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
450
451
452
		return
	}

453
	sessionptr, err := s.register(msg)
454
	if err != nil && err == keysharecore.ErrPinTooLong {
455
456
457
458
459
460
461
462
463
464
465
466
		// 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)
}

467
func (s *Server) register(msg irma.KeyshareEnrollment) (*irma.Qr, error) {
468
	// Generate keyshare server account
469
470
471
	username := common.NewSessionToken() // TODO use newRandomString() for this when shoulder-surf is merged
	username = username[:12]

472
	secrets, err := s.core.NewUserSecrets(msg.Pin)
473
474
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not register user")
475
		return nil, err
476
	}
477
	user := &User{Username: username, Language: msg.Language, Secrets: secrets}
478
	err = s.db.AddUser(user)
479
480
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not store new user in database")
481
		return nil, err
482
483
	}

484
	// Send email if user specified email address
485
	if msg.Email != nil && *msg.Email != "" && s.conf.EmailServer != "" {
486
		err = s.sendRegistrationEmail(user, msg.Language, *msg.Email)
487
		if err != nil {
488
489
			// already logged in sendRegistrationEmail
			return nil, err
490
		}
491
492
	}

493
494
495
	// Setup and return issuance session for keyshare credential.
	request := irma.NewIssuanceRequest([]*irma.CredentialRequest{
		{
496
			CredentialTypeID: s.conf.KeyshareAttribute.CredentialTypeIdentifier(),
497
			Attributes: map[string]string{
498
				s.conf.KeyshareAttribute.Name(): username,
499
500
			},
		}})
501
	sessionptr, _, err := s.irmaserv.StartSession(request, nil)
502
503
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not start keyshare credential issuance sessions")
504
		return nil, err
505
	}
506
507
508
	return sessionptr, nil
}

509
func (s *Server) sendRegistrationEmail(user *User, language, email string) error {
510
511
512
513
	// Generate token
	token := common.NewSessionToken()

	// Add it to the database
514
	err := s.db.addEmailVerification(user, email, token)
515
	if err != nil {
516
		s.conf.Logger.WithField("error", err).Error("Could not generate email verification mail record")
517
518
519
		return err
	}

520
521
522
	verificationBaseURL := s.conf.TranslateString(s.conf.VerificationURL, language)
	return s.conf.SendEmail(
		s.conf.registrationEmailTemplates,
523
		s.conf.RegistrationEmailSubjects,
524
525
526
527
		map[string]string{"VerificationURL": verificationBaseURL + token},
		[]string{email},
		language,
	)
528
529
}

530
531
532
533
534
535
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
536
		user, err := s.db.user(username)
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
		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()
557
		err := s.core.ValidateJWT(ctx.Value("user").(*User).Secrets, authorization)
558
		hasValidAuthorization := err == nil
559
560
561
562
563
564
565
566
567

		// 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))
	})
}
568

569
func (s *Server) reservePinCheck(user *User) (bool, int, int64, error) {
570
	ok, tries, wait, err := s.db.reservePinTry(user)
571
572
573
574
575
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reserve pin check slot")
		return false, 0, 0, err
	}
	if !ok {
576
		err = s.db.addLog(user, eventTypePinCheckRefused, nil)
577
578
579
580
581
582
583
584
		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
}