security: 10-slot Semaphore around password hash functions to prevent… (#904)
This commit is contained in:
commit
6dfffd1170
|
|
@ -3,6 +3,7 @@ package api
|
||||||
import (
|
import (
|
||||||
ctx "context"
|
ctx "context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -144,6 +145,9 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
|
||||||
hash, err := createHash(req.Msg.Password)
|
hash, err := createHash(req.Msg.Password)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrArgon2Busy) {
|
||||||
|
return nil, connect.NewError(connect.CodeResourceExhausted, err)
|
||||||
|
}
|
||||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("error creating hash: %w", err))
|
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("error creating hash: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +166,13 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
match := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
|
match, err := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrArgon2Busy) {
|
||||||
|
return nil, connect.NewError(connect.CodeResourceExhausted, err)
|
||||||
|
}
|
||||||
|
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("checking password: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{
|
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{
|
||||||
Success: match,
|
Success: match,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
config "github.com/OliveTin/OliveTin/internal/config"
|
config "github.com/OliveTin/OliveTin/internal/config"
|
||||||
|
|
@ -8,6 +9,12 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrArgon2Busy = errors.New("too many concurrent password operations")
|
||||||
|
|
||||||
|
const argon2MaxConcurrent = 10
|
||||||
|
|
||||||
|
var argon2Sem = make(chan struct{}, argon2MaxConcurrent)
|
||||||
|
|
||||||
var defaultParams = argon2id.Params{
|
var defaultParams = argon2id.Params{
|
||||||
Memory: 64 * 1024,
|
Memory: 64 * 1024,
|
||||||
Iterations: 4,
|
Iterations: 4,
|
||||||
|
|
@ -17,6 +24,12 @@ var defaultParams = argon2id.Params{
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateHash(password string) (string, error) {
|
func CreateHash(password string) (string, error) {
|
||||||
|
select {
|
||||||
|
case argon2Sem <- struct{}{}:
|
||||||
|
defer func() { <-argon2Sem }()
|
||||||
|
default:
|
||||||
|
return "", ErrArgon2Busy
|
||||||
|
}
|
||||||
hash, err := argon2id.CreateHash(password, &defaultParams)
|
hash, err := argon2id.CreateHash(password, &defaultParams)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -31,30 +44,38 @@ func createHash(password string) (string, error) {
|
||||||
return CreateHash(password)
|
return CreateHash(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func comparePasswordAndHash(password, hash string) bool {
|
func comparePasswordAndHash(password, hash string) (bool, error) {
|
||||||
|
select {
|
||||||
|
case argon2Sem <- struct{}{}:
|
||||||
|
defer func() { <-argon2Sem }()
|
||||||
|
default:
|
||||||
|
return false, ErrArgon2Busy
|
||||||
|
}
|
||||||
match, err := argon2id.ComparePasswordAndHash(password, hash)
|
match, err := argon2id.ComparePasswordAndHash(password, hash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error comparing password and hash: %v", err)
|
log.Errorf("Error comparing password and hash: %v", err)
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return match
|
return match, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkUserPassword(cfg *config.Config, username, password string) bool {
|
func checkUserPassword(cfg *config.Config, username, password string) (bool, error) {
|
||||||
for _, user := range cfg.AuthLocalUsers.Users {
|
for _, user := range cfg.AuthLocalUsers.Users {
|
||||||
if user.Username == username {
|
if user.Username == username {
|
||||||
match := comparePasswordAndHash(password, user.Password)
|
match, err := comparePasswordAndHash(password, user.Password)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
if match {
|
if match {
|
||||||
return true
|
return true, nil
|
||||||
} else {
|
} else {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"username": username,
|
"username": username,
|
||||||
}).Warn("Password does not match for user")
|
}).Warn("Password does not match for user")
|
||||||
|
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,5 +84,5 @@ func checkUserPassword(cfg *config.Config, username, password string) bool {
|
||||||
"username": username,
|
"username": username,
|
||||||
}).Warn("Failed to check password for user, as username was not found")
|
}).Warn("Failed to check password for user, as username was not found")
|
||||||
|
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue