@marcel91200 / caddy Mirror · read-only / commits / 30d63648
Fork 0

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 )