Skip to content

Commit

Permalink
Add support for hashed passwords (#876)
Browse files Browse the repository at this point in the history
* Add support for hashed passwords in ftpserver.json

* Add option for config file to automatically hash plain-text passwords

* Prevent hashing of anonymous user's plain-text password

* Fix issue where ftpserver.json got truncated without being saved

* Improve the way config is saved after hashing passwords

* Change in-memory config when hashing plain-text passwords
  • Loading branch information
wanieru authored Jan 11, 2023
1 parent 99e4ede commit 3fba0c2
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 12 deletions.
22 changes: 12 additions & 10 deletions config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@
200
]
},
"hash_plaintext_passwords": {
"type": "boolean",
"default": false,
"title": "Overwrite plain-text passwords with hashed equivalents",
"examples": [
true,
false
]
},
"passive_transfer_port_range": {
"type": "object",
"default": {},
Expand Down Expand Up @@ -183,22 +192,15 @@
"type": "string",
"title": "The FTP user",
"examples": [
"test",
"dropbox",
"gdrive",
"s3",
"sftp"
"username"
]
},
"pass": {
"type": "string",
"title": "The FTP password",
"examples": [
"test",
"dropbox",
"gdrive",
"s3",
"sftp"
"plaintext-password",
"$2a$10$jG7tuqIlcUDMl1m1Ytj1TunU7pk.ko8lj3nOGzZvkIeU/BsfPVBra"
]
},
"fs": {
Expand Down
60 changes: 58 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ package config
import (
"encoding/json"
"errors"
"fmt"
"os"

log "github.com/fclairamb/go-log"
"github.com/tidwall/sjson"

"github.com/fclairamb/ftpserver/config/confpar"
"github.com/fclairamb/ftpserver/fs"
"golang.org/x/crypto/bcrypt"
)

// ErrUnknownUser is returned when the provided user cannot be identified through our authentication mechanism
Expand Down Expand Up @@ -82,9 +85,50 @@ func (c *Config) Load() error {

c.Content = &content

if c.Content.HashPlaintextPasswords {
c.HashPlaintextPasswords()
}

return c.Prepare()
}

func (c *Config) HashPlaintextPasswords() error {

json, errReadFile := os.ReadFile(c.fileName)
if errReadFile != nil {
c.logger.Error("Cannot read config file!", "err", errReadFile)
return errReadFile
}

save := false
for i, a := range c.Content.Accesses {
if a.User == "anonymous" && a.Pass == "*" {
continue
}
_, errCost := bcrypt.Cost([]byte(a.Pass))
if errCost != nil {
//This password is not hashed
hash, errHash := bcrypt.GenerateFromPassword([]byte(a.Pass), 10)
if errHash == nil {
modified, errJsonSet := sjson.Set(string(json), "accesses."+fmt.Sprint(i)+".pass", string(hash))
c.Content.Accesses[i].Pass = string(hash)
if errJsonSet == nil {
save = true
json = []byte(modified)
}
}
}
}
if save {
errWriteFile := os.WriteFile(c.fileName, json, 0644)
if errWriteFile != nil {
c.logger.Error("Cannot write config file!", "err", errWriteFile)
return errWriteFile
}
}
return nil
}

// Prepare the config before using it
func (c *Config) Prepare() error {
ct := c.Content
Expand Down Expand Up @@ -116,8 +160,20 @@ func (c *Config) CheckAccesses() error {
// GetAccess return a file system access given some credentials
func (c *Config) GetAccess(user string, pass string) (*confpar.Access, error) {
for _, a := range c.Content.Accesses {
if a.User == user && (a.Pass == pass || (a.User == "anonymous" && a.Pass == "*")) {
return a, nil
if a.User == user {
_, errCost := bcrypt.Cost([]byte(a.Pass))
if errCost == nil {
//This user's password is bcrypted
errCompare := bcrypt.CompareHashAndPassword([]byte(a.Pass), []byte(pass))
if errCompare == nil {
return a, nil
}
} else {
//This user's password is plain-text
if a.Pass == pass || (a.User == "anonymous" && a.Pass == "*") {
return a, nil
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions config/confpar/confpar.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Content struct {
ListenAddress string `json:"listen_address"` // Address to listen on
PublicHost string `json:"public_host"` // Public host to listen on
MaxClients int `json:"max_clients"` // Maximum clients who can connect
HashPlaintextPasswords bool `json:"hash_plaintext_passwords"` // Overwrite plain-text passwords with hashed equivalents
Accesses []*Access `json:"accesses"` // Accesses offered to users
PassiveTransferPortRange *PortRange `json:"passive_transfer_port_range"` // Listen port range
Logging Logging `json:"logging"` // Logging parameters
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ require (
golang.org/x/oauth2 v0.4.0
)

require (
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
)

require (
cloud.google.com/go/compute v1.7.0 // indirect
github.com/dropbox/dropbox-sdk-go-unofficial v5.6.0+incompatible // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,16 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down

0 comments on commit 3fba0c2

Please sign in to comment.