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

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

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

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

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

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

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

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

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

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

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

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

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

72
73
	// Load neccessary idemix keys into core, and ensure that future updates
	// to them are processed
74
75
76
77
78
79
80
81
82
	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)
		}
	})
83

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

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

91
92
93
	return s, nil
}

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

99
// clean up any expired sessions
100
101
102
103
104
105
106
107
108
109
110
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)
		}
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	server.WriteString(w, proofResponse)
}
273

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

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

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

295
	return proofResponse, nil
296
297
}

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

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

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

	server.WriteJson(w, result)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

523
func (s *Server) sendRegistrationEmail(user *KeyshareUser, language, email string) error {
524
	// Fetch template and configuration data for users language, falling back if needed
525
526
527
	template := s.conf.TranslateTemplate(s.conf.registrationEmailTemplates, language)
	verificationBaseURL := s.conf.TranslateString(s.conf.VerificationURL, language)
	subject := s.conf.TranslateString(s.conf.RegistrationEmailSubject, language)
528
529
530
531
532
533
534

	// Generate token
	token := common.NewSessionToken()

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

	// Build message
	var msg bytes.Buffer
	err = template.Execute(&msg, map[string]string{"VerificationURL": verificationBaseURL + token})
	if err != nil {
543
		s.conf.Logger.WithField("error", err).Error("Could not generate email verification mail")
544
545
546
547
548
549
550
551
552
553
554
555
556
		return err
	}

	// And send it
	err = server.SendHTMLMail(
		s.conf.EmailServer,
		s.conf.EmailAuth,
		s.conf.EmailFrom,
		email,
		subject,
		msg.Bytes())

	if err != nil {
557
		s.conf.Logger.WithField("error", err).Error("Could not send email verification mail")
558
559
560
561
		return err
	}

	return nil
562
563
}

564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
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()
591
		err := s.core.ValidateJWT(ctx.Value("user").(*KeyshareUser).Coredata, authorization)
592
		hasValidAuthorization := err == nil
593
594
595
596
597
598
599
600
601

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

603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
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
}