lib/config: Accept pre-hashed password (fixes #9123) (#9124)

This commit is contained in:
Jakob Borg 2023-09-24 19:23:49 +02:00 committed by GitHub
parent 19bbf4f6bf
commit 6ed9c0c34c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 32 additions and 9 deletions

View File

@ -69,7 +69,7 @@ func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFo
return err return err
} }
if err := syncthing.EnsureDir(dir, 0700); err != nil { if err := syncthing.EnsureDir(dir, 0o700); err != nil {
return err return err
} }
locations.SetBaseDir(locations.ConfigBaseDir, dir) locations.SetBaseDir(locations.ConfigBaseDir, dir)
@ -127,7 +127,7 @@ func updateGUIAuthentication(l logger.Logger, guiCfg *config.GUIConfiguration, g
} }
if guiPassword != "" && guiCfg.Password != guiPassword { 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) return fmt.Errorf("failed to set GUI authentication password: %w", err)
} }
l.Infoln("Updated GUI authentication password.") l.Infoln("Updated GUI authentication password.")

View File

@ -16,7 +16,7 @@ var guiCfg config.GUIConfiguration
func init() { func init() {
guiCfg.User = "user" guiCfg.User = "user"
guiCfg.HashAndSetPassword("pass") guiCfg.SetPassword("pass")
} }
func TestStaticAuthOK(t *testing.T) { func TestStaticAuthOK(t *testing.T) {

View File

@ -319,7 +319,7 @@ func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request)
var status int var status int
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) { waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
if to.GUI.Password != cfg.GUI.Password { 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) l.Warnln("hashing password:", err)
errMsg = err.Error() errMsg = err.Error()
status = http.StatusInternalServerError status = http.StatusInternalServerError
@ -401,7 +401,7 @@ func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui
var status int var status int
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) { waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
if gui.Password != oldPassword { 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) l.Warnln("hashing password:", err)
errMsg = err.Error() errMsg = err.Error()
status = http.StatusInternalServerError status = http.StatusInternalServerError

View File

@ -22,6 +22,7 @@ import (
"testing" "testing"
"github.com/d4l3k/messagediff" "github.com/d4l3k/messagediff"
"golang.org/x/crypto/bcrypt"
"github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
@ -773,8 +774,9 @@ func TestGUIConfigURL(t *testing.T) {
func TestGUIPasswordHash(t *testing.T) { func TestGUIPasswordHash(t *testing.T) {
var c GUIConfiguration var c GUIConfiguration
// Setting a plaintext password should work
testPass := "pass" testPass := "pass"
if err := c.HashAndSetPassword(testPass); err != nil { if err := c.SetPassword(testPass); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if c.Password == testPass { if c.Password == testPass {
@ -789,6 +791,16 @@ func TestGUIPasswordHash(t *testing.T) {
if err := c.CompareHashedPassword(failPass); err == nil { if err := c.CompareHashedPassword(failPass); err == nil {
t.Errorf("Match on different password: %v", err) 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) { func TestDuplicateDevices(t *testing.T) {

View File

@ -9,6 +9,7 @@ package config
import ( import (
"net/url" "net/url"
"os" "os"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -115,9 +116,19 @@ func (c GUIConfiguration) URL() string {
return u.String() return u.String()
} }
// SetHashedPassword hashes the given plaintext password and stores the new hash. // matches a bcrypt hash and not too much else
func (c *GUIConfiguration) HashAndSetPassword(password string) error { var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
hash, err := bcrypt.GenerateFromPassword([]byte(password), 0)
// 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 { if err != nil {
return err return err
} }