From 6ed9c0c34cd5f98b5769288d36b7c654e7ebb513 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sun, 24 Sep 2023 19:23:49 +0200 Subject: [PATCH] lib/config: Accept pre-hashed password (fixes #9123) (#9124) --- cmd/syncthing/generate/generate.go | 4 ++-- lib/api/api_auth_test.go | 2 +- lib/api/confighandler.go | 4 ++-- lib/config/config_test.go | 14 +++++++++++++- lib/config/guiconfiguration.go | 17 ++++++++++++++--- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go index 6b3d0b369..5446cb3ba 100644 --- a/cmd/syncthing/generate/generate.go +++ b/cmd/syncthing/generate/generate.go @@ -69,7 +69,7 @@ func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFo return err } - if err := syncthing.EnsureDir(dir, 0700); err != nil { + if err := syncthing.EnsureDir(dir, 0o700); err != nil { return err } locations.SetBaseDir(locations.ConfigBaseDir, dir) @@ -127,7 +127,7 @@ func updateGUIAuthentication(l logger.Logger, guiCfg *config.GUIConfiguration, g } if guiPassword != "" && guiCfg.Password != guiPassword { - if err := guiCfg.HashAndSetPassword(guiPassword); err != nil { + if err := guiCfg.SetPassword(guiPassword); err != nil { return fmt.Errorf("failed to set GUI authentication password: %w", err) } l.Infoln("Updated GUI authentication password.") diff --git a/lib/api/api_auth_test.go b/lib/api/api_auth_test.go index be6a0c3a8..ed3677eeb 100644 --- a/lib/api/api_auth_test.go +++ b/lib/api/api_auth_test.go @@ -16,7 +16,7 @@ var guiCfg config.GUIConfiguration func init() { guiCfg.User = "user" - guiCfg.HashAndSetPassword("pass") + guiCfg.SetPassword("pass") } func TestStaticAuthOK(t *testing.T) { diff --git a/lib/api/confighandler.go b/lib/api/confighandler.go index 5abc1f055..fc3e73a59 100644 --- a/lib/api/confighandler.go +++ b/lib/api/confighandler.go @@ -319,7 +319,7 @@ func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request) var status int waiter, err := c.cfg.Modify(func(cfg *config.Configuration) { if to.GUI.Password != cfg.GUI.Password { - if err := to.GUI.HashAndSetPassword(to.GUI.Password); err != nil { + if err := to.GUI.SetPassword(to.GUI.Password); err != nil { l.Warnln("hashing password:", err) errMsg = err.Error() status = http.StatusInternalServerError @@ -401,7 +401,7 @@ func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui var status int waiter, err := c.cfg.Modify(func(cfg *config.Configuration) { if gui.Password != oldPassword { - if err := gui.HashAndSetPassword(gui.Password); err != nil { + if err := gui.SetPassword(gui.Password); err != nil { l.Warnln("hashing password:", err) errMsg = err.Error() status = http.StatusInternalServerError diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 241cc2b59..d90e01fb9 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/d4l3k/messagediff" + "golang.org/x/crypto/bcrypt" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/events" @@ -773,8 +774,9 @@ func TestGUIConfigURL(t *testing.T) { func TestGUIPasswordHash(t *testing.T) { var c GUIConfiguration + // Setting a plaintext password should work testPass := "pass" - if err := c.HashAndSetPassword(testPass); err != nil { + if err := c.SetPassword(testPass); err != nil { t.Fatal(err) } if c.Password == testPass { @@ -789,6 +791,16 @@ func TestGUIPasswordHash(t *testing.T) { if err := c.CompareHashedPassword(failPass); err == nil { t.Errorf("Match on different password: %v", err) } + + // Setting a bcrypt hash directly should also work + hash, err := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.MinCost) + if err != nil { + t.Fatal(err) + } + c.SetPassword(string(hash)) + if err := c.CompareHashedPassword("test"); err != nil { + t.Errorf("No match on hashed password: %v", err) + } } func TestDuplicateDevices(t *testing.T) { diff --git a/lib/config/guiconfiguration.go b/lib/config/guiconfiguration.go index cbedfffcb..9f5df227f 100644 --- a/lib/config/guiconfiguration.go +++ b/lib/config/guiconfiguration.go @@ -9,6 +9,7 @@ package config import ( "net/url" "os" + "regexp" "strconv" "strings" @@ -115,9 +116,19 @@ func (c GUIConfiguration) URL() string { return u.String() } -// SetHashedPassword hashes the given plaintext password and stores the new hash. -func (c *GUIConfiguration) HashAndSetPassword(password string) error { - hash, err := bcrypt.GenerateFromPassword([]byte(password), 0) +// matches a bcrypt hash and not too much else +var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`) + +// SetPassword takes a bcrypt hash or a plaintext password and stores it. +// Plaintext passwords are hashed. Returns an error if the password is not +// valid. +func (c *GUIConfiguration) SetPassword(password string) error { + if bcryptExpr.MatchString(password) { + // Already hashed + c.Password = password + return nil + } + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err }