Commit e516be97 authored by David Venhoek's avatar David Venhoek Committed by Sietse Ringers
Browse files

Initial version of postgres database connection for keyshare server.

parent 35e1aa5a
......@@ -16,6 +16,15 @@ import (
"github.com/sirupsen/logrus"
)
type DatabaseType string
var ErrUnknownDatabaseType = errors.New("Unknown database type")
const (
DatabaseTypeMemory = "memory"
DatabaseTypePostgres = "postgres"
)
// Configuration contains configuration for the irmaserver library and irmad.
type Configuration struct {
// Irma server configuration. If not given, this will be populated using information here
......@@ -40,6 +49,12 @@ type Configuration struct {
// ensure (using eg a reverse proxy with TLS enabled) that the attributes are protected in transit.
DisableTLS bool `json:"no_tls" mapstructure:"no_tls"`
// Database configuration (ignored when database is provided)
DbType DatabaseType `json:"db_type" mapstructure:"db_type"`
DbConnstring string `json:"db_connstring" mapstructure:"db_connstring"`
// Provide a prepared database (useful for testing)
DB KeyshareDB `json:"-"`
// Configuration of secure Core
// Private key used to sign JWTs with
JwtKeyId int `json:"jwt_key_id" mapstructure:"jwt_key_id"`
......@@ -140,6 +155,22 @@ func processConfiguration(conf *Configuration) (*keyshareCore.KeyshareCore, erro
}
}
// Setup database
if conf.DB == nil {
switch conf.DbType {
case DatabaseTypeMemory:
conf.DB = NewMemoryDatabase()
case DatabaseTypePostgres:
var err error
conf.DB, err = NewPostgresDatabase(conf.DbConnstring)
if err != nil {
return nil, server.LogError(err)
}
default:
return nil, server.LogError(ErrUnknownDatabaseType)
}
}
// Setup server urls
if !strings.HasSuffix(conf.URL, "/") {
conf.URL = conf.URL + "/"
......
package keyshareServerCore
import (
"database/sql"
"encoding/base64"
"errors"
"sync"
"time"
"github.com/privacybydesign/irmago/keyshareCore"
_ "github.com/jackc/pgx/stdlib"
)
var (
ErrUserAlreadyExists = errors.New("Cannot create user, username already taken")
ErrUserNotFound = errors.New("Could not find specified user")
ErrInvalidData = errors.New("Invalid user datastructure passed")
)
type KeyshareDB interface {
NewUser(user *KeyshareUser) error
User(username string) (*KeyshareUser, error)
UpdateUser(user *KeyshareUser) error
NewUser(user KeyshareUserData) error
User(username string) (KeyshareUser, error)
UpdateUser(user KeyshareUser) error
// Reserve returns (allow, tries, wait, error)
ReservePincheck(user *KeyshareUser) (bool, int, int, error)
ClearPincheck(user *KeyshareUser) error
ReservePincheck(user KeyshareUser) (bool, int, int64, error)
ClearPincheck(user KeyshareUser) error
}
type KeyshareUser interface {
Data() *KeyshareUserData
}
type KeyshareUser struct {
type KeyshareUserData struct {
Username string
Coredata keyshareCore.EncryptedKeysharePacket
}
......@@ -32,11 +42,19 @@ type keyshareMemoryDB struct {
users map[string]keyshareCore.EncryptedKeysharePacket
}
type keyshareMemoryUser struct {
KeyshareUserData
}
func (m *keyshareMemoryUser) Data() *KeyshareUserData {
return &m.KeyshareUserData
}
func NewMemoryDatabase() KeyshareDB {
return &keyshareMemoryDB{users: map[string]keyshareCore.EncryptedKeysharePacket{}}
}
func (db *keyshareMemoryDB) User(username string) (*KeyshareUser, error) {
func (db *keyshareMemoryDB) User(username string) (KeyshareUser, error) {
// Ensure access to database is single-threaded
db.lock.Lock()
defer db.lock.Unlock()
......@@ -46,10 +64,10 @@ func (db *keyshareMemoryDB) User(username string) (*KeyshareUser, error) {
if !ok {
return nil, ErrUserNotFound
}
return &KeyshareUser{Username: username, Coredata: data}, nil
return &keyshareMemoryUser{KeyshareUserData{Username: username, Coredata: data}}, nil
}
func (db *keyshareMemoryDB) NewUser(user *KeyshareUser) error {
func (db *keyshareMemoryDB) NewUser(user KeyshareUserData) error {
// Ensure access to database is single-threaded
db.lock.Lock()
defer db.lock.Unlock()
......@@ -63,26 +81,198 @@ func (db *keyshareMemoryDB) NewUser(user *KeyshareUser) error {
return nil
}
func (db *keyshareMemoryDB) UpdateUser(user *KeyshareUser) error {
func (db *keyshareMemoryDB) UpdateUser(user KeyshareUser) error {
userdata, ok := user.(*keyshareMemoryUser)
if !ok {
return ErrInvalidData
}
// Ensure access to database is single-threaded
db.lock.Lock()
defer db.lock.Unlock()
// Check and update user.
_, exists := db.users[user.Username]
_, exists := db.users[userdata.Username]
if !exists {
return ErrUserNotFound
}
db.users[user.Username] = user.Coredata
db.users[userdata.Username] = userdata.Coredata
return nil
}
func (db *keyshareMemoryDB) ReservePincheck(user *KeyshareUser) (bool, int, int, error) {
func (db *keyshareMemoryDB) ReservePincheck(user KeyshareUser) (bool, int, int64, error) {
// Since this is a testing DB, implementing anything more than always allow creates hastle
return false, 1, 0, nil
}
func (db *keyshareMemoryDB) ClearPincheck(user *KeyshareUser) error {
func (db *keyshareMemoryDB) ClearPincheck(user KeyshareUser) error {
// Since this is a testing DB, implementing anything more than always allow creates hastle
return nil
}
type keysharePostgresDatabase struct {
db *sql.DB
}
type keysharePostgresUser struct {
KeyshareUserData
id int
}
func (m *keysharePostgresUser) Data() *KeyshareUserData {
return &m.KeyshareUserData
}
const MAX_PIN_TRIES = 3
const BACKOFF_START = 30
func NewPostgresDatabase(connstring string) (KeyshareDB, error) {
db, err := sql.Open("pgx", connstring)
if err != nil {
return nil, err
}
return &keysharePostgresDatabase{
db: db,
}, nil
}
func (db *keysharePostgresDatabase) NewUser(user KeyshareUserData) error {
ep := base64.StdEncoding.EncodeToString(user.Coredata[:])
res, err := db.db.Exec("INSERT INTO irma.users (username, coredata) VALUES (?, ?)", user.Username, ep)
if err != nil {
return err
}
c, err := res.RowsAffected()
if err != nil {
return err
}
if c == 0 {
return ErrUserAlreadyExists
}
return nil
}
func (db *keysharePostgresDatabase) User(username string) (KeyshareUser, error) {
rows, err := db.db.Query("SELECT id, username, coredata FROM irma.users WHERE username = ?", username)
if err != nil {
return nil, err
}
defer rows.Close()
if !rows.Next() {
return nil, ErrUserNotFound
}
var result keysharePostgresUser
var epEnc string
err = rows.Scan(&result.id, &result.Username, epEnc)
if err != nil {
return nil, err
}
ep, err := base64.StdEncoding.DecodeString(epEnc)
if err != nil {
return nil, err
}
if len(ep) != len(result.Coredata[:]) {
return nil, ErrInvalidData
}
copy(result.Coredata[:], ep)
return &result, nil
}
func (db *keysharePostgresDatabase) UpdateUser(user KeyshareUser) error {
userdata, ok := user.(*keysharePostgresUser)
if !ok {
return ErrInvalidData
}
ep := base64.StdEncoding.EncodeToString(userdata.Coredata[:])
res, err := db.db.Exec("UPDATE irma.users SET username=?, coredata=? WHERE id=?", userdata.Username, ep, userdata.id)
if err != nil {
return err
}
c, err := res.RowsAffected()
if err != nil {
return err
}
if c == 0 {
return ErrUserNotFound
}
return nil
}
func (db *keysharePostgresDatabase) ReservePincheck(user KeyshareUser) (bool, int, int64, error) {
// Extract data
userdata, ok := user.(*keysharePostgresUser)
if !ok {
return false, 0, 0, ErrInvalidData
}
// Check that account is not blocked already, and if not,
// update pinCounter and pinBlockDate
uprows, err := db.db.Query(`
UPDATE irma.users
SET pinCounter = pinCounter+1,
pinBlockDate = ?+?*2^MIN(0, pinCounter-?)
WHERE id=?, pinBlockDate<=?
RETURNING pinCounter, pinBlockDate`,
time.Now().Unix()/1000000000-1-BACKOFF_START, // Grace time of 2 seconds on pinBlockDate set
BACKOFF_START,
MAX_PIN_TRIES-2,
userdata.id,
time.Now().Unix()/1000000000)
if err != nil {
return false, 0, 0, err
}
defer uprows.Close()
// Check whether we have results
if !uprows.Next() {
// if no, then account either does not exist (which would be weird here) or is blocked
// so request wait timeout
pinrows, err := db.db.Query("SELECT pinBlockDate FROM irma.users WHERE id=?", userdata.id)
if err != nil {
return false, 0, 0, err
}
defer pinrows.Close()
if !pinrows.Next() {
return false, 0, 0, ErrUserNotFound
}
var wait int64
err = pinrows.Scan(&wait)
if err != nil {
return false, 0, 0, err
}
return false, 0, wait - time.Now().Unix()/1000000000, nil
}
// Pin check is allowed (implied since there is a result, so pinBlockDate <= now)
// calculate tries remaining and wait time
var tries int
var wait int64
err = uprows.Scan(&tries, &wait)
if err != nil {
return false, 0, 0, err
}
tries = MAX_PIN_TRIES - tries
if tries < 0 {
tries = 0
}
return true, tries, wait - time.Now().Unix()/1000000000, nil
}
func (db *keysharePostgresDatabase) ClearPincheck(user KeyshareUser) error {
userdata, ok := user.(*keysharePostgresUser)
if !ok {
return ErrInvalidData
}
res, err := db.db.Exec("UPDATE irma.users SET pinCounter=0, pinBlockDate=0 WHERE id=?", userdata.id)
if err != nil {
return err
}
c, err := res.RowsAffected()
if err != nil {
return err
}
if c == 0 {
return ErrUserNotFound
}
return nil
}
......@@ -65,8 +65,8 @@ func New(conf *Configuration) (*Server, error) {
return nil, err
}
// Setup DB (TODO: make configurable)
s.db = NewMemoryDatabase()
// Setup DB
s.db = conf.DB
return s, nil
}
......@@ -138,7 +138,7 @@ func (s *Server) handleCommitments(w http.ResponseWriter, r *http.Request) {
}
// Generate commitments
commitments, commitId, err := s.core.GenerateCommitments(user.Coredata, authorization, keys)
commitments, commitId, err := s.core.GenerateCommitments(user.Data().Coredata, authorization, keys)
if err != nil {
s.conf.Logger.WithField("error", err).Warn("Could not generate commitments for request")
server.WriteError(w, server.ErrorInvalidRequest, err.Error())
......@@ -199,7 +199,7 @@ func (s *Server) handleResponse(w http.ResponseWriter, r *http.Request) {
}
// verify access (avoids leaking information to unauthorized callers)
err = s.core.ValidateJWT(user.Coredata, authorization)
err = s.core.ValidateJWT(user.Data().Coredata, authorization)
if err != nil {
s.conf.Logger.WithField("error", err).Warn("Could not generate keyshare response")
server.WriteError(w, server.ErrorInvalidRequest, err.Error())
......@@ -216,7 +216,7 @@ func (s *Server) handleResponse(w http.ResponseWriter, r *http.Request) {
return
}
proofResponse, err := s.core.GenerateResponse(user.Coredata, authorization, sessionData.LastCommitID, challenge, sessionData.LastKeyid)
proofResponse, err := s.core.GenerateResponse(user.Data().Coredata, authorization, sessionData.LastCommitID, challenge, sessionData.LastKeyid)
if err != nil {
s.conf.Logger.WithField("error", err).Error("Could not generate response for request")
server.WriteError(w, server.ErrorInvalidRequest, err.Error())
......@@ -240,7 +240,7 @@ func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) {
}
// Validate jwt
err = s.core.ValidateJWT(user.Coredata, authorization)
err = s.core.ValidateJWT(user.Data().Coredata, authorization)
if err != nil {
server.WriteJson(w, &keyshareAuthorization{Status: "expired", Candidates: []string{"pin"}})
} else {
......@@ -283,7 +283,7 @@ func (s *Server) handleVerifyPin(w http.ResponseWriter, r *http.Request) {
server.WriteJson(w, keysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)})
return
}
jwtt, err := s.core.ValidatePin(user.Coredata, msg.Pin, msg.Username)
jwtt, err := s.core.ValidatePin(user.Data().Coredata, msg.Pin, msg.Username)
if err == keyshareCore.ErrInvalidPin {
if tries == 0 {
server.WriteJson(w, keysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)})
......@@ -338,7 +338,7 @@ func (s *Server) handleChangePin(w http.ResponseWriter, r *http.Request) {
server.WriteJson(w, keysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)})
return
}
user.Coredata, err = s.core.ChangePin(user.Coredata, msg.OldPin, msg.NewPin)
user.Data().Coredata, err = s.core.ChangePin(user.Data().Coredata, msg.OldPin, msg.NewPin)
if err == keyshareCore.ErrInvalidPin {
if tries == 0 {
server.WriteJson(w, keysharePinStatus{Status: "error", Message: fmt.Sprintf("%v", wait)})
......@@ -393,7 +393,7 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
server.WriteError(w, server.ErrorInvalidRequest, err.Error())
return
}
err = s.db.NewUser(&KeyshareUser{Username: username, Coredata: coredata})
err = s.db.NewUser(KeyshareUserData{Username: username, Coredata: coredata})
if err != nil {
s.conf.Logger.WithField("error", err).Error("Could not store new user in database")
server.WriteError(w, server.ErrorInternal, err.Error())
......
......@@ -10,6 +10,8 @@ func main() {
s, err := keyshareServerCore.New(&keyshareServerCore.Configuration{
SchemesPath: "schemes/",
URL: "http://10.0.2.2:8080/",
DbType: keyshareServerCore.DatabaseTypePostgres,
DbConnstring: "postgresql://localhost:5432/test",
JwtKeyId: 0,
JwtPrivateKeyFile: "schemes/test/kss-0.private.pem",
StoragePrimaryKeyFile: "storagekey",
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment