mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-09 08:15:13 +03:00
replace the library to verify UNIX's crypt(3) passwords
This commit is contained in:
@@ -30,9 +30,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/GehirnInc/crypt"
|
||||||
|
"github.com/GehirnInc/crypt/apr1_crypt"
|
||||||
|
"github.com/GehirnInc/crypt/md5_crypt"
|
||||||
|
"github.com/GehirnInc/crypt/sha512_crypt"
|
||||||
"github.com/alexedwards/argon2id"
|
"github.com/alexedwards/argon2id"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
unixcrypt "github.com/nathanaelle/password/v2"
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
@@ -112,7 +115,6 @@ var (
|
|||||||
logSender = "dataProvider"
|
logSender = "dataProvider"
|
||||||
availabilityTicker *time.Ticker
|
availabilityTicker *time.Ticker
|
||||||
availabilityTickerDone chan bool
|
availabilityTickerDone chan bool
|
||||||
errWrongPassword = errors.New("password does not match")
|
|
||||||
credentialsDirPath string
|
credentialsDirPath string
|
||||||
sqlTableUsers = "users"
|
sqlTableUsers = "users"
|
||||||
sqlTableFolders = "folders"
|
sqlTableFolders = "folders"
|
||||||
@@ -1163,34 +1165,20 @@ func checkUserAndPubKey(user User, pubKey []byte) (User, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func compareUnixPasswordAndHash(user *User, password string) (bool, error) {
|
func compareUnixPasswordAndHash(user *User, password string) (bool, error) {
|
||||||
match := false
|
var crypter crypt.Crypter
|
||||||
var err error
|
|
||||||
if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) {
|
if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) {
|
||||||
crypter, ok := unixcrypt.SHA512.CrypterFound(user.Password)
|
crypter = sha512_crypt.New()
|
||||||
if !ok {
|
} else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) {
|
||||||
err = errors.New("cannot found matching SHA512 crypter")
|
crypter = md5_crypt.New()
|
||||||
providerLog(logger.LevelWarn, "error comparing password with SHA512 crypt hash: %v", err)
|
} else if strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) {
|
||||||
return match, err
|
crypter = apr1_crypt.New()
|
||||||
}
|
|
||||||
if !crypter.Verify([]byte(password)) {
|
|
||||||
return match, errWrongPassword
|
|
||||||
}
|
|
||||||
match = true
|
|
||||||
} else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) || strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) {
|
|
||||||
crypter, ok := unixcrypt.MD5.CrypterFound(user.Password)
|
|
||||||
if !ok {
|
|
||||||
err = errors.New("cannot found matching MD5 crypter")
|
|
||||||
providerLog(logger.LevelWarn, "error comparing password with MD5 crypt hash: %v", err)
|
|
||||||
return match, err
|
|
||||||
}
|
|
||||||
if !crypter.Verify([]byte(password)) {
|
|
||||||
return match, errWrongPassword
|
|
||||||
}
|
|
||||||
match = true
|
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("unix crypt: invalid or unsupported hash format")
|
return false, errors.New("unix crypt: invalid or unsupported hash format")
|
||||||
}
|
}
|
||||||
return match, err
|
if err := crypter.Verify(user.Password, []byte(password)); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func comparePbkdf2PasswordAndHash(password, hashedPassword string) (bool, error) {
|
func comparePbkdf2PasswordAndHash(password, hashedPassword string) (bool, error) {
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -5,6 +5,7 @@ go 1.13
|
|||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.65.0 // indirect
|
cloud.google.com/go v0.65.0 // indirect
|
||||||
cloud.google.com/go/storage v1.11.0
|
cloud.google.com/go/storage v1.11.0
|
||||||
|
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||||
github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b
|
github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b
|
||||||
github.com/aws/aws-sdk-go v1.34.13
|
github.com/aws/aws-sdk-go v1.34.13
|
||||||
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d
|
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d
|
||||||
@@ -21,7 +22,6 @@ require (
|
|||||||
github.com/mattn/go-sqlite3 v1.14.2
|
github.com/mattn/go-sqlite3 v1.14.2
|
||||||
github.com/miekg/dns v1.1.31 // indirect
|
github.com/miekg/dns v1.1.31 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||||
github.com/nathanaelle/password/v2 v2.0.1
|
|
||||||
github.com/otiai10/copy v1.2.0
|
github.com/otiai10/copy v1.2.0
|
||||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||||
github.com/pires/go-proxyproto v0.1.3
|
github.com/pires/go-proxyproto v0.1.3
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.11.0/go.mod h1:/PAbprKS+5msVYogBmczjWalDXnQ9mr64y
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
|
||||||
|
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
@@ -322,8 +324,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nathanaelle/password/v2 v2.0.1 h1:ItoCTdsuIWzilYmllQPa3DR3YoCXcpfxScWLqr8Ii2s=
|
|
||||||
github.com/nathanaelle/password/v2 v2.0.1/go.mod h1:eaoT+ICQEPNtikBRIAatN8ThWwMhVG+r1jTw60BvPJk=
|
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
unixcrypt "github.com/nathanaelle/password/v2"
|
"github.com/GehirnInc/crypt/apr1_crypt"
|
||||||
|
"github.com/GehirnInc/crypt/md5_crypt"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/logger"
|
"github.com/drakkan/sftpgo/logger"
|
||||||
@@ -20,11 +21,12 @@ const (
|
|||||||
authenticationHeader = "WWW-Authenticate"
|
authenticationHeader = "WWW-Authenticate"
|
||||||
authenticationRealm = "SFTPGo Web"
|
authenticationRealm = "SFTPGo Web"
|
||||||
unauthResponse = "Unauthorized"
|
unauthResponse = "Unauthorized"
|
||||||
|
md5CryptPwdPrefix = "$1$"
|
||||||
|
apr1CryptPwdPrefix = "$apr1$"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
md5CryptPwdPrefixes = []string{"$1$", "$apr1$"}
|
bcryptPwdPrefixes = []string{"$2a$", "$2$", "$2x$", "$2y$", "$2b$"}
|
||||||
bcryptPwdPrefixes = []string{"$2a$", "$2$", "$2x$", "$2y$", "$2b$"}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpAuthProvider interface {
|
type httpAuthProvider interface {
|
||||||
@@ -136,14 +138,15 @@ func validateCredentials(r *http.Request) bool {
|
|||||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(password))
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(password))
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
if utils.IsStringPrefixInSlice(hashedPwd, md5CryptPwdPrefixes) {
|
if strings.HasPrefix(hashedPwd, md5CryptPwdPrefix) {
|
||||||
crypter, ok := unixcrypt.MD5.CrypterFound(hashedPwd)
|
crypter := md5_crypt.New()
|
||||||
if !ok {
|
err := crypter.Verify(hashedPwd, []byte(password))
|
||||||
err := errors.New("cannot found matching MD5 crypter")
|
return err == nil
|
||||||
logger.Debug(logSender, "", "error comparing password with MD5 crypt hash: %v", err)
|
}
|
||||||
return false
|
if strings.HasPrefix(hashedPwd, apr1CryptPwdPrefix) {
|
||||||
}
|
crypter := apr1_crypt.New()
|
||||||
return crypter.Verify([]byte(password))
|
err := crypter.Verify(hashedPwd, []byte(password))
|
||||||
|
return err == nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -466,6 +466,15 @@ func TestBasicAuth(t *testing.T) {
|
|||||||
resp, _ := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(metricsPath), nil, "")
|
resp, _ := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(metricsPath), nil, "")
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||||
|
authUserData = append(authUserData, []byte("test2:$1$OtSSTL8b$bmaCqEksI1e7rnZSjsIDR1\n")...)
|
||||||
|
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
|
||||||
|
_, _, err = GetVersion(http.StatusOK)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
SetBaseURLAndCredentials(httpBaseURL, "test2", "wrong_password")
|
||||||
|
_, _, err = GetVersion(http.StatusOK)
|
||||||
|
assert.Error(t, err)
|
||||||
authUserData = append(authUserData, []byte("test2:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
|
authUserData = append(authUserData, []byte("test2:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
|
||||||
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user