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

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

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

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

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

type SessionData struct {
28
	LastKeyID    irma.PublicKeyIdentifier // last used key, used in signing the issuance message
29
	LastCommitID uint64
30
	expiry       time.Time
31
32
33
}

type Server struct {
34
	// configuration
35
36
	conf *Configuration

37
	// external components
38
	core          *keysharecore.Core
39
40
	sessionserver *irmaserver.Server
	db            KeyshareDB
41
42

	// Scheduler used to clean sessions
43
44
	scheduler     *gocron.Scheduler
	stopScheduler chan<- bool
45

46
	// Session data, keeping track of current keyshare protocol session state for each user
47
48
49
50
51
52
53
	sessions    map[string]*SessionData
	sessionLock sync.Mutex
}

func New(conf *Configuration) (*Server, error) {
	var err error
	s := &Server{
54
55
56
		conf:      conf,
		sessions:  map[string]*SessionData{},
		scheduler: gocron.NewScheduler(),
57
58
	}

59
60
	// Setup IRMA session server
	s.sessionserver, err = irmaserver.New(conf.Configuration)
61
62
63
64
	if err != nil {
		return nil, err
	}

65
66
	// Process configuration and create keyshare core
	s.core, err = processConfiguration(conf)
67
68
69
70
	if err != nil {
		return nil, err
	}

71
72
	// Load neccessary idemix keys into core, and ensure that future updates
	// to them are processed
73
74
75
76
77
78
79
80
81
	if err = s.LoadIdemixKeys(conf.IrmaConfiguration); err != nil {
		return nil, err
	}
	conf.IrmaConfiguration.UpdateListeners = append(conf.IrmaConfiguration.UpdateListeners, func(conf *irma.Configuration) {
		if err := s.LoadIdemixKeys(conf); err != nil {
			// run periodically; can only log the error here
			_ = server.LogError(err)
		}
	})
82

83
84
	// Setup DB
	s.db = conf.DB
85

86
87
88
89
	// Setup session cache clearing
	s.scheduler.Every(10).Seconds().Do(s.clearSessions)
	s.stopScheduler = s.scheduler.Start()

90
91
92
	return s, nil
}

93
94
95
96
97
func (s *Server) Stop() {
	s.stopScheduler <- true
	s.sessionserver.Stop()
}

98
// clean up any expired sessions
99
100
101
102
103
104
105
106
107
108
109
func (s *Server) clearSessions() {
	now := time.Now()
	s.sessionLock.Lock()
	defer s.sessionLock.Unlock()
	for k, v := range s.sessions {
		if now.After(v.expiry) {
			delete(s.sessions, k)
		}
	}
}

110
111
func (s *Server) Handler() http.Handler {
	router := chi.NewRouter()
112

113
	router.Group(func(router chi.Router) {
114
115
116
		router.Use(server.SizeLimitMiddleware)
		router.Use(server.TimeoutMiddleware(nil, server.WriteTimeout))

117
118
119
120
121
		if s.conf.Verbose >= 2 {
			opts := server.LogOptions{Response: true, Headers: true, From: false, EncodeBinary: true}
			router.Use(server.LogMiddleware("keyshareserver", opts))
		}

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
		// 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)
		})
137
	})
138

139
	// IRMA server for issuing myirma credential during registration
140
141
142
143
	router.Mount("/irma/", s.sessionserver.HandlerFunc())
	return router
}

144
// On configuration changes, inform the keyshare core of any
145
// new IRMA issuer public keys.
146
147
func (s *Server) LoadIdemixKeys(conf *irma.Configuration) error {
	errs := multierror.Error{}
148
	for _, issuer := range conf.Issuers {
149
		keyIDs, err := conf.PublicKeyIndices(issuer.Identifier())
150
		if err != nil {
151
			errs.Errors = append(errs.Errors, errors.Errorf("issuer %v: could not find key IDs: %v", issuer, err))
152
153
			continue
		}
154
		for _, id := range keyIDs {
155
156
			key, err := conf.PublicKey(issuer.Identifier(), id)
			if err != nil {
157
				errs.Errors = append(errs.Errors, server.LogError(errors.Errorf("key %v-%v: could not fetch public key: %v", issuer, id, err)))
158
159
				continue
			}
160
			s.core.DangerousAddTrustedPublicKey(irma.PublicKeyIdentifier{Issuer: issuer.Identifier(), Counter: id}, key)
161
162
		}
	}
163
	return errs.ErrorOrNil()
164
165
}

