Merge pull request #549 from brennie/dev/bcrypt-htpasswd
Support bcrypt passwords in htpasswd
This commit is contained in:
commit
a94b0a8b25
53
Gopkg.lock
generated
53
Gopkg.lock
generated
@ -58,7 +58,10 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/pquerna/cachecontrol"
|
name = "github.com/pquerna/cachecontrol"
|
||||||
packages = [".","cacheobject"]
|
packages = [
|
||||||
|
".",
|
||||||
|
"cacheobject"
|
||||||
|
]
|
||||||
revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
@ -70,30 +73,60 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = ["ed25519","ed25519/internal/edwards25519"]
|
packages = [
|
||||||
|
"bcrypt",
|
||||||
|
"blowfish",
|
||||||
|
"ed25519",
|
||||||
|
"ed25519/internal/edwards25519"
|
||||||
|
]
|
||||||
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = ["context","context/ctxhttp"]
|
packages = [
|
||||||
|
"context",
|
||||||
|
"context/ctxhttp"
|
||||||
|
]
|
||||||
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/oauth2"
|
name = "golang.org/x/oauth2"
|
||||||
packages = [".","google","internal","jws","jwt"]
|
packages = [
|
||||||
|
".",
|
||||||
|
"google",
|
||||||
|
"internal",
|
||||||
|
"jws",
|
||||||
|
"jwt"
|
||||||
|
]
|
||||||
revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
|
revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "google.golang.org/api"
|
name = "google.golang.org/api"
|
||||||
packages = ["admin/directory/v1","gensupport","googleapi","googleapi/internal/uritemplates"]
|
packages = [
|
||||||
|
"admin/directory/v1",
|
||||||
|
"gensupport",
|
||||||
|
"googleapi",
|
||||||
|
"googleapi/internal/uritemplates"
|
||||||
|
]
|
||||||
revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
|
revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "google.golang.org/appengine"
|
name = "google.golang.org/appengine"
|
||||||
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
|
packages = [
|
||||||
|
".",
|
||||||
|
"internal",
|
||||||
|
"internal/app_identity",
|
||||||
|
"internal/base",
|
||||||
|
"internal/datastore",
|
||||||
|
"internal/log",
|
||||||
|
"internal/modules",
|
||||||
|
"internal/remote_api",
|
||||||
|
"internal/urlfetch",
|
||||||
|
"urlfetch"
|
||||||
|
]
|
||||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
@ -105,13 +138,17 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "gopkg.in/square/go-jose.v2"
|
name = "gopkg.in/square/go-jose.v2"
|
||||||
packages = [".","cipher","json"]
|
packages = [
|
||||||
|
".",
|
||||||
|
"cipher",
|
||||||
|
"json"
|
||||||
|
]
|
||||||
revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
|
revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
|
||||||
version = "v2.1.3"
|
version = "v2.1.3"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "efab48a0e196c2a849bfbe9aa02d2ae28d281ce1bfe9f23720d648858eefc8e6"
|
inputs-digest = "b502c41a61115d14d6379be26b0300f65d173bdad852f0170d387ebf2d7ec173"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -38,3 +38,7 @@
|
|||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "gopkg.in/fsnotify.v1"
|
name = "gopkg.in/fsnotify.v1"
|
||||||
version = "~1.2.0"
|
version = "~1.2.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
24
htpasswd.go
24
htpasswd.go
@ -7,10 +7,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// lookup passwords in a htpasswd file
|
// Lookup passwords in a htpasswd file
|
||||||
// The entries must have been created with -s for SHA encryption
|
// Passwords must be generated with -B for bcrypt or -s for SHA1.
|
||||||
|
|
||||||
type HtpasswdFile struct {
|
type HtpasswdFile struct {
|
||||||
Users map[string]string
|
Users map[string]string
|
||||||
@ -47,14 +49,20 @@ func (h *HtpasswdFile) Validate(user string, password string) bool {
|
|||||||
if !exists {
|
if !exists {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if realPassword[:5] == "{SHA}" {
|
|
||||||
|
shaPrefix := realPassword[:5]
|
||||||
|
if shaPrefix == "{SHA}" {
|
||||||
|
shaValue := realPassword[5:]
|
||||||
d := sha1.New()
|
d := sha1.New()
|
||||||
d.Write([]byte(password))
|
d.Write([]byte(password))
|
||||||
if realPassword[5:] == base64.StdEncoding.EncodeToString(d.Sum(nil)) {
|
return shaValue == base64.StdEncoding.EncodeToString(d.Sum(nil))
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Invalid htpasswd entry for %s. Must be a SHA entry.", user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bcryptPrefix := realPassword[:4]
|
||||||
|
if bcryptPrefix == "$2a$" || bcryptPrefix == "$2b$" || bcryptPrefix == "$2x$" || bcryptPrefix == "$2y$" {
|
||||||
|
return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password)) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/stretchr/testify/assert"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHtpasswd(t *testing.T) {
|
func TestSHA(t *testing.T) {
|
||||||
file := bytes.NewBuffer([]byte("testuser:{SHA}PaVBVZkYqAjCQCu6UBL2xgsnZhw=\n"))
|
file := bytes.NewBuffer([]byte("testuser:{SHA}PaVBVZkYqAjCQCu6UBL2xgsnZhw=\n"))
|
||||||
h, err := NewHtpasswd(file)
|
h, err := NewHtpasswd(file)
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
@ -14,3 +17,21 @@ func TestHtpasswd(t *testing.T) {
|
|||||||
valid := h.Validate("testuser", "asdf")
|
valid := h.Validate("testuser", "asdf")
|
||||||
assert.Equal(t, valid, true)
|
assert.Equal(t, valid, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBcrypt(t *testing.T) {
|
||||||
|
hash1, err := bcrypt.GenerateFromPassword([]byte("password"), 1)
|
||||||
|
hash2, err := bcrypt.GenerateFromPassword([]byte("top-secret"), 2)
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
|
||||||
|
contents := fmt.Sprintf("testuser1:%s\ntestuser2:%s\n", hash1, hash2)
|
||||||
|
file := bytes.NewBuffer([]byte(contents))
|
||||||
|
|
||||||
|
h, err := NewHtpasswd(file)
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
|
||||||
|
valid := h.Validate("testuser1", "password")
|
||||||
|
assert.Equal(t, valid, true)
|
||||||
|
|
||||||
|
valid = h.Validate("testuser2", "top-secret")
|
||||||
|
assert.Equal(t, valid, true)
|
||||||
|
}
|
||||||
|
2
main.go
2
main.go
@ -52,7 +52,7 @@ func main() {
|
|||||||
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
|
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
|
||||||
flagSet.String("client-secret", "", "the OAuth Client Secret")
|
flagSet.String("client-secret", "", "the OAuth Client Secret")
|
||||||
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
|
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
|
||||||
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption")
|
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption")
|
||||||
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
|
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
|
||||||
flagSet.String("custom-templates-dir", "", "path to custom html templates")
|
flagSet.String("custom-templates-dir", "", "path to custom html templates")
|
||||||
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
|
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
|
||||||
|
Loading…
Reference in New Issue
Block a user