git.iko.soy/typeable/oauth2-proxy
on master help sign in
Support bcrypt passwords in htpasswd
008ffaeBarret Rennie
Gopkg.lock+45−8
@@ -58,7 +58,10 @@
58[[projects]]
58[[projects]]
59 branch = "master"
59 branch = "master"
60 name = "github.com/pquerna/cachecontrol"
60 name = "github.com/pquerna/cachecontrol"
61+ packages = [
62+ ".",
61− packages = [".","cacheobject"]
63+ "cacheobject"
64+ ]
62 revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
65 revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
63
66
64[[projects]]
67[[projects]]
@@ -70,30 +73,60 @@
70[[projects]]
73[[projects]]
71 branch = "master"
74 branch = "master"
72 name = "golang.org/x/crypto"
75 name = "golang.org/x/crypto"
76+ packages = [
77+ "bcrypt",
78+ "blowfish",
79+ "ed25519",
73− packages = ["ed25519","ed25519/internal/edwards25519"]
80+ "ed25519/internal/edwards25519"
81+ ]
74 revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
82 revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
75
83
76[[projects]]
84[[projects]]
77 branch = "master"
85 branch = "master"
78 name = "golang.org/x/net"
86 name = "golang.org/x/net"
87+ packages = [
88+ "context",
79− packages = ["context","context/ctxhttp"]
89+ "context/ctxhttp"
90+ ]
80 revision = "9dfe39835686865bff950a07b394c12a98ddc811"
91 revision = "9dfe39835686865bff950a07b394c12a98ddc811"
81
92
82[[projects]]
93[[projects]]
83 branch = "master"
94 branch = "master"
84 name = "golang.org/x/oauth2"
95 name = "golang.org/x/oauth2"
85− packages = [".","google","internal","jws","jwt"]
96+ packages = [
97+ ".",
98+ "google",
99+ "internal",
100+ "jws",
101+ "jwt"
102+ ]
86 revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
103 revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
87
104
88[[projects]]
105[[projects]]
89 branch = "master"
106 branch = "master"
90 name = "google.golang.org/api"
107 name = "google.golang.org/api"
108+ packages = [
109+ "admin/directory/v1",
110+ "gensupport",
111+ "googleapi",
91− packages = ["admin/directory/v1","gensupport","googleapi","googleapi/internal/uritemplates"]
112+ "googleapi/internal/uritemplates"
113+ ]
92 revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
114 revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
93
115
94[[projects]]
116[[projects]]
95 name = "google.golang.org/appengine"
117 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+ ]
97 revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
130 revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
98 version = "v1.0.0"
131 version = "v1.0.0"
99
132
@@ -105,13 +138,17 @@
105
138
106[[projects]]
139[[projects]]
107 name = "gopkg.in/square/go-jose.v2"
140 name = "gopkg.in/square/go-jose.v2"
108− packages = [".","cipher","json"]
141+ packages = [
142+ ".",
143+ "cipher",
144+ "json"
145+ ]
109 revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
146 revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
110 version = "v2.1.3"
147 version = "v2.1.3"
111
148
112[solve-meta]
149[solve-meta]
113 analyzer-name = "dep"
150 analyzer-name = "dep"
114 analyzer-version = 1
151 analyzer-version = 1
115− inputs-digest = "efab48a0e196c2a849bfbe9aa02d2ae28d281ce1bfe9f23720d648858eefc8e6"
152+ inputs-digest = "b502c41a61115d14d6379be26b0300f65d173bdad852f0170d387ebf2d7ec173"
116 solver-name = "gps-cdcl"
153 solver-name = "gps-cdcl"
117 solver-version = 1
154 solver-version = 1
Gopkg.toml+4
@@ -38,3 +38,7 @@
38[[constraint]]
38[[constraint]]
39 name = "gopkg.in/fsnotify.v1"
39 name = "gopkg.in/fsnotify.v1"
40 version = "~1.2.0"
40 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 (
7 "io"
7 "io"
8 "log"
8 "log"
9 "os"
9 "os"
10+
11+ "golang.org/x/crypto/bcrypt"
10)
12)
11
13
12−// lookup passwords in a htpasswd file
14+// Lookup passwords in a htpasswd file
13−// The entries must have been created with -s for SHA encryption
15+// Passwords must be generated with -B for bcrypt or -s for SHA1.
14
16
15type HtpasswdFile struct {
17type HtpasswdFile struct {
16 Users map[string]string
18 Users map[string]string
@@ -47,14 +49,20 @@ func (h *HtpasswdFile) Validate(user string, password string) bool {
47 if !exists {
49 if !exists {
48 return false
50 return false
49 }
51 }
50− if realPassword[:5] == "{SHA}" {
52+
53+ shaPrefix := realPassword[:5]
54+ if shaPrefix == "{SHA}" {
55+ shaValue := realPassword[5:]
51 d := sha1.New()
56 d := sha1.New()
52 d.Write([]byte(password))
57 d.Write([]byte(password))
53− if realPassword[5:] == base64.StdEncoding.EncodeToString(d.Sum(nil)) {
58+ return shaValue == 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 }
59 }
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)
59 return false
67 return false
60}
68}
htpasswd_test.go+23−2
@@ -2,11 +2,14 @@ package main
2
2
3import (
3import (
4 "bytes"
4 "bytes"
5− "github.com/stretchr/testify/assert"
5+ "fmt"
6 "testing"
6 "testing"
7+
8+ "github.com/stretchr/testify/assert"
9+ "golang.org/x/crypto/bcrypt"
7)
10)
8
11
9−func TestHtpasswd(t *testing.T) {
12+func TestSHA(t *testing.T) {
10 file := bytes.NewBuffer([]byte("testuser:{SHA}PaVBVZkYqAjCQCu6UBL2xgsnZhw=\n"))
13 file := bytes.NewBuffer([]byte("testuser:{SHA}PaVBVZkYqAjCQCu6UBL2xgsnZhw=\n"))
11 h, err := NewHtpasswd(file)
14 h, err := NewHtpasswd(file)
12 assert.Equal(t, err, nil)
15 assert.Equal(t, err, nil)
@@ -14,3 +17,21 @@ func TestHtpasswd(t *testing.T) {
14 valid := h.Validate("testuser", "asdf")
17 valid := h.Validate("testuser", "asdf")
15 assert.Equal(t, valid, true)
18 assert.Equal(t, valid, true)
16}
19}
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() {
52 flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
52 flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
53 flagSet.String("client-secret", "", "the OAuth Client Secret")
53 flagSet.String("client-secret", "", "the OAuth Client Secret")
54 flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
54 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")
56 flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
56 flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
57 flagSet.String("custom-templates-dir", "", "path to custom html templates")
57 flagSet.String("custom-templates-dir", "", "path to custom html templates")
58 flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
58 flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")