Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 17:00 UTC
caddyauth: Drop support for `scrypt` (#6091)
Francis Lavoie committed
Feb 12, 2024 at 14:33 UTC
30d63648f526c1f994173ce24ccf7fe71ca24365
4 files changed
+22
-142
modules/caddyhttp/caddyauth/basicauth.go
+12
-24
@@ -108,7 +108,6 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
108
109
acct.Username = repl.ReplaceAll(acct.Username, "")
110
acct.Password = repl.ReplaceAll(acct.Password, "")
111
- acct.Salt = repl.ReplaceAll(acct.Salt, "")
111
112
if acct.Username == "" || acct.Password == "" {
113
return fmt.Errorf("account %d: username and password are required", i)
@@ -127,13 +126,6 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
126
}
127
}
128
130
- if acct.Salt != "" {
131
- acct.salt, err = base64.StdEncoding.DecodeString(acct.Salt)
132
- if err != nil {
133
- return fmt.Errorf("base64-decoding salt: %v", err)
134
- }
135
- }
136
-
129
hba.Accounts[acct.Username] = acct
130
}
131
hba.AccountList = nil // allow GC to deallocate
@@ -172,7 +164,7 @@ func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request)
164
165
func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
166
compare := func() (bool, error) {
175
- return hba.Hash.Compare(account.password, plaintextPassword, account.salt)
167
+ return hba.Hash.Compare(account.password, plaintextPassword)
168
}
169
170
// if no caching is enabled, simply return the result of hashing + comparing
@@ -181,7 +173,7 @@ func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []by
173
}
174
175
// compute a cache key that is unique for these input parameters
184
- cacheKey := hex.EncodeToString(append(append(account.password, account.salt...), plaintextPassword...))
176
+ cacheKey := hex.EncodeToString(append(account.password, plaintextPassword...))
177
178
// fast track: if the result of the input is already cached, use it
179
hba.HashCache.mu.RLock()
@@ -231,7 +223,7 @@ type Cache struct {
223
mu *sync.RWMutex
224
g *singleflight.Group
225
234
- // map of concatenated hashed password + plaintext password + salt, to result
226
+ // map of concatenated hashed password + plaintext password, to result
227
cache map[string]bool
228
}
229
@@ -274,37 +266,33 @@ func (c *Cache) makeRoom() {
266
// comparison.
267
type Comparer interface {
268
// Compare returns true if the result of hashing
277
- // plaintextPassword with salt is hashedPassword,
278
- // false otherwise. An error is returned only if
269
+ // plaintextPassword is hashedPassword, false
270
+ // otherwise. An error is returned only if
271
// there is a technical/configuration error.
280
- Compare(hashedPassword, plaintextPassword, salt []byte) (bool, error)
272
+ Compare(hashedPassword, plaintextPassword []byte) (bool, error)
273
}
274
275
// Hasher is a type that can generate a secure hash
284
-// given a plaintext and optional salt (for algorithms
285
-// that require a salt). Hashing modules which implement
276
+// given a plaintext. Hashing modules which implement
277
// this interface can be used with the hash-password
278
// subcommand as well as benefitting from anti-timing
279
// features. A hasher also returns a fake hash which
280
// can be used for timing side-channel mitigation.
281
type Hasher interface {
291
- Hash(plaintext, salt []byte) ([]byte, error)
282
+ Hash(plaintext []byte) ([]byte, error)
283
FakeHash() []byte
284
}
285
295
-// Account contains a username, password, and salt (if applicable).
286
+// Account contains a username and password.
287
type Account struct {
288
// A user's username.
289
Username string `json:"username"`
290
300
- // The user's hashed password, base64-encoded.
291
+ // The user's hashed password, in Modular Crypt Format (with `$` prefix)
292
+ // or base64-encoded.
293
Password string `json:"password"`
294
303
- // The user's password salt, base64-encoded; for
304
- // algorithms where external salt is needed.
305
- Salt string `json:"salt,omitempty"`
306
-
307
- password, salt []byte
295
+ password []byte
296
}
297
298
// Interface guards
modules/caddyhttp/caddyauth/caddyfile.go
+3
-6
@@ -29,7 +29,7 @@ func init() {
29
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
30
//
31
// basic_auth [<matcher>] [<hash_algorithm> [<realm>]] {
32
-// <username> <hashed_password_base64> [<salt_base64>]
32
+// <username> <hashed_password>
33
// ...
34
// }
35
//
@@ -64,8 +64,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
64
switch hashName {
65
case "bcrypt":
66
cmp = BcryptHash{}
67
- case "scrypt":
68
- cmp = ScryptHash{}
67
default:
68
return nil, h.Errf("unrecognized hash algorithm: %s", hashName)
69
}
@@ -75,8 +73,8 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
73
for h.NextBlock(0) {
74
username := h.Val()
75
78
- var b64Pwd, b64Salt string
79
- h.Args(&b64Pwd, &b64Salt)
76
+ var b64Pwd string
77
+ h.Args(&b64Pwd)
78
if h.NextArg() {
79
return nil, h.ArgErr()
80
}
@@ -88,7 +86,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
86
ba.AccountList = append(ba.AccountList, Account{
87
Username: username,
88
Password: b64Pwd,
91
- Salt: b64Salt,
89
})
90
}
91
modules/caddyhttp/caddyauth/command.go
+3
-17
@@ -17,7 +17,6 @@ package caddyauth
17
import (
18
"bufio"
19
"bytes"
20
- "encoding/base64"
20
"fmt"
21
"os"
22
"os/signal"
@@ -33,7 +32,7 @@ import (
32
func init() {
33
caddycmd.RegisterCommand(caddycmd.Command{
34
Name: "hash-password",
36
- Usage: "[--algorithm <name>] [--salt <string>] [--plaintext <password>]",
35
+ Usage: "[--plaintext <password>] [--algorithm <name>]",
36
Short: "Hashes a password and writes base64",
37
Long: `
38
Convenient way to hash a plaintext password. The resulting
@@ -43,17 +42,10 @@ hash is written to stdout as a base64 string.
42
Caddy is attached to a controlling tty, the plaintext will
43
not be echoed.
44
46
---algorithm may be bcrypt or scrypt. If scrypt, the default
47
-parameters are used.
48
-
49
-Use the --salt flag for algorithms which require a salt to
50
-be provided (scrypt).
51
-
52
-Note that scrypt is deprecated. Please use 'bcrypt' instead.
45
+--algorithm currently only supports 'bcrypt', and is the default.
46
`,
47
CobraFunc: func(cmd *cobra.Command) {
48
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
56
- cmd.Flags().StringP("salt", "s", "", "The password salt")
49
cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
50
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
51
},
@@ -65,7 +57,6 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
57
58
algorithm := fs.String("algorithm")
59
plaintext := []byte(fs.String("plaintext"))
68
- salt := []byte(fs.String("salt"))
60
61
if len(plaintext) == 0 {
62
fd := int(os.Stdin.Fd())
@@ -117,13 +108,8 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
108
var hashString string
109
switch algorithm {
110
case "bcrypt":
120
- hash, err = BcryptHash{}.Hash(plaintext, nil)
111
+ hash, err = BcryptHash{}.Hash(plaintext)
112
hashString = string(hash)
122
- case "scrypt":
123
- def := ScryptHash{}
124
- def.SetDefaults()
125
- hash, err = def.Hash(plaintext, salt)
126
- hashString = base64.StdEncoding.EncodeToString(hash)
113
default:
114
return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
115
}
modules/caddyhttp/caddyauth/hashes.go
+4
-95
@@ -15,18 +15,13 @@
15
package caddyauth
16
17
import (
18
- "crypto/subtle"
19
- "encoding/base64"
20
-
18
"golang.org/x/crypto/bcrypt"
22
- "golang.org/x/crypto/scrypt"
19
20
"github.com/caddyserver/caddy/v2"
21
)
22
23
func init() {
24
caddy.RegisterModule(BcryptHash{})
29
- caddy.RegisterModule(ScryptHash{})
25
}
26
27
// BcryptHash implements the bcrypt hash.
@@ -41,7 +36,7 @@ func (BcryptHash) CaddyModule() caddy.ModuleInfo {
36
}
37
38
// Compare compares passwords.
44
-func (BcryptHash) Compare(hashed, plaintext, _ []byte) (bool, error) {
39
+func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
40
err := bcrypt.CompareHashAndPassword(hashed, plaintext)
41
if err == bcrypt.ErrMismatchedHashAndPassword {
42
return false, nil
@@ -53,7 +48,7 @@ func (BcryptHash) Compare(hashed, plaintext, _ []byte) (bool, error) {
48
}
49
50
// Hash hashes plaintext using a random salt.
56
-func (BcryptHash) Hash(plaintext, _ []byte) ([]byte, error) {
51
+func (BcryptHash) Hash(plaintext []byte) ([]byte, error) {
52
return bcrypt.GenerateFromPassword(plaintext, 14)
53
}
54
@@ -64,94 +59,8 @@ func (BcryptHash) FakeHash() []byte {
59
return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6")
60
}
61
67
-// ScryptHash implements the scrypt KDF as a hash.
68
-//
69
-// DEPRECATED, please use 'bcrypt' instead.
70
-type ScryptHash struct {
71
- // scrypt's N parameter. If unset or 0, a safe default is used.
72
- N int `json:"N,omitempty"`
73
-
74
- // scrypt's r parameter. If unset or 0, a safe default is used.
75
- R int `json:"r,omitempty"`
76
-
77
- // scrypt's p parameter. If unset or 0, a safe default is used.
78
- P int `json:"p,omitempty"`
79
-
80
- // scrypt's key length parameter (in bytes). If unset or 0, a
81
- // safe default is used.
82
- KeyLength int `json:"key_length,omitempty"`
83
-}
84
-
85
-// CaddyModule returns the Caddy module information.
86
-func (ScryptHash) CaddyModule() caddy.ModuleInfo {
87
- return caddy.ModuleInfo{
88
- ID: "http.authentication.hashes.scrypt",
89
- New: func() caddy.Module { return new(ScryptHash) },
90
- }
91
-}
92
-
93
-// Provision sets up s.
94
-func (s *ScryptHash) Provision(ctx caddy.Context) error {
95
- s.SetDefaults()
96
- ctx.Logger().Warn("use of 'scrypt' is deprecated, please use 'bcrypt' instead")
97
- return nil
98
-}
99
-
100
-// SetDefaults sets safe default parameters, but does
101
-// not overwrite existing values. Each default parameter
102
-// is set independently; it does not check to ensure
103
-// that r*p < 2^30. The defaults chosen are those as
104
-// recommended in 2019 by
105
-// https://godoc.org/golang.org/x/crypto/scrypt.
106
-func (s *ScryptHash) SetDefaults() {
107
- if s.N == 0 {
108
- s.N = 32768
109
- }
110
- if s.R == 0 {
111
- s.R = 8
112
- }
113
- if s.P == 0 {
114
- s.P = 1
115
- }
116
- if s.KeyLength == 0 {
117
- s.KeyLength = 32
118
- }
119
-}
120
-
121
-// Compare compares passwords.
122
-func (s ScryptHash) Compare(hashed, plaintext, salt []byte) (bool, error) {
123
- ourHash, err := scrypt.Key(plaintext, salt, s.N, s.R, s.P, s.KeyLength)
124
- if err != nil {
125
- return false, err
126
- }
127
- if hashesMatch(hashed, ourHash) {
128
- return true, nil
129
- }
130
- return false, nil
131
-}
132
-
133
-// Hash hashes plaintext using the given salt.
134
-func (s ScryptHash) Hash(plaintext, salt []byte) ([]byte, error) {
135
- return scrypt.Key(plaintext, salt, s.N, s.R, s.P, s.KeyLength)
136
-}
137
-
138
-// FakeHash returns a fake hash.
139
-func (ScryptHash) FakeHash() []byte {
140
- // hashed with the following command:
141
- // caddy hash-password --plaintext "antitiming" --salt "fakesalt" --algorithm "scrypt"
142
- bytes, _ := base64.StdEncoding.DecodeString("kFbjiVemlwK/ZS0tS6/UQqEDeaNMigyCs48KEsGUse8=")
143
- return bytes
144
-}
145
-
146
-func hashesMatch(pwdHash1, pwdHash2 []byte) bool {
147
- return subtle.ConstantTimeCompare(pwdHash1, pwdHash2) == 1
148
-}
149
-
62
// Interface guards
63
var (
152
- _ Comparer = (*BcryptHash)(nil)
153
- _ Comparer = (*ScryptHash)(nil)
154
- _ Hasher = (*BcryptHash)(nil)
155
- _ Hasher = (*ScryptHash)(nil)
156
- _ caddy.Provisioner = (*ScryptHash)(nil)
64
+ _ Comparer = (*BcryptHash)(nil)
65
+ _ Hasher = (*BcryptHash)(nil)
66
)