|
1 | 1 | package auth |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "crypto/sha1" |
5 | 6 | "crypto/subtle" |
6 | 7 | "encoding/base64" |
| 8 | + "errors" |
7 | 9 | "net/http" |
8 | 10 | "strings" |
| 11 | + |
| 12 | + "golang.org/x/crypto/bcrypt" |
| 13 | +) |
| 14 | + |
| 15 | +type compareFunc func(hashedPassword, password []byte) error |
| 16 | + |
| 17 | +var ( |
| 18 | + errMismatchedHashAndPassword = errors.New("mismatched hash and password") |
| 19 | + |
| 20 | + compareFuncs = []struct { |
| 21 | + prefix string |
| 22 | + compare compareFunc |
| 23 | + }{ |
| 24 | + {"", compareMD5HashAndPassword}, // default compareFunc |
| 25 | + {"{SHA}", compareShaHashAndPassword}, |
| 26 | + {"$2y$", bcrypt.CompareHashAndPassword}, |
| 27 | + } |
9 | 28 | ) |
10 | 29 |
|
11 | 30 | type BasicAuth struct { |
@@ -34,28 +53,46 @@ func (a *BasicAuth) CheckAuth(r *http.Request) string { |
34 | 53 | if len(pair) != 2 { |
35 | 54 | return "" |
36 | 55 | } |
37 | | - passwd := a.Secrets(pair[0], a.Realm) |
38 | | - if passwd == "" { |
| 56 | + user, password := pair[0], pair[1] |
| 57 | + secret := a.Secrets(user, a.Realm) |
| 58 | + if secret == "" { |
39 | 59 | return "" |
40 | 60 | } |
41 | | - if strings.HasPrefix(passwd, "{SHA}") { |
42 | | - d := sha1.New() |
43 | | - d.Write([]byte(pair[1])) |
44 | | - if subtle.ConstantTimeCompare([]byte(passwd[5:]), []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 { |
45 | | - return "" |
46 | | - } |
47 | | - } else { |
48 | | - e := NewMD5Entry(passwd) |
49 | | - if e == nil { |
50 | | - return "" |
51 | | - } |
52 | | - if subtle.ConstantTimeCompare([]byte(passwd), MD5Crypt([]byte(pair[1]), e.Salt, e.Magic)) != 1 { |
53 | | - return "" |
| 61 | + compare := compareFuncs[0].compare |
| 62 | + for _, cmp := range compareFuncs[1:] { |
| 63 | + if strings.HasPrefix(secret, cmp.prefix) { |
| 64 | + compare = cmp.compare |
| 65 | + break |
54 | 66 | } |
55 | 67 | } |
| 68 | + if compare([]byte(secret), []byte(password)) != nil { |
| 69 | + return "" |
| 70 | + } |
56 | 71 | return pair[0] |
57 | 72 | } |
58 | 73 |
|
| 74 | +func compareShaHashAndPassword(hashedPassword, password []byte) error { |
| 75 | + d := sha1.New() |
| 76 | + d.Write(password) |
| 77 | + if subtle.ConstantTimeCompare(hashedPassword[5:], []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 { |
| 78 | + return errMismatchedHashAndPassword |
| 79 | + } |
| 80 | + return nil |
| 81 | +} |
| 82 | + |
| 83 | +func compareMD5HashAndPassword(hashedPassword, password []byte) error { |
| 84 | + parts := bytes.SplitN(hashedPassword, []byte("$"), 4) |
| 85 | + if len(parts) != 4 { |
| 86 | + return errMismatchedHashAndPassword |
| 87 | + } |
| 88 | + magic := []byte("$" + string(parts[1]) + "$") |
| 89 | + salt := parts[2] |
| 90 | + if subtle.ConstantTimeCompare(hashedPassword, MD5Crypt(password, salt, magic)) != 1 { |
| 91 | + return errMismatchedHashAndPassword |
| 92 | + } |
| 93 | + return nil |
| 94 | +} |
| 95 | + |
59 | 96 | /* |
60 | 97 | http.Handler for BasicAuth which initiates the authentication process |
61 | 98 | (or requires reauthentication). |
|
0 commit comments