diff --git a/cmd/create-account/main.go b/cmd/create-account/main.go index bba2d55d6..22732c518 100644 --- a/cmd/create-account/main.go +++ b/cmd/create-account/main.go @@ -24,6 +24,7 @@ import ( "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" ) const usage = `Usage: %s @@ -57,7 +58,7 @@ func main() { accountDB, err := accounts.NewDatabase(&config.DatabaseOptions{ ConnectionString: cfg.UserAPI.AccountDatabase.ConnectionString, - }, cfg.Global.ServerName) + }, cfg.Global.ServerName, bcrypt.DefaultCost) if err != nil { logrus.Fatalln("Failed to connect to the database:", err.Error()) } diff --git a/cmd/generate-config/main.go b/cmd/generate-config/main.go index 9ef0f0b41..bd70cbc5d 100644 --- a/cmd/generate-config/main.go +++ b/cmd/generate-config/main.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/matrix-org/dendrite/setup/config" + "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" ) @@ -68,6 +69,7 @@ func main() { cfg.Logging[0].Level = "trace" // don't hit matrix.org when running tests!!! cfg.SigningKeyServer.KeyPerspectives = config.KeyPerspectives{} + cfg.UserAPI.BCryptCost = bcrypt.MinCost } j, err := yaml.Marshal(cfg) diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 22c7b902f..13564590d 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -340,6 +340,13 @@ sync_api: # Configuration for the User API. user_api: + # The cost when hashing passwords on registration/login. Default: 10. Min: 4, Max: 31 + # See https://pkg.go.dev/golang.org/x/crypto/bcrypt for more information. + # Setting this lower makes registration/login consume less CPU resources at the cost of security + # should the database be compromised. Setting this higher makes registration/login consume more + # CPU resources but makes it harder to brute force password hashes. + # This value can be low if performing tests or on embedded Dendrite instances (e.g WASM builds) + # bcrypt_cost: 10 internal_api: listen: http://localhost:7781 connect: http://localhost:7781 diff --git a/setup/base.go b/setup/base.go index e9aa2a45e..4c50a6be6 100644 --- a/setup/base.go +++ b/setup/base.go @@ -263,7 +263,7 @@ func (b *BaseDendrite) KeyServerHTTPClient() keyserverAPI.KeyInternalAPI { // CreateAccountsDB creates a new instance of the accounts database. Should only // be called once per component. func (b *BaseDendrite) CreateAccountsDB() accounts.Database { - db, err := accounts.NewDatabase(&b.Cfg.UserAPI.AccountDatabase, b.Cfg.Global.ServerName) + db, err := accounts.NewDatabase(&b.Cfg.UserAPI.AccountDatabase, b.Cfg.Global.ServerName, b.Cfg.UserAPI.BCryptCost) if err != nil { logrus.WithError(err).Panicf("failed to connect to accounts db") } diff --git a/setup/config/config_userapi.go b/setup/config/config_userapi.go index 91b351d1b..e69123842 100644 --- a/setup/config/config_userapi.go +++ b/setup/config/config_userapi.go @@ -1,10 +1,15 @@ package config +import "golang.org/x/crypto/bcrypt" + type UserAPI struct { Matrix *Global `yaml:"-"` InternalAPI InternalAPIOptions `yaml:"internal_api"` + // The cost when hashing passwords. + BCryptCost int `yaml:"bcrypt_cost"` + // The Account database stores the login details and account information // for local users. It is accessed by the UserAPI. AccountDatabase DatabaseOptions `yaml:"account_database"` @@ -20,6 +25,7 @@ func (c *UserAPI) Defaults() { c.DeviceDatabase.Defaults(10) c.AccountDatabase.ConnectionString = "file:userapi_accounts.db" c.DeviceDatabase.ConnectionString = "file:userapi_devices.db" + c.BCryptCost = bcrypt.DefaultCost } func (c *UserAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index e6adbfd83..3933fe5bd 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -44,10 +44,11 @@ type Database struct { accountDatas accountDataStatements threepids threepidStatements serverName gomatrixserverlib.ServerName + bcryptCost int } // NewDatabase creates a new accounts and profiles database -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName) (*Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) { db, err := sqlutil.Open(dbProperties) if err != nil { return nil, err @@ -56,6 +57,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver serverName: serverName, db: db, writer: sqlutil.NewDummyWriter(), + bcryptCost: bcryptCost, } // Create tables before executing migrations so we don't fail if the table is missing, @@ -131,7 +133,7 @@ func (d *Database) SetDisplayName( func (d *Database) SetPassword( ctx context.Context, localpart, plaintextPassword string, ) error { - hash, err := hashPassword(plaintextPassword) + hash, err := d.hashPassword(plaintextPassword) if err != nil { return err } @@ -175,7 +177,7 @@ func (d *Database) createAccount( // Generate a password hash if this is not a password-less user hash := "" if plaintextPassword != "" { - hash, err = hashPassword(plaintextPassword) + hash, err = d.hashPassword(plaintextPassword) if err != nil { return nil, err } @@ -246,8 +248,8 @@ func (d *Database) GetNewNumericLocalpart( return d.accounts.selectNewNumericLocalpart(ctx, nil) } -func hashPassword(plaintext string) (hash string, err error) { - hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost) +func (d *Database) hashPassword(plaintext string) (hash string, err error) { + hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), d.bcryptCost) return string(hashBytes), err } diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index 747be34f6..07cc68b35 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -42,6 +42,7 @@ type Database struct { accountDatas accountDataStatements threepids threepidStatements serverName gomatrixserverlib.ServerName + bcryptCost int accountsMu sync.Mutex profilesMu sync.Mutex @@ -50,7 +51,7 @@ type Database struct { } // NewDatabase creates a new accounts and profiles database -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName) (*Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) { db, err := sqlutil.Open(dbProperties) if err != nil { return nil, err @@ -59,6 +60,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver serverName: serverName, db: db, writer: sqlutil.NewExclusiveWriter(), + bcryptCost: bcryptCost, } // Create tables before executing migrations so we don't fail if the table is missing, @@ -143,7 +145,7 @@ func (d *Database) SetDisplayName( func (d *Database) SetPassword( ctx context.Context, localpart, plaintextPassword string, ) error { - hash, err := hashPassword(plaintextPassword) + hash, err := d.hashPassword(plaintextPassword) if err != nil { return err } @@ -208,7 +210,7 @@ func (d *Database) createAccount( // Generate a password hash if this is not a password-less user hash := "" if plaintextPassword != "" { - hash, err = hashPassword(plaintextPassword) + hash, err = d.hashPassword(plaintextPassword) if err != nil { return nil, err } @@ -278,8 +280,8 @@ func (d *Database) GetNewNumericLocalpart( return d.accounts.selectNewNumericLocalpart(ctx, nil) } -func hashPassword(plaintext string) (hash string, err error) { - hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost) +func (d *Database) hashPassword(plaintext string) (hash string, err error) { + hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), d.bcryptCost) return string(hashBytes), err } diff --git a/userapi/storage/accounts/storage.go b/userapi/storage/accounts/storage.go index 3f69e95f6..28c437daa 100644 --- a/userapi/storage/accounts/storage.go +++ b/userapi/storage/accounts/storage.go @@ -27,12 +27,12 @@ import ( // NewDatabase opens a new Postgres or Sqlite database (based on dataSourceName scheme) // and sets postgres connection parameters -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName) (Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (Database, error) { switch { case dbProperties.ConnectionString.IsSQLite(): - return sqlite3.NewDatabase(dbProperties, serverName) + return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost) case dbProperties.ConnectionString.IsPostgres(): - return postgres.NewDatabase(dbProperties, serverName) + return postgres.NewDatabase(dbProperties, serverName, bcryptCost) default: return nil, fmt.Errorf("unexpected database type") } diff --git a/userapi/storage/accounts/storage_wasm.go b/userapi/storage/accounts/storage_wasm.go index dcaf371a1..8038326fe 100644 --- a/userapi/storage/accounts/storage_wasm.go +++ b/userapi/storage/accounts/storage_wasm.go @@ -25,10 +25,11 @@ import ( func NewDatabase( dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, + bcryptCost int, ) (Database, error) { switch { case dbProperties.ConnectionString.IsSQLite(): - return sqlite3.NewDatabase(dbProperties, serverName) + return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost) case dbProperties.ConnectionString.IsPostgres(): return nil, fmt.Errorf("can't use Postgres implementation") default: diff --git a/userapi/userapi.go b/userapi/userapi.go index b8b826bc8..74702020a 100644 --- a/userapi/userapi.go +++ b/userapi/userapi.go @@ -37,7 +37,6 @@ func AddInternalRoutes(router *mux.Router, intAPI api.UserInternalAPI) { func NewInternalAPI( accountDB accounts.Database, cfg *config.UserAPI, appServices []config.ApplicationService, keyAPI keyapi.KeyInternalAPI, ) api.UserInternalAPI { - deviceDB, err := devices.NewDatabase(&cfg.DeviceDatabase, cfg.Matrix.ServerName) if err != nil { logrus.WithError(err).Panicf("failed to connect to device db") diff --git a/userapi/userapi_test.go b/userapi/userapi_test.go index 25c262ad1..9a45a2dc8 100644 --- a/userapi/userapi_test.go +++ b/userapi/userapi_test.go @@ -16,6 +16,7 @@ import ( "github.com/matrix-org/dendrite/userapi/inthttp" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/matrix-org/gomatrixserverlib" + "golang.org/x/crypto/bcrypt" ) const ( @@ -25,7 +26,7 @@ const ( func MustMakeInternalAPI(t *testing.T) (api.UserInternalAPI, accounts.Database) { accountDB, err := accounts.NewDatabase(&config.DatabaseOptions{ ConnectionString: "file::memory:", - }, serverName) + }, serverName, bcrypt.MinCost) if err != nil { t.Fatalf("failed to create account DB: %s", err) }