From ee90bfb506d93f179cfbaa684b10284e6d01e4d9 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sun, 26 Mar 2023 10:33:30 +0200 Subject: [PATCH] add unixcrypt build tag Signed-off-by: Nicola Murino --- .github/workflows/development.yml | 2 +- .github/workflows/docker.yml | 3 ++ docs/build-from-source.md | 1 + go.mod | 3 +- go.sum | 6 ++-- internal/dataprovider/dataprovider.go | 21 ++++++----- internal/dataprovider/unixcrypt.go | 39 +++++++++++++++++++++ internal/dataprovider/unixcrypt_disabled.go | 32 +++++++++++++++++ openapi/openapi.yaml | 2 +- 9 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 internal/dataprovider/unixcrypt.go create mode 100644 internal/dataprovider/unixcrypt_disabled.go diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 58682430..697ed854 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -236,7 +236,7 @@ jobs: - name: Build run: | - go build -trimpath -tags nopgxregisterdefaulttypes,nogcs,nos3,noportable,nobolt,nomysql,nopgsql,nosqlite,nometrics,noazblob -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo + go build -trimpath -tags nopgxregisterdefaulttypes,nogcs,nos3,noportable,nobolt,nomysql,nopgsql,nosqlite,nometrics,noazblob,unixcrypt -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo ./sftpgo -v cp -r openapi static templates internal/bundle/ go build -trimpath -tags nopgxregisterdefaulttypes,bundle -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c9ef5653..ec1a0ed0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -72,6 +72,9 @@ jobs: elif [[ $DOCKER_PKG == debian-plugins ]]; then VERSION="${VERSION}-plugins" VERSION_SLIM="${VERSION}-slim" + FEATURES="${FEATURES},unixcrypt" + elif [[ $DOCKER_PKG == debian ]]; then + FEATURES="${FEATURES},unixcrypt" fi DOCKER_IMAGES=("drakkan/sftpgo" "ghcr.io/drakkan/sftpgo") TAGS="${DOCKER_IMAGES[0]}:${VERSION}" diff --git a/docs/build-from-source.md b/docs/build-from-source.md index b8287676..052dd477 100644 --- a/docs/build-from-source.md +++ b/docs/build-from-source.md @@ -14,6 +14,7 @@ The following build tags are available: - `noportable`, disable portable mode, default enabled - `nometrics`, disable Prometheus metrics, default enabled - `bundle`, embed static files and templates. Before building with this tag enabled you have to copy `openapi`, `static` and `templates` dirs to `internal/bundle` directory. Default disabled +- `unixcrypt`, enable linking to `libcrypt`, default disabled, requires `CGO` If no build tag is specified the build will include the default features. diff --git a/go.mod b/go.mod index 190933af..5352381b 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 + github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 github.com/aws/aws-sdk-go-v2 v1.17.7 github.com/aws/aws-sdk-go-v2/config v1.18.19 github.com/aws/aws-sdk-go-v2/credentials v1.13.18 @@ -35,7 +36,7 @@ require ( github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-plugin v1.4.10-0.20230306173702-d78f3fc2891d github.com/hashicorp/go-retryablehttp v0.7.2 - github.com/jackc/pgx/v5 v5.3.2-0.20230324225134-e9d64ec29d90 + github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 github.com/klauspost/compress v1.16.3 github.com/lestrrat-go/jwx/v2 v2.0.9 diff --git a/go.sum b/go.sum index 16099809..3c62d13b 100644 --- a/go.sum +++ b/go.sum @@ -534,6 +534,8 @@ github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 h1:qZaEtLxnqY github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTeFRcTdnpzOlRjMoFYC/80HwVUreupyAiqPkCZQOXc= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= +github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4= +github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -1389,8 +1391,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= -github.com/jackc/pgx/v5 v5.3.2-0.20230324225134-e9d64ec29d90 h1:gBugq4KF3zkdaM4oQHSfhUFAfkVQOQpbD20wNggWPP4= -github.com/jackc/pgx/v5 v5.3.2-0.20230324225134-e9d64ec29d90/go.mod h1:sU+RaYl9qnhD3Ce+mwnFii6YEPx70mCYghBzKvqq4qo= +github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf h1:BLqUs5GEGMJ+h9s63INbbwXUXe95wvhvh1esISGgau0= +github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf/go.mod h1:sU+RaYl9qnhD3Ce+mwnFii6YEPx70mCYghBzKvqq4qo= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go index d0e69d03..e310518d 100644 --- a/internal/dataprovider/dataprovider.go +++ b/internal/dataprovider/dataprovider.go @@ -99,6 +99,7 @@ const ( md5cryptApr1PwdPrefix = "$apr1$" sha256cryptPwdPrefix = "$5$" sha512cryptPwdPrefix = "$6$" + yescryptPwdPrefix = "$y$" md5LDAPPwdPrefix = "{MD5}" trackQuotaDisabledError = "please enable track_quota in your configuration to use this method" operationAdd = "add" @@ -165,10 +166,11 @@ var ( internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix} hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5LDAPPwdPrefix, - sha256cryptPwdPrefix, sha512cryptPwdPrefix} - pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix} - pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix} - unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix} + sha256cryptPwdPrefix, sha512cryptPwdPrefix, yescryptPwdPrefix} + pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix} + pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix} + unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix, + yescryptPwdPrefix} sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName} logSender = "dataprovider" sqlTableUsers string @@ -3081,13 +3083,13 @@ func isPasswordOK(user *User, password string) (bool, error) { return match, err } updatePwd = config.PasswordHashing.Algo != HashingAlgoArgon2ID - } else if util.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) { - match, err = comparePbkdf2PasswordAndHash(password, user.Password) + } else if util.IsStringPrefixInSlice(user.Password, unixPwdPrefixes) { + match, err = compareUnixPasswordAndHash(user, password) if err != nil { return match, err } - } else if util.IsStringPrefixInSlice(user.Password, unixPwdPrefixes) { - match, err = compareUnixPasswordAndHash(user, password) + } else if util.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) { + match, err = comparePbkdf2PasswordAndHash(password, user.Password) if err != nil { return match, err } @@ -3258,6 +3260,9 @@ func checkUserAndPubKey(user *User, pubKey []byte, isSSHCert bool) (User, string } func compareUnixPasswordAndHash(user *User, password string) (bool, error) { + if strings.HasPrefix(user.Password, yescryptPwdPrefix) { + return compareYescryptPassword(user.Password, password) + } var crypter crypt.Crypter if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) { crypter = sha512_crypt.New() diff --git a/internal/dataprovider/unixcrypt.go b/internal/dataprovider/unixcrypt.go new file mode 100644 index 00000000..aff3cbc4 --- /dev/null +++ b/internal/dataprovider/unixcrypt.go @@ -0,0 +1,39 @@ +// Copyright (C) 2019-2023 Nicola Murino +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build unixcrypt +// +build unixcrypt + +package dataprovider + +import ( + "strings" + + "github.com/amoghe/go-crypt" + + "github.com/drakkan/sftpgo/v2/internal/version" +) + +func init() { + version.AddFeature("+unixcrypt") +} + +func compareYescryptPassword(hashedPwd, plainPwd string) (bool, error) { + lastIdx := strings.LastIndex(hashedPwd, "$") + pwd, err := crypt.Crypt(plainPwd, hashedPwd[:lastIdx+1]) + if err != nil { + return false, err + } + return pwd == hashedPwd, nil +} diff --git a/internal/dataprovider/unixcrypt_disabled.go b/internal/dataprovider/unixcrypt_disabled.go new file mode 100644 index 00000000..71e63ad9 --- /dev/null +++ b/internal/dataprovider/unixcrypt_disabled.go @@ -0,0 +1,32 @@ +// Copyright (C) 2019-2023 Nicola Murino +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build !unixcrypt +// +build !unixcrypt + +package dataprovider + +import ( + "errors" + + "github.com/drakkan/sftpgo/v2/internal/version" +) + +func init() { + version.AddFeature("-unixcrypt") +} + +func compareYescryptPassword(_, _ string) (bool, error) { + return false, errors.New("yescrypt hash format is not supported or disabled") +} diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 198cd57d..dd3ea8e5 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -7175,7 +7175,7 @@ components: type: array items: type: string - description: 'Features for the current build. Available features are `portable`, `bolt`, `mysql`, `sqlite`, `pgsql`, `s3`, `gcs`, `azblob`, `metrics`. If a feature is available it has a `+` prefix, otherwise a `-` prefix' + description: 'Features for the current build. Available features are `portable`, `bolt`, `mysql`, `sqlite`, `pgsql`, `s3`, `gcs`, `azblob`, `metrics`, `unixcrypt`. If a feature is available it has a `+` prefix, otherwise a `-` prefix' Token: type: object properties: