From 542554fb2c4f46a90024282d6401a694eaac18ee Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Fri, 4 Sep 2020 21:08:09 +0200 Subject: [PATCH] replace the library to verify UNIX's crypt(3) passwords --- dataprovider/dataprovider.go | 42 +++++++++++++----------------------- go.mod | 2 +- go.sum | 4 ++-- httpd/auth.go | 25 +++++++++++---------- httpd/internal_test.go | 9 ++++++++ 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index 2635f269..1bf7bac0 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -30,9 +30,12 @@ import ( "sync" "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/go-chi/render" - unixcrypt "github.com/nathanaelle/password/v2" "github.com/rs/xid" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/pbkdf2" @@ -112,7 +115,6 @@ var ( logSender = "dataProvider" availabilityTicker *time.Ticker availabilityTickerDone chan bool - errWrongPassword = errors.New("password does not match") credentialsDirPath string sqlTableUsers = "users" sqlTableFolders = "folders" @@ -1163,34 +1165,20 @@ func checkUserAndPubKey(user User, pubKey []byte) (User, string, error) { } func compareUnixPasswordAndHash(user *User, password string) (bool, error) { - match := false - var err error + var crypter crypt.Crypter if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) { - crypter, ok := unixcrypt.SHA512.CrypterFound(user.Password) - if !ok { - err = errors.New("cannot found matching SHA512 crypter") - providerLog(logger.LevelWarn, "error comparing password with SHA512 crypt hash: %v", err) - return match, err - } - 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 + crypter = sha512_crypt.New() + } else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) { + crypter = md5_crypt.New() + } else if strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) { + crypter = apr1_crypt.New() } 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) { diff --git a/go.mod b/go.mod index b6325048..e5b6c1b2 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( cloud.google.com/go v0.65.0 // indirect 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/aws/aws-sdk-go v1.34.13 github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d @@ -21,7 +22,6 @@ require ( github.com/mattn/go-sqlite3 v1.14.2 github.com/miekg/dns v1.1.31 // 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/pelletier/go-toml v1.8.0 // indirect github.com/pires/go-proxyproto v0.1.3 diff --git a/go.sum b/go.sum index b1e736c7..6de6a96f 100644 --- a/go.sum +++ b/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= 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/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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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/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/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.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= diff --git a/httpd/auth.go b/httpd/auth.go index 1de479d0..8b6987ca 100644 --- a/httpd/auth.go +++ b/httpd/auth.go @@ -9,7 +9,8 @@ import ( "strings" "sync" - unixcrypt "github.com/nathanaelle/password/v2" + "github.com/GehirnInc/crypt/apr1_crypt" + "github.com/GehirnInc/crypt/md5_crypt" "golang.org/x/crypto/bcrypt" "github.com/drakkan/sftpgo/logger" @@ -20,11 +21,12 @@ const ( authenticationHeader = "WWW-Authenticate" authenticationRealm = "SFTPGo Web" unauthResponse = "Unauthorized" + md5CryptPwdPrefix = "$1$" + apr1CryptPwdPrefix = "$apr1$" ) var ( - md5CryptPwdPrefixes = []string{"$1$", "$apr1$"} - bcryptPwdPrefixes = []string{"$2a$", "$2$", "$2x$", "$2y$", "$2b$"} + bcryptPwdPrefixes = []string{"$2a$", "$2$", "$2x$", "$2y$", "$2b$"} ) type httpAuthProvider interface { @@ -136,14 +138,15 @@ func validateCredentials(r *http.Request) bool { err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(password)) return err == nil } - if utils.IsStringPrefixInSlice(hashedPwd, md5CryptPwdPrefixes) { - crypter, ok := unixcrypt.MD5.CrypterFound(hashedPwd) - if !ok { - err := errors.New("cannot found matching MD5 crypter") - logger.Debug(logSender, "", "error comparing password with MD5 crypt hash: %v", err) - return false - } - return crypter.Verify([]byte(password)) + if strings.HasPrefix(hashedPwd, md5CryptPwdPrefix) { + crypter := md5_crypt.New() + err := crypter.Verify(hashedPwd, []byte(password)) + return err == nil + } + if strings.HasPrefix(hashedPwd, apr1CryptPwdPrefix) { + crypter := apr1_crypt.New() + err := crypter.Verify(hashedPwd, []byte(password)) + return err == nil } } return false diff --git a/httpd/internal_test.go b/httpd/internal_test.go index 85e54532..1dd847dc 100644 --- a/httpd/internal_test.go +++ b/httpd/internal_test.go @@ -466,6 +466,15 @@ func TestBasicAuth(t *testing.T) { resp, _ := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(metricsPath), nil, "") defer resp.Body.Close() 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")...) err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm) assert.NoError(t, err)