166
// /prove/getCommitments
167
func (s *Server) handleCommitments(w http.ResponseWriter, r *http.Request) {
168
	// Fetch from context
169
	user := r.Context().Value("user").(*KeyshareUser)
170
171
	authorization := r.Context().Value("authorization").(string)

172
	// Read keys
173
	var keys []irma.PublicKeyIdentifier
174
	if err := server.ParseBody(w, r, &keys); err != nil {
175
176
177
178
179
180
181
182
183
		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
	}

184
	commitments, err := s.generateCommitments(user, authorization, keys)
185
	if err != nil && (err == keysharecore.ErrInvalidChallenge || err == keysharecore.ErrInvalidJWT) {
186
187
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
188
	}
189
	if err != nil {
190
191
		// already logged
		server.WriteError(w, server.ErrorInternal, err.Error())
192
193
194
		return
	}

195
196
197
	server.WriteJson(w, commitments)
}

198
func (s *Server) generateCommitments(user *KeyshareUser, authorization string, keys []irma.PublicKeyIdentifier) (*irma.ProofPCommitmentMap, error) {
199
	// Generate commitments
200
	commitments, commitID, err := s.core.GenerateCommitments(user.Coredata, authorization, keys)
201
202
	if err != nil {
		s.conf.Logger.WithField("error", err).Warn("Could not generate commitments for request")
203
		return nil, err
204
205
206
	}

	// Prepare output message format
207
	mappedCommitments := map[irma.PublicKeyIdentifier]*gabi.ProofPCommitment{}
208
	for i, keyID := range keys {
209
		mappedCommitments[keyID] = commitments[i]
210
211
212
	}

	// Store needed data for later requests.
213
	username := user.Username
214
	s.sessionLock.Lock()
215
216
217
218
	session, ok := s.sessions[username]
	if !ok {
		session = &SessionData{}
		s.sessions[username] = session
219
	}
220
221
222
223
	// 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.
224
225
226
	session.LastKeyID = keys[0]
	session.LastCommitID = commitID
	session.expiry = time.Now().Add(10 * time.Second)
227
228
229
	s.sessionLock.Unlock()

	// And send response
230
	return &irma.ProofPCommitmentMap{Commitments: mappedCommitments}, nil
231
232
}

233
// /prove/getResponse
234
func (s *Server) handleResponse(w http.ResponseWriter, r *http.Request) {
235
	// Fetch from context
236
	user := r.Context().Value("user").(*KeyshareUser)
237
238
	authorization := r.Context().Value("authorization").(string)

239
240
	// Read challenge
	challenge := new(big.Int)
241
	if err := server.ParseBody(w, r, challenge); err != nil {
242
243
244
245
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}

246
247
248
249
250
	// 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
251
	}
252

253
254
	// Get data from session
	s.sessionLock.Lock()
255
	sessionData, ok := s.sessions[user.Username]
256
257
258
259
	s.sessionLock.Unlock()
	if !ok {
		s.conf.Logger.Warn("Request for response without previous call to get commitments")
		server.WriteError(w, server.ErrorInvalidRequest, "Missing previous call to getCommitments")
260
261
262
		return
	}

263
264
	// And do the actual responding
	proofResponse, err := s.doGenerateResponses(user, authorization, challenge, sessionData.LastCommitID, sessionData.LastKeyID)
265
	if err != nil && (err == keysharecore.ErrInvalidChallenge || err == keysharecore.ErrInvalidJWT) {
266
267
268
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}
269
270
271
272
273
274
275
276
	if err != nil {
		// already logged
		server.WriteError(w, server.ErrorInternal, err.Error())
		return
	}

	server.WriteString(w, proofResponse)
}
277

278
func (s *Server) doGenerateResponses(user *KeyshareUser, authorization string, challenge *big.Int, commitID uint64, keyID irma.PublicKeyIdentifier) (string, error) {
David Venhoek's avatar
David Venhoek committed
279
	// Indicate activity on user account
280
	err := s.db.SetSeen(user)
David Venhoek's avatar
David Venhoek committed
281
282
283
284
285
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not mark user as seen recently")
		// Do not send to user
	}

286
287
288
289
	// Make log entry
	err = s.db.AddLog(user, IrmaSession, nil)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
290
		return "", err
291
292
	}

293
	proofResponse, err := s.core.GenerateResponse(user.Coredata, authorization, commitID, challenge, keyID)
294
295
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not generate response for request")
296
		return "", err
297
298
	}

299
	return proofResponse, nil
300
301
}

302
// /users/isAuthorized
303
func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) {
304
	if r.Context().Value("hasValidAuthorization").(bool) {
305
		server.WriteJson(w, &irma.KeyshareAuthorization{Status: "authorized", Candidates: []string{"pin"}})
306
	} else {
307
		server.WriteJson(w, &irma.KeyshareAuthorization{Status: "expired", Candidates: []string{"pin"}})
308
309
310
	}
}

311
// /users/verify/pin
312
313
func (s *Server) handleVerifyPin(w http.ResponseWriter, r *http.Request) {
	// Extract request
314
	var msg irma.KeysharePinMessage
315
	if err := server.ParseBody(w, r, &msg); err != nil {
316
317
318
319
320
321
322
323
324
325
326
327
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}

	// Fetch user
	user, err := s.db.User(msg.Username)
	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
	}

328
329
	// and verify pin
	result, err := s.doVerifyPin(user, msg.Username, msg.Pin)
330
	if err != nil {
331
		// already logged
332
333
334
		server.WriteError(w, server.ErrorInternal, err.Error())
		return
	}
335
336
337
338

	server.WriteJson(w, result)
}

339
func (s *Server) doVerifyPin(user *KeyshareUser, username, pin string) (irma.KeysharePinStatus, error) {
340
341
	// Check whether pin check is currently allowed
	ok, tries, wait, err := s.reservePinCheck(user, pin)
342
	if err != nil {
343
		return irma.KeysharePinStatus{}, err
344
	}
345
	if !ok {
346
		return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
347
	}
348

349
	// At this point, we are allowed to do an actual check (we have successfully reserved a spot for it), so do it.
350
	jwtt, err := s.core.ValidatePin(user.Coredata, pin, username)
351
352
353
	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")
354
		return irma.KeysharePinStatus{}, err
355
	}
356

357
	if err == keysharecore.ErrInvalidPin {
358
		// Handle invalid pin
359
360
361
		err = s.db.AddLog(user, PinCheckFailed, tries)
		if err != nil {
			s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
362
			return irma.KeysharePinStatus{}, err
363
		}
364
		if tries == 0 {
365
366
367
			err = s.db.AddLog(user, PinCheckBlocked, wait)
			if err != nil {
				s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
368
				return irma.KeysharePinStatus{}, err
369
			}
370
			return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
371
		} else {
372
			return irma.KeysharePinStatus{Status: "failure", Message: fmt.Sprintf("%v", tries)}, nil
373
		}
374
	}
375

376
377
378
379
380
381
382
383
384
385
386
387
388
389
	// Handle success
	err = s.db.ClearPincheck(user)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reset users pin check logic")
		// Do not send to user
	}
	err = s.db.SetSeen(user)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not indicate user activity")
		// Do not send to user
	}
	err = s.db.AddLog(user, PinCheckSuccess, nil)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not add log entry for user")
390
		return irma.KeysharePinStatus{}, err
391
	}
392

393
	return irma.KeysharePinStatus{Status: "success", Message: jwtt}, err
394
395
}

396
// /users/change/pin
397
398
func (s *Server) handleChangePin(w http.ResponseWriter, r *http.Request) {
	// Extract request
399
	var msg irma.KeyshareChangePin
400
	if err := server.ParseBody(w, r, &msg); err != nil {
401
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
402
403
404
405
406
407
408
409
410
411
412
		return
	}

	// Fetch user
	user, err := s.db.User(msg.Username)
	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
	}

413
	result, err := s.doUpdatePin(user, msg.OldPin, msg.NewPin)
414
	if err != nil {
415
		// already logged
416
		server.WriteError(w, server.ErrorInternal, err.Error())
417
418
		return
	}
419
420
421
	server.WriteJson(w, result)
}

422
func (s *Server) doUpdatePin(user *KeyshareUser, oldPin, newPin string) (irma.KeysharePinStatus, error) {
423
	// Check whether pin check is currently allowed
424
	ok, tries, wait, err := s.reservePinCheck(user, oldPin)
425
	if err != nil {
426
		return irma.KeysharePinStatus{}, err
427
	}
428
	if !ok {
429
		return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
430
	}
431
432

	// Try to do the update
433
	user.Coredata, err = s.core.ChangePin(user.Coredata, oldPin, newPin)
434
	if err == keysharecore.ErrInvalidPin {
435
		if tries == 0 {
436
			return irma.KeysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)}, nil
437
		} else {
438
			return irma.KeysharePinStatus{Status: "failure", Message: fmt.Sprintf("%v", tries)}, nil
439
440
441
		}
	} else if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not change pin")
442
		return irma.KeysharePinStatus{}, err
443
	}
444
445

	// Mark pincheck as success, resetting users wait and count
446
447
448
449
450
	err = s.db.ClearPincheck(user)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reset users pin check logic")
		// Do not send to user
	}
451
452
453
454
455

	// Write user back
	err = s.db.UpdateUser(user)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not write updated user to database")
456
		return irma.KeysharePinStatus{}, err
457
458
	}

459
	return irma.KeysharePinStatus{Status: "success"}, nil
460
461
}

462
// /client/register
463
464
func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
	// Extract request
465
	var msg irma.KeyshareEnrollment
466
	if err := server.ParseBody(w, r, &msg); err != nil {
467
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
468
469
470
		return
	}

471
	sessionptr, err := s.doRegistration(msg)
472
	if err != nil && err == keysharecore.ErrPinTooLong {
473
474
475
476
477
478
479
480
481
482
483
484
		// 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)
}

485
func (s *Server) doRegistration(msg irma.KeyshareEnrollment) (*irma.Qr, error) {
486
	// Generate keyshare server account
487
488
489
	username := common.NewSessionToken() // TODO use newRandomString() for this when shoulder-surf is merged
	username = username[:12]

490
491
492
	coredata, err := s.core.GenerateKeyshareSecret(msg.Pin)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not register user")
493
		return nil, err
494
	}
495
496
	user := &KeyshareUser{Username: username, Language: msg.Language, Coredata: coredata}
	err = s.db.NewUser(user)
497
498
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not store new user in database")
499
		return nil, err
500
501
	}

502
	// Send email if user specified email address
503
	if msg.Email != nil && *msg.Email != "" && s.conf.EmailServer != "" {
504
		err = s.sendRegistrationEmail(user, msg.Language, *msg.Email)
505
		if err != nil {
506
507
			// already logged in sendRegistrationEmail
			return nil, err
508
		}
509
510
	}

511
512
513
	// Setup and return issuance session for keyshare credential.
	request := irma.NewIssuanceRequest([]*irma.CredentialRequest{
		{
514
			CredentialTypeID: s.conf.KeyshareAttribute.CredentialTypeIdentifier(),
515
			Attributes: map[string]string{
516
				s.conf.KeyshareAttribute.Name(): username,
517
518
519
520
521
			},
		}})
	sessionptr, _, err := s.sessionserver.StartSession(request, nil)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not start keyshare credential issuance sessions")
522
		return nil, err
523
	}
524
525
526
	return sessionptr, nil
}

527
func (s *Server) sendRegistrationEmail(user *KeyshareUser, language, email string) error {
528
529
530
531
532
533
	// Generate token
	token := common.NewSessionToken()

	// Add it to the database
	err := s.db.AddEmailVerification(user, email, token)
	if err != nil {
534
		s.conf.Logger.WithField("error", err).Error("Could not generate email verification mail record")
535
536
537
		return err
	}

538
539
540
541
542
543
544
545
	verificationBaseURL := s.conf.TranslateString(s.conf.VerificationURL, language)
	return s.conf.SendEmail(
		s.conf.registrationEmailTemplates,
		s.conf.RegistrationEmailSubject,
		map[string]string{"VerificationURL": verificationBaseURL + token},
		[]string{email},
		language,
	)
546
547
}

548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
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
		user, err := s.db.User(username)
		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()
575
		err := s.core.ValidateJWT(ctx.Value("user").(*KeyshareUser).Coredata, authorization)
576
		hasValidAuthorization := err == nil
577
578
579
580
581
582
583
584
585

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

587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
func (s *Server) reservePinCheck(user *KeyshareUser, pin string) (bool, int, int64, error) {
	ok, tries, wait, err := s.db.ReservePincheck(user)
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not reserve pin check slot")
		return false, 0, 0, err
	}
	if !ok {
		err = s.db.AddLog(user, PinCheckRefused, nil)
		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
}