Skip to content

Commit 30d6364

Browse files
caddyauth: Drop support for scrypt (#6091)
1 parent 21744b6 commit 30d6364

File tree

4 files changed

+22
-142
lines changed

4 files changed

+22
-142
lines changed

modules/caddyhttp/caddyauth/basicauth.go

+12-24
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
108108

109109
acct.Username = repl.ReplaceAll(acct.Username, "")
110110
acct.Password = repl.ReplaceAll(acct.Password, "")
111-
acct.Salt = repl.ReplaceAll(acct.Salt, "")
112111

113112
if acct.Username == "" || acct.Password == "" {
114113
return fmt.Errorf("account %d: username and password are required", i)
@@ -127,13 +126,6 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
127126
}
128127
}
129128

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-
137129
hba.Accounts[acct.Username] = acct
138130
}
139131
hba.AccountList = nil // allow GC to deallocate
@@ -172,7 +164,7 @@ func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request)
172164

173165
func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
174166
compare := func() (bool, error) {
175-
return hba.Hash.Compare(account.password, plaintextPassword, account.salt)
167+
return hba.Hash.Compare(account.password, plaintextPassword)
176168
}
177169

178170
// if no caching is enabled, simply return the result of hashing + comparing
@@ -181,7 +173,7 @@ func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []by
181173
}
182174

183175
// 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...))
185177

186178
// fast track: if the result of the input is already cached, use it
187179
hba.HashCache.mu.RLock()
@@ -231,7 +223,7 @@ type Cache struct {
231223
mu *sync.RWMutex
232224
g *singleflight.Group
233225

234-
// map of concatenated hashed password + plaintext password + salt, to result
226+
// map of concatenated hashed password + plaintext password, to result
235227
cache map[string]bool
236228
}
237229

