From 5bd9e831eb9eaf5d1b0f7f4b908eaa5377cd8246 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Fri, 1 Nov 2019 19:31:51 +0100 Subject: [PATCH] Use pure implementation of crypt to generate and check password hashes. This allows to remove the dependency to libc. --- CHANGELOG.md | 1 + Dockerfile | 44 +++++++++++++++++------ authentication/file_user_provider.go | 2 +- authentication/password_hash.go | 37 +++++++------------ authentication/password_hash_test.go | 7 ++-- cmd/authelia-scripts/cmd_hash_password.go | 2 +- go.mod | 1 + go.sum | 5 +++ 8 files changed, 58 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc8fdfb44..ca51d6a9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Release Notes - Version 4.0.0 * The local storage has been replaced by a good old sqlite3 database. * The "secure" flag from the SMTP notifier configuration has been removed as TLS is used by default when available. * authelia-scripts tool has been rewritten in Go. +* Use pure implementation of crypt to avoid CGO and dependency to libc. Release Notes - Version 3.16.3 ------------------------------ diff --git a/Dockerfile b/Dockerfile index 9d76b8b72..f1f4852fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,40 @@ -FROM alpine:3.9.4 +# ======================================= +# ===== Build image for the backend ===== +# ======================================= +FROM golang:1.13-alpine AS builder-backend + +# gcc and musl-dev are required for building go-sqlite3 +RUN apk --no-cache add gcc musl-dev + +WORKDIR /go/src/app +COPY . . + +# CGO_ENABLED=1 is mandatory for building go-sqlite3 +RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o authelia + + +# ======================================== +# ===== Build image for the frontend ===== +# ======================================== +FROM node:11-alpine AS builder-frontend + +WORKDIR /node/src/app +COPY client . + +# Install the dependencies and build +RUN npm ci && npm run build + +# =================================== +# ===== Authelia official image ===== +# =================================== +FROM alpine:3.10.3 + +RUN apk --no-cache add ca-certificates tzdata WORKDIR /usr/app -RUN apk --no-cache add ca-certificates tzdata wget - -# Install the libc required by the password hashing compiled with CGO. -RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -RUN wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.30-r0/glibc-2.30-r0.apk -RUN apk --no-cache add glibc-2.30-r0.apk - -ADD dist/authelia authelia -ADD dist/public_html public_html +COPY --from=builder-backend /go/src/app/authelia authelia +COPY --from=builder-frontend /node/src/app/build public_html EXPOSE 9091 diff --git a/authentication/file_user_provider.go b/authentication/file_user_provider.go index 13f0ab071..9882eee71 100644 --- a/authentication/file_user_provider.go +++ b/authentication/file_user_provider.go @@ -96,7 +96,7 @@ func (p *FileUserProvider) UpdatePassword(username string, newPassword string) e return fmt.Errorf("User '%s' does not exist in database", username) } - hash := HashPassword(newPassword, nil) + hash := HashPassword(newPassword, "") details.HashedPassword = fmt.Sprintf("{CRYPT}%s", hash) p.lock.Lock() diff --git a/authentication/password_hash.go b/authentication/password_hash.go index 42203809d..346ac9641 100644 --- a/authentication/password_hash.go +++ b/authentication/password_hash.go @@ -1,29 +1,15 @@ package authentication -// #cgo LDFLAGS: -lcrypt -// #define _GNU_SOURCE -// #include -// #include -import "C" import ( "errors" "fmt" + "log" "math/rand" "strconv" "strings" - "unsafe" -) -// Crypt wraps C library crypt_r -func crypt(key string, salt string) string { - data := C.struct_crypt_data{} - ckey := C.CString(key) - csalt := C.CString(salt) - out := C.GoString(C.crypt_r(ckey, csalt, &data)) - C.free(unsafe.Pointer(ckey)) - C.free(unsafe.Pointer(csalt)) - return out -} + "github.com/simia-tech/crypt" +) // PasswordHash represents all characteristics of a password hash. // Authelia only supports salted SHA512 method, i.e., $6$ mode. @@ -79,14 +65,15 @@ func RandomString(n int) string { // HashPassword generate a salt and hash the password with the salt and a constant // number of rounds. -func HashPassword(password string, salt *string) string { - var generatedSalt string - if salt == nil { - generatedSalt = fmt.Sprintf("$6$rounds=50000$%s$", RandomString(16)) - } else { - generatedSalt = *salt +func HashPassword(password string, salt string) string { + if salt == "" { + salt = fmt.Sprintf("$6$rounds=50000$%s", RandomString(16)) } - return crypt(password, generatedSalt) + hash, err := crypt.Crypt(password, salt) + if err != nil { + log.Fatal(err) + } + return hash } // CheckPassword check a password against a hash. @@ -96,6 +83,6 @@ func CheckPassword(password string, hash string) (bool, error) { return false, err } salt := fmt.Sprintf("$6$rounds=%d$%s$", passwordHash.Rounds, passwordHash.Salt) - pHash := HashPassword(password, &salt) + pHash := HashPassword(password, salt) return pHash == hash, nil } diff --git a/authentication/password_hash_test.go b/authentication/password_hash_test.go index 3d4745fc8..2da589f34 100644 --- a/authentication/password_hash_test.go +++ b/authentication/password_hash_test.go @@ -7,13 +7,12 @@ import ( ) func TestShouldHashPassword(t *testing.T) { - salt := "$6$rounds=5000$aFr56HjK3DrB8t3S$" - hash := HashPassword("password", &salt) - assert.Equal(t, "$6$rounds=5000$aFr56HjK3DrB8t3S$3yTiN5991WnlmhE8qlMmayIiUiT5ppq68CIuHBrGgQHJ4RWSCb0AykB0E6Ij761ZTzLaCZKuXpurcBiqDR1hu.", hash) + hash := HashPassword("password", "$6$rounds=50000$aFr56HjK3DrB8t3S") + assert.Equal(t, "$6$rounds=50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1", hash) } func TestShouldCheckPassword(t *testing.T) { - ok, err := CheckPassword("password", "$6$rounds=5000$aFr56HjK3DrB8t3S$3yTiN5991WnlmhE8qlMmayIiUiT5ppq68CIuHBrGgQHJ4RWSCb0AykB0E6Ij761ZTzLaCZKuXpurcBiqDR1hu.") + ok, err := CheckPassword("password", "$6$rounds=50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1") assert.NoError(t, err) assert.True(t, ok) diff --git a/cmd/authelia-scripts/cmd_hash_password.go b/cmd/authelia-scripts/cmd_hash_password.go index b98cf21ad..33dee09cf 100644 --- a/cmd/authelia-scripts/cmd_hash_password.go +++ b/cmd/authelia-scripts/cmd_hash_password.go @@ -9,5 +9,5 @@ import ( // HashPassword hash the provided password with crypt sha256 hash function func HashPassword(cobraCmd *cobra.Command, args []string) { - fmt.Println(authentication.HashPassword(args[0], nil)) + fmt.Println(authentication.HashPassword(args[0], "")) } diff --git a/go.mod b/go.mod index 4ea6adec5..e63c61c5b 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/golang/snappy v0.0.1 // indirect github.com/mattn/go-sqlite3 v1.11.0 github.com/pquerna/otp v1.2.0 + github.com/simia-tech/crypt v0.2.0 github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v0.0.5 github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index bd9160dc5..eb3ea68ca 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,8 @@ github.com/savsgio/dictpool v0.0.0-20191028211042-a886cee3358a h1:HHo8bk/5tOTL+U github.com/savsgio/dictpool v0.0.0-20191028211042-a886cee3358a/go.mod h1:hnGRFeigcU3gTEMTWW8OjfoSYztn2GPcriOY9iIzCrA= github.com/savsgio/gotils v0.0.0-20190925070755-524bc4f47500 h1:9Pi10H7E8E79/x2HSe1FmMGd7BJ1WAqDKzwjpv+ojFg= github.com/savsgio/gotils v0.0.0-20190925070755-524bc4f47500/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY= +github.com/simia-tech/crypt v0.2.0 h1:cU8qdqUYNuEFKSMq15yaB2aI1aC5vrn6dFOonT6Kg6o= +github.com/simia-tech/crypt v0.2.0/go.mod h1:DMwvjPTzsiHrjqHVW5HvIbF4vUUzMCYDKVLsPWmLdTo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -71,6 +73,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= @@ -92,6 +95,7 @@ github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -99,6 +103,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181116161606-93218def8b18/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=