server.go 18.4 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
114

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

118
	// Registration
119
	router.Post("/client/register", s.handleRegister)
120

121
	// Pin logic
122
123
	router.Post("/users/verify/pin", s.handleVerifyPin)
	router.Post("/users/change/pin", s.handleChangePin)
124
125

	// Keyshare sessions
126
127
128
129
130
131
132
	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)
	})
133

134
	// IRMA server for issuing myirma credential during registration
135
136
137
138
	router.Mount("/irma/", s.sessionserver.HandlerFunc())
	return router
}

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

161
// /prove/getCommitments
162
func (s *Server) handleCommitments(w http.ResponseWriter, r *http.Request) {
163
	// Fetch from context
164
	user := r.Context().Value("user").(*KeyshareUser)
165
166
	authorization := r.Context().Value("authorization").(string)

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

179
	commitments, err := s.generateCommitments(user, authorization, keys)
180
	if err != nil && (err == keysharecore.ErrInvalidChallenge || err == keysharecore.ErrInvalidJWT) {
181
182
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
183
	}
184
	if err != nil {
185
186
		// already logged
		server.WriteError(w, server.ErrorInternal, err.Error())
187
188
189
		return
	}

190
191
192
	server.WriteJson(w, commitments)
}

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

	// Prepare output message format
202
	mappedCommitments := map[irma.PublicKeyIdentifier]*gabi.ProofPCommitment{}
203
	for i, keyID := range keys {
204
		mappedCommitments[keyID] = commitments[i]
205
206
207
	}

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

	// And send response
225
	return &irma.ProofPCommitmentMap{Commitments: mappedCommitments}, nil
226
227
}

228
// /prove/getResponse
229
func (s *Server) handleResponse(w http.ResponseWriter, r *http.Request) {
230
	// Fetch from context
231
	user := r.Context().Value("user").(*KeyshareUser)
232
233
	authorization := r.Context().Value("authorization").(string)

234
235
	// Read challenge
	challenge := new(big.Int)
236
	if err := server.ParseBody(w, r, challenge); err != nil {
237
238
239
240
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
		return
	}

241
242
243
244
245
	// 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
246
	}
247

248
249
	// Get data from session
	s.sessionLock.Lock()
250
	sessionData, ok := s.sessions[user.Username]
251
252
253
254
	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")
255
256
257
		return
	}

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

	server.WriteString(w, proofResponse)
}
272

273
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
274
	// Indicate activity on user account
275
	err := s.db.SetSeen(user)
David Venhoek's avatar
David Venhoek committed
276
277
278
279
280
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not mark user as seen recently")
		// Do not send to user
	}

281
282
283
284
	// 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")
285
		return "", err
286
287
	}

288
	proofResponse, err := s.core.GenerateResponse(user.Coredata, authorization, commitID, challenge, keyID)
289
290
	if err != nil {
		s.conf.Logger.WithField("error", err).Error("Could not generate response for request")
291
		return "", err
292
293
	}

294
	return proofResponse, nil
295
296
}

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

306
// /users/verify/pin
307
308
func (s *Server) handleVerifyPin(w http.ResponseWriter, r *http.Request) {
	// Extract request
309
	var msg irma.KeysharePinMessage
310
	if err := server.ParseBody(w, r, &msg); err != nil {
311
312
313
314
315
316
317
318
319
320
321
322
		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
	}

323
324
	// and verify pin
	result, err := s.doVerifyPin(user, msg.Username, msg.Pin)
325
	if err != nil {
326
		// already logged
327
328
329
		server.WriteError(w, server.ErrorInternal, err.Error())
		return
	}
330
331
332
333

	server.WriteJson(w, result)
}

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

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

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

371
372
373
374
375
376
377
378
379
380
381
382
383
384
	// 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")
385
		return irma.KeysharePinStatus{}, err
386
	}
387

388
	return irma.KeysharePinStatus{Status: "success", Message: jwtt}, err
389
390
}

391
// /users/change/pin
392
393
func (s *Server) handleChangePin(w http.ResponseWriter, r *http.Request) {
	// Extract request
394
	var msg irma.KeyshareChangePin
395
	if err := server.ParseBody(w, r, &msg); err != nil {
396
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
397
398
399
400
401
402
403
404
405
406
407
		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
	}

408
	result, err := s.doUpdatePin(user, msg.OldPin, msg.NewPin)
409
	if err != nil {
410
		// already logged
411
		server.WriteError(w, server.ErrorInternal, err.Error())
412
413
		return
	}
414
415
416
	server.WriteJson(w, result)
}

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

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

	// Mark pincheck as success, resetting users wait and count
441
442
443
444
445
	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
	}
446
447
448
449
450

	// 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")
451
		return irma.KeysharePinStatus{}, err
452
453
	}

454
	return irma.KeysharePinStatus{Status: "success"}, nil
455
456
}

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

466
	sessionptr, err := s.doRegistration(msg)
467
	if err != nil && err == keysharecore.ErrPinTooLong {
468
469
470
471
472
473
474
475
476
477
478
479
		// 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)
}

480
func (s *Server) doRegistration(msg irma.KeyshareEnrollment) (*irma.Qr, error) {
481
	// Generate keyshare server account
482
483
484
	username := common.NewSessionToken() // TODO use newRandomString() for this when shoulder-surf is merged
	username = username[:12]

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

497
	// Send email if user specified email address
498
	if msg.Email != nil && *msg.Email != "" && s.conf.EmailServer != "" {
499
		err = s.sendRegistrationEmail(user, msg.Language, *msg.Email)
500
		if err != nil {
501
502
			// already logged in sendRegistrationEmail
			return nil, err
503
		}
504
505
	}

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

522
func (s *Server) sendRegistrationEmail(user *KeyshareUser, language, email string) error {
523
524
525
526
527
528
	// Generate token
	token := common.NewSessionToken()

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

533
534
535
536
537
538
539
540
	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,
	)
541
542
}

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

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

582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
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
}