@@ -274,37 +266,33 @@ func (c *Cache) makeRoom() {
274266
// comparison.
275267
type Comparer interface {
276268
// 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
279271
// there is a technical/configuration error.
280-
Compare(hashedPassword, plaintextPassword, salt []byte) (bool, error)
272+
Compare(hashedPassword, plaintextPassword []byte) (bool, error)
281273
}
282274

283275
// 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
286277
// this interface can be used with the hash-password
287278
// subcommand as well as benefitting from anti-timing
288279
// features. A hasher also returns a fake hash which
289280
// can be used for timing side-channel mitigation.
290281
type Hasher interface {
291-
Hash(plaintext, salt []byte) ([]byte, error)
282+
Hash(plaintext []byte) ([]byte, error)
292283
FakeHash() []byte
293284
}
294285

295-
// Account contains a username, password, and salt (if applicable).
286+
// Account contains a username and password.
296287
type Account struct {
297288
// A user's username.
298289
Username string `json:"username"`
299290

300-
// The user's hashed password, base64-encoded.
291+
// The user's hashed password, in Modular Crypt Format (with `$` prefix)
292+
// or base64-encoded.
301293
Password string `json:"password"`
302294

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
308296
}
309297

310298
// Interface guards

modules/caddyhttp/caddyauth/caddyfile.go

+3-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func init() {
2929
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
3030
//
3131
// basic_auth [<matcher>] [<hash_algorithm> [<realm>]] {
32-
// <username> <hashed_password_base64> [<salt_base64>]
32+
// <username> <hashed_password>
3333
// ...
3434
// }
3535
//
@@ -64,8 +64,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
6464
switch hashName {
6565
case "bcrypt":
6666
cmp = BcryptHash{}
67-
case "scrypt":
68-
cmp = ScryptHash{}
6967
default:
7068
return nil, h.Errf("unrecognized hash algorithm: %s", hashName)
7169
}
@@ -75,8 +73,8 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
7573
for h.NextBlock(0) {
7674
username := h.Val()
7775

78-
var b64Pwd, b64Salt string
79-
h.Args(&b64Pwd, &b64Salt)
76+
var b64Pwd string
77+
h.Args(&b64Pwd)
8078
if h.NextArg() {
8179
return nil, h.ArgErr()
8280
}
@@ -88,7 +86,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
8886
ba.AccountList = append(ba.AccountList, Account{
8987
Username: username,
9088
Password: b64Pwd,
91-
Salt: b64Salt,
9289
})
9390
}
9491

modules/caddyhttp/caddyauth/command.go

+3-17
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package caddyauth
1717
import (
1818
"bufio"
1919
"bytes"
20-
"encoding/base64"
2120
"fmt"
2221
"os"
2322
"os/signal"
@@ -33,7 +32,7 @@ import (
3332
func init() {
3433
caddycmd.RegisterCommand(caddycmd.Command{
3534
Name: "hash-password",
36-
Usage: "[--algorithm <name>] [--salt <string>] [--plaintext <password>]",
35+
Usage: "[--plaintext <password>] [--algorithm <name>]",
3736
Short: "Hashes a password and writes base64",
3837
Long: `
3938
Convenient way to hash a plaintext password. The resulting
@@ -43,17 +42,10 @@ hash is written to stdout as a base64 string.
4342
Caddy is attached to a controlling tty, the plaintext will
4443
not be echoed.
4544
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.
5346
`,
5447
CobraFunc: func(cmd *cobra.Command) {
5548
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
56-
cmd.Flags().StringP("salt", "s", "", "The password salt")
5749
cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
5850
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
5951
},
@@ -65,7 +57,6 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
6557

6658
algorithm := fs.String("algorithm")
6759
plaintext := []byte(fs.String("plaintext"))
68-
salt := []byte(fs.String("salt"))
6960

7061
if len(plaintext) == 0 {
7162
fd := int(os.Stdin.Fd())
@@ -117,13 +108,8 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
117108
var hashString string
118109
switch algorithm {
119110
case "bcrypt":
120-
hash, err = BcryptHash{}.Hash(plaintext, nil)
111+
hash, err = BcryptHash{}.Hash(plaintext)
121112
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)
127113
default:
128114
return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
129115
}

modules/caddyhttp/caddyauth/hashes.go

+4-95
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,13 @@
1515
package caddyauth
1616

1717
import (
18-
"crypto/subtle"
19-
"encoding/base64"
20-
2118
"golang.org/x/crypto/bcrypt"
22-
"golang.org/x/crypto/scrypt"
2319

2420
"github.com/caddyserver/caddy/v2"
2521
)
2622

2723
func init() {
2824
caddy.RegisterModule(BcryptHash{})
29-
caddy.RegisterModule(ScryptHash{})
3025
}
3126

3227
// BcryptHash implements the bcrypt hash.
@@ -41,7 +36,7 @@ func (BcryptHash) CaddyModule() caddy.ModuleInfo {
4136
}
4237

4338
// Compare compares passwords.
44-
func (BcryptHash) Compare(hashed, plaintext, _ []byte) (bool, error) {
39+
func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
4540
err := bcrypt.CompareHashAndPassword(hashed, plaintext)
4641
if err == bcrypt.ErrMismatchedHashAndPassword {
4742
return false, nil
@@ -53,7 +48,7 @@ func (BcryptHash) Compare(hashed, plaintext, _ []byte) (bool, error) {
5348
}
5449

5550
// Hash hashes plaintext using a random salt.
56-
func (BcryptHash) Hash(plaintext, _ []byte) ([]byte, error) {
51+
func (BcryptHash) Hash(plaintext []byte) ([]byte, error) {
5752
return bcrypt.GenerateFromPassword(plaintext, 14)
5853
}
5954

@@ -64,94 +59,8 @@ func (BcryptHash) FakeHash() []byte {
6459
return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6")
6560
}
6661

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-
15062
// Interface guards
15163
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)
15766
)

0 commit comments

Comments
 (0)