git.iko.soy/typeable/oauth2-proxy
on master help sign in
Support bcrypt passwords in htpasswd
commit008ffae3bb5f1d068f2ea4dddb88ea80a6697297 authorBarret Rennie <barret@brennie.ca> dateFri 16 Feb 2018, 02:14 (9 years ago) parentae49c7d
5 files changed+89−19
Gopkg.lock+45−8 Gopkg.toml+4 htpasswd.go+16−8 htpasswd_test.go+23−2 main.go+1−1
Gopkg.lock+45−8
@@ -58,7 +58,10 @@
5858[[projects]]
5959 branch = "master"
6060 name = "github.com/pquerna/cachecontrol"
61− packages = [".","cacheobject"]
61+ packages = [
62+ ".",
63+ "cacheobject"
64+ ]
6265 revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
6366
6467[[projects]]
@@ -70,30 +73,60 @@
7073[[projects]]
7174 branch = "master"
7275 name = "golang.org/x/crypto"
73− packages = ["ed25519","ed25519/internal/edwards25519"]
76+ packages = [
77+ "bcrypt",
78+ "blowfish",
79+ "ed25519",
80+ "ed25519/internal/edwards25519"
81+ ]
7482 revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
7583
7684[[projects]]
7785 branch = "master"
7886 name = "golang.org/x/net"
79− packages = ["context","context/ctxhttp"]
87+ packages = [
88+ "context",
89+ "context/ctxhttp"
90+ ]
8091 revision = "9dfe39835686865bff950a07b394c12a98ddc811"
8192
8293[[projects]]
8394 branch = "master"
8495 name = "golang.org/x/oauth2"
85− packages = [".","google","internal","jws","jwt"]
96+ packages = [
97+ ".",
98+ "google",
99+ "internal",
100+ "jws",
101+ "jwt"
102+ ]
86103 revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
87104
88105[[projects]]
89106 branch = "master"
90107 name = "google.golang.org/api"
91− packages = ["admin/directory/v1","gensupport","googleapi","googleapi/internal/uritemplates"]
108+ packages = [
109+ "admin/directory/v1",
110+ "gensupport",
111+ "googleapi",
112+ "googleapi/internal/uritemplates"
113+ ]
92114 revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
93115
94116[[projects]]
95117 name = "google.golang.org/appengine"
96− packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
118+ packages = [
119+ ".",
120+ "internal",
121+ "internal/app_identity",
122+ "internal/base",
123+ "internal/datastore",
124+ "internal/log",
125+ "internal/modules",
126+ "internal/remote_api",
127+ "internal/urlfetch",
128+ "urlfetch"
129+ ]
97130 revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
98131 version = "v1.0.0"
99132
@@ -105,13 +138,17 @@
105138
106139[[projects]]
107140 name = "gopkg.in/square/go-jose.v2"
108− packages = [".","cipher","json"]
141+ packages = [
142+ ".",
143+ "cipher",
144+ "json"
145+ ]
109146 revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
110147 version = "v2.1.3"
111148
112149[solve-meta]
113150 analyzer-name = "dep"
114151 analyzer-version = 1
115− inputs-digest = "efab48a0e196c2a849bfbe9aa02d2ae28d281ce1bfe9f23720d648858eefc8e6"
152+ inputs-digest = "b502c41a61115d14d6379be26b0300f65d173bdad852f0170d387ebf2d7ec173"
116153 solver-name = "gps-cdcl"
117154 solver-version = 1
Gopkg.toml+4
@@ -38,3 +38,7 @@
3838[[constraint]]
3939 name = "gopkg.in/fsnotify.v1"
4040 version = "~1.2.0"
41+
42+[[constraint]]
43+ branch = "master"
44+ name = "golang.org/x/crypto"
htpasswd.go+16−8
@@ -7,10 +7,12 @@ import (
77 "io"
88 "log"
99 "os"
10+
11+ "golang.org/x/crypto/bcrypt"
1012)
1113
12−// lookup passwords in a htpasswd file
13−// The entries must have been created with -s for SHA encryption
14+// Lookup passwords in a htpasswd file
15+// Passwords must be generated with -B for bcrypt or -s for SHA1.
1416
1517type HtpasswdFile struct {
1618 Users map[string]string
@@ -47,14 +49,20 @@ func (h *HtpasswdFile) Validate(user string, password string) bool {
4749 if !exists {
4850 return false
4951 }
50− if realPassword[:5] == "{SHA}" {
52+
53+ shaPrefix := realPassword[:5]
54+ if shaPrefix == "{SHA}" {
55+ shaValue := realPassword[5:]
5156 d := sha1.New()
5257 d.Write([]byte(password))
53− if realPassword[5:] == base64.StdEncoding.EncodeToString(d.Sum(nil)) {
54− return true
55− }
56− } else {
57− log.Printf("Invalid htpasswd entry for %s. Must be a SHA entry.", user)
58+ return shaValue == base64.StdEncoding.EncodeToString(d.Sum(nil))
5859 }
60+
61+ bcryptPrefix := realPassword[:4]
62+ if bcryptPrefix == "$2a$" || bcryptPrefix == "$2b$" || bcryptPrefix == "$2x$" || bcryptPrefix == "$2y$" {
63+ return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password)) == nil
64+ }
65+
66+ log.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
5967 return false
6068}
htpasswd_test.go+23−2
@@ -2,11 +2,14 @@ package main
22
33import (
44 "bytes"
5− "github.com/stretchr/testify/assert"
5+ "fmt"
66 "testing"
7+
8+ "github.com/stretchr/testify/assert"
9+ "golang.org/x/crypto/bcrypt"
710)
811
9−func TestHtpasswd(t *testing.T) {
12+func TestSHA(t *testing.T) {
1013 file := bytes.NewBuffer([]byte("testuser:{SHA}PaVBVZkYqAjCQCu6UBL2xgsnZhw=\n"))
1114 h, err := NewHtpasswd(file)
1215 assert.Equal(t, err, nil)
@@ -14,3 +17,21 @@ func TestHtpasswd(t *testing.T) {
1417 valid := h.Validate("testuser", "asdf")
1518 assert.Equal(t, valid, true)
1619}
20+
21+func TestBcrypt(t *testing.T) {
22+ hash1, err := bcrypt.GenerateFromPassword([]byte("password"), 1)
23+ hash2, err := bcrypt.GenerateFromPassword([]byte("top-secret"), 2)
24+ assert.Equal(t, err, nil)
25+
26+ contents := fmt.Sprintf("testuser1:%s\ntestuser2:%s\n", hash1, hash2)
27+ file := bytes.NewBuffer([]byte(contents))
28+
29+ h, err := NewHtpasswd(file)
30+ assert.Equal(t, err, nil)
31+
32+ valid := h.Validate("testuser1", "password")
33+ assert.Equal(t, valid, true)
34+
35+ valid = h.Validate("testuser2", "top-secret")
36+ assert.Equal(t, valid, true)
37+}
main.go+1−1
@@ -52,7 +52,7 @@ func main() {
5252 flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
5353 flagSet.String("client-secret", "", "the OAuth Client Secret")
5454 flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
55− flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption")
55+ 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")
5656 flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
5757 flagSet.String("custom-templates-dir", "", "path to custom html templates")
5858 flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")