mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
caddyauth: Drop support for scrypt
(#6091)
This commit is contained in:
parent
21744b6c4c
commit
30d63648f5
4 changed files with 22 additions and 142 deletions
|
@ -108,7 +108,6 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
acct.Username = repl.ReplaceAll(acct.Username, "")
|
acct.Username = repl.ReplaceAll(acct.Username, "")
|
||||||
acct.Password = repl.ReplaceAll(acct.Password, "")
|
acct.Password = repl.ReplaceAll(acct.Password, "")
|
||||||
acct.Salt = repl.ReplaceAll(acct.Salt, "")
|
|
||||||
|
|
||||||
if acct.Username == "" || acct.Password == "" {
|
if acct.Username == "" || acct.Password == "" {
|
||||||
return fmt.Errorf("account %d: username and password are required", i)
|
return fmt.Errorf("account %d: username and password are required", i)
|
||||||
|
@ -127,13 +126,6 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if acct.Salt != "" {
|
|
||||||
acct.salt, err = base64.StdEncoding.DecodeString(acct.Salt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("base64-decoding salt: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hba.Accounts[acct.Username] = acct
|
hba.Accounts[acct.Username] = acct
|
||||||
}
|
}
|
||||||
hba.AccountList = nil // allow GC to deallocate
|
hba.AccountList = nil // allow GC to deallocate
|
||||||
|
@ -172,7 +164,7 @@ func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request)
|
||||||
|
|
||||||
func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
|
func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
|
||||||
compare := func() (bool, error) {
|
compare := func() (bool, error) {
|
||||||
return hba.Hash.Compare(account.password, plaintextPassword, account.salt)
|
return hba.Hash.Compare(account.password, plaintextPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no caching is enabled, simply return the result of hashing + comparing
|
// if no caching is enabled, simply return the result of hashing + comparing
|
||||||
|
@ -181,7 +173,7 @@ func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []by
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute a cache key that is unique for these input parameters
|
// compute a cache key that is unique for these input parameters
|
||||||
cacheKey := hex.EncodeToString(append(append(account.password, account.salt...), plaintextPassword...))
|
cacheKey := hex.EncodeToString(append(account.password, plaintextPassword...))
|
||||||
|
|
||||||
// fast track: if the result of the input is already cached, use it
|
// fast track: if the result of the input is already cached, use it
|
||||||
hba.HashCache.mu.RLock()
|
hba.HashCache.mu.RLock()
|
||||||
|
@ -231,7 +223,7 @@ type Cache struct {
|
||||||
mu *sync.RWMutex
|
mu *sync.RWMutex
|
||||||
g *singleflight.Group
|
g *singleflight.Group
|
||||||
|
|
||||||
// map of concatenated hashed password + plaintext password + salt, to result
|
// map of concatenated hashed password + plaintext password, to result
|
||||||
cache map[string]bool
|
cache map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,37 +266,33 @@ func (c *Cache) makeRoom() {
|
||||||
// comparison.
|
// comparison.
|
||||||
type Comparer interface {
|
type Comparer interface {
|
||||||
// Compare returns true if the result of hashing
|
// Compare returns true if the result of hashing
|
||||||
// plaintextPassword with salt is hashedPassword,
|
// plaintextPassword is hashedPassword, false
|
||||||
// false otherwise. An error is returned only if
|
// otherwise. An error is returned only if
|
||||||
// there is a technical/configuration error.
|
// there is a technical/configuration error.
|
||||||
Compare(hashedPassword, plaintextPassword, salt []byte) (bool, error)
|
Compare(hashedPassword, plaintextPassword []byte) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hasher is a type that can generate a secure hash
|
// Hasher is a type that can generate a secure hash
|
||||||
// given a plaintext and optional salt (for algorithms
|
// given a plaintext. Hashing modules which implement
|
||||||
// that require a salt). Hashing modules which implement
|
|
||||||
// this interface can be used with the hash-password
|
// this interface can be used with the hash-password
|
||||||
// subcommand as well as benefitting from anti-timing
|
// subcommand as well as benefitting from anti-timing
|
||||||
// features. A hasher also returns a fake hash which
|
// features. A hasher also returns a fake hash which
|
||||||
// can be used for timing side-channel mitigation.
|
// can be used for timing side-channel mitigation.
|
||||||
type Hasher interface {
|
type Hasher interface {
|
||||||
Hash(plaintext, salt []byte) ([]byte, error)
|
Hash(plaintext []byte) ([]byte, error)
|
||||||
FakeHash() []byte
|
FakeHash() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account contains a username, password, and salt (if applicable).
|
// Account contains a username and password.
|
||||||
type Account struct {
|
type Account struct {
|
||||||
// A user's username.
|
// A user's username.
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
|
||||||
// The user's hashed password, base64-encoded.
|
// The user's hashed password, in Modular Crypt Format (with `$` prefix)
|
||||||
|
// or base64-encoded.
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
|
||||||
// The user's password salt, base64-encoded; for
|
password []byte
|
||||||
// algorithms where external salt is needed.
|
|
||||||
Salt string `json:"salt,omitempty"`
|
|
||||||
|
|
||||||
password, salt []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
|
|
|
@ -29,7 +29,7 @@ func init() {
|
||||||
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// basic_auth [<matcher>] [<hash_algorithm> [<realm>]] {
|
// basic_auth [<matcher>] [<hash_algorithm> [<realm>]] {
|
||||||
// <username> <hashed_password_base64> [<salt_base64>]
|
// <username> <hashed_password>
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
@ -64,8 +64,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||||
switch hashName {
|
switch hashName {
|
||||||
case "bcrypt":
|
case "bcrypt":
|
||||||
cmp = BcryptHash{}
|
cmp = BcryptHash{}
|
||||||
case "scrypt":
|
|
||||||
cmp = ScryptHash{}
|
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unrecognized hash algorithm: %s", hashName)
|
return nil, h.Errf("unrecognized hash algorithm: %s", hashName)
|
||||||
}
|
}
|
||||||
|
@ -75,8 +73,8 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||||
for h.NextBlock(0) {
|
for h.NextBlock(0) {
|
||||||
username := h.Val()
|
username := h.Val()
|
||||||
|
|
||||||
var b64Pwd, b64Salt string
|
var b64Pwd string
|
||||||
h.Args(&b64Pwd, &b64Salt)
|
h.Args(&b64Pwd)
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
@ -88,7 +86,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||||
ba.AccountList = append(ba.AccountList, Account{
|
ba.AccountList = append(ba.AccountList, Account{
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: b64Pwd,
|
Password: b64Pwd,
|
||||||
Salt: b64Salt,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ package caddyauth
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -33,7 +32,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
caddycmd.RegisterCommand(caddycmd.Command{
|
caddycmd.RegisterCommand(caddycmd.Command{
|
||||||
Name: "hash-password",
|
Name: "hash-password",
|
||||||
Usage: "[--algorithm <name>] [--salt <string>] [--plaintext <password>]",
|
Usage: "[--plaintext <password>] [--algorithm <name>]",
|
||||||
Short: "Hashes a password and writes base64",
|
Short: "Hashes a password and writes base64",
|
||||||
Long: `
|
Long: `
|
||||||
Convenient way to hash a plaintext password. The resulting
|
Convenient way to hash a plaintext password. The resulting
|
||||||
|
@ -43,17 +42,10 @@ hash is written to stdout as a base64 string.
|
||||||
Caddy is attached to a controlling tty, the plaintext will
|
Caddy is attached to a controlling tty, the plaintext will
|
||||||
not be echoed.
|
not be echoed.
|
||||||
|
|
||||||
--algorithm may be bcrypt or scrypt. If scrypt, the default
|
--algorithm currently only supports 'bcrypt', and is the default.
|
||||||
parameters are used.
|
|
||||||
|
|
||||||
Use the --salt flag for algorithms which require a salt to
|
|
||||||
be provided (scrypt).
|
|
||||||
|
|
||||||
Note that scrypt is deprecated. Please use 'bcrypt' instead.
|
|
||||||
`,
|
`,
|
||||||
CobraFunc: func(cmd *cobra.Command) {
|
CobraFunc: func(cmd *cobra.Command) {
|
||||||
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
|
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
|
||||||
cmd.Flags().StringP("salt", "s", "", "The password salt")
|
|
||||||
cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
|
cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
|
||||||
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
|
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
|
||||||
},
|
},
|
||||||
|
@ -65,7 +57,6 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
|
||||||
|
|
||||||
algorithm := fs.String("algorithm")
|
algorithm := fs.String("algorithm")
|
||||||
plaintext := []byte(fs.String("plaintext"))
|
plaintext := []byte(fs.String("plaintext"))
|
||||||
salt := []byte(fs.String("salt"))
|
|
||||||
|
|
||||||
if len(plaintext) == 0 {
|
if len(plaintext) == 0 {
|
||||||
fd := int(os.Stdin.Fd())
|
fd := int(os.Stdin.Fd())
|
||||||
|
@ -117,13 +108,8 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
|
||||||
var hashString string
|
var hashString string
|
||||||
switch algorithm {
|
switch algorithm {
|
||||||
case "bcrypt":
|
case "bcrypt":
|
||||||
hash, err = BcryptHash{}.Hash(plaintext, nil)
|
hash, err = BcryptHash{}.Hash(plaintext)
|
||||||
hashString = string(hash)
|
hashString = string(hash)
|
||||||
case "scrypt":
|
|
||||||
def := ScryptHash{}
|
|
||||||
def.SetDefaults()
|
|
||||||
hash, err = def.Hash(plaintext, salt)
|
|
||||||
hashString = base64.StdEncoding.EncodeToString(hash)
|
|
||||||
default:
|
default:
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,13 @@
|
||||||
package caddyauth
|
package caddyauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/crypto/scrypt"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(BcryptHash{})
|
caddy.RegisterModule(BcryptHash{})
|
||||||
caddy.RegisterModule(ScryptHash{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BcryptHash implements the bcrypt hash.
|
// BcryptHash implements the bcrypt hash.
|
||||||
|
@ -41,7 +36,7 @@ func (BcryptHash) CaddyModule() caddy.ModuleInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare compares passwords.
|
// Compare compares passwords.
|
||||||
func (BcryptHash) Compare(hashed, plaintext, _ []byte) (bool, error) {
|
func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
|
||||||
err := bcrypt.CompareHashAndPassword(hashed, plaintext)
|
err := bcrypt.CompareHashAndPassword(hashed, plaintext)
|
||||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -53,7 +48,7 @@ func (BcryptHash) Compare(hashed, plaintext, _ []byte) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash hashes plaintext using a random salt.
|
// Hash hashes plaintext using a random salt.
|
||||||
func (BcryptHash) Hash(plaintext, _ []byte) ([]byte, error) {
|
func (BcryptHash) Hash(plaintext []byte) ([]byte, error) {
|
||||||
return bcrypt.GenerateFromPassword(plaintext, 14)
|
return bcrypt.GenerateFromPassword(plaintext, 14)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,94 +59,8 @@ func (BcryptHash) FakeHash() []byte {
|
||||||
return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6")
|
return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScryptHash implements the scrypt KDF as a hash.
|
|
||||||
//
|
|
||||||
// DEPRECATED, please use 'bcrypt' instead.
|
|
||||||
type ScryptHash struct {
|
|
||||||
// scrypt's N parameter. If unset or 0, a safe default is used.
|
|
||||||
N int `json:"N,omitempty"`
|
|
||||||
|
|
||||||
// scrypt's r parameter. If unset or 0, a safe default is used.
|
|
||||||
R int `json:"r,omitempty"`
|
|
||||||
|
|
||||||
// scrypt's p parameter. If unset or 0, a safe default is used.
|
|
||||||
P int `json:"p,omitempty"`
|
|
||||||
|
|
||||||
// scrypt's key length parameter (in bytes). If unset or 0, a
|
|
||||||
// safe default is used.
|
|
||||||
KeyLength int `json:"key_length,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
|
||||||
func (ScryptHash) CaddyModule() caddy.ModuleInfo {
|
|
||||||
return caddy.ModuleInfo{
|
|
||||||
ID: "http.authentication.hashes.scrypt",
|
|
||||||
New: func() caddy.Module { return new(ScryptHash) },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provision sets up s.
|
|
||||||
func (s *ScryptHash) Provision(ctx caddy.Context) error {
|
|
||||||
s.SetDefaults()
|
|
||||||
ctx.Logger().Warn("use of 'scrypt' is deprecated, please use 'bcrypt' instead")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaults sets safe default parameters, but does
|
|
||||||
// not overwrite existing values. Each default parameter
|
|
||||||
// is set independently; it does not check to ensure
|
|
||||||
// that r*p < 2^30. The defaults chosen are those as
|
|
||||||
// recommended in 2019 by
|
|
||||||
// https://godoc.org/golang.org/x/crypto/scrypt.
|
|
||||||
func (s *ScryptHash) SetDefaults() {
|
|
||||||
if s.N == 0 {
|
|
||||||
s.N = 32768
|
|
||||||
}
|
|
||||||
if s.R == 0 {
|
|
||||||
s.R = 8
|
|
||||||
}
|
|
||||||
if s.P == 0 {
|
|
||||||
s.P = 1
|
|
||||||
}
|
|
||||||
if s.KeyLength == 0 {
|
|
||||||
s.KeyLength = 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare compares passwords.
|
|
||||||
func (s ScryptHash) Compare(hashed, plaintext, salt []byte) (bool, error) {
|
|
||||||
ourHash, err := scrypt.Key(plaintext, salt, s.N, s.R, s.P, s.KeyLength)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if hashesMatch(hashed, ourHash) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash hashes plaintext using the given salt.
|
|
||||||
func (s ScryptHash) Hash(plaintext, salt []byte) ([]byte, error) {
|
|
||||||
return scrypt.Key(plaintext, salt, s.N, s.R, s.P, s.KeyLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FakeHash returns a fake hash.
|
|
||||||
func (ScryptHash) FakeHash() []byte {
|
|
||||||
// hashed with the following command:
|
|
||||||
// caddy hash-password --plaintext "antitiming" --salt "fakesalt" --algorithm "scrypt"
|
|
||||||
bytes, _ := base64.StdEncoding.DecodeString("kFbjiVemlwK/ZS0tS6/UQqEDeaNMigyCs48KEsGUse8=")
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashesMatch(pwdHash1, pwdHash2 []byte) bool {
|
|
||||||
return subtle.ConstantTimeCompare(pwdHash1, pwdHash2) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ Comparer = (*BcryptHash)(nil)
|
_ Comparer = (*BcryptHash)(nil)
|
||||||
_ Comparer = (*ScryptHash)(nil)
|
_ Hasher = (*BcryptHash)(nil)
|
||||||
_ Hasher = (*BcryptHash)(nil)
|
|
||||||
_ Hasher = (*ScryptHash)(nil)
|
|
||||||
_ caddy.Provisioner = (*ScryptHash)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue