Merge pull request #549 from brennie/dev/bcrypt-htpasswd
Support bcrypt passwords in htpasswd
This commit is contained in:
		
						commit
						a94b0a8b25
					
				|  | @ -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" | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								htpasswd.go
								
								
								
								
							
							
						
						
									
										22
									
								
								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