mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-03 23:09:57 -05:00
caddyauth: Speed up basicauth provision, deprecate scrypt (#4720)
* caddyauth: Speed up basicauth provisioning, precalculate fake password * Deprecate scrypt, allow using decoded bcrypt hashes * Add TODO note Co-authored-by: Matt Holt <mholt@users.noreply.github.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
This commit is contained in:
parent
d6b3c7d262
commit
6e3063b15a
3 changed files with 44 additions and 10 deletions
|
@ -21,6 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
weakrand "math/rand"
|
weakrand "math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// if supported, generate a fake password we can compare against if needed
|
// if supported, generate a fake password we can compare against if needed
|
||||||
if hasher, ok := hba.Hash.(Hasher); ok {
|
if hasher, ok := hba.Hash.(Hasher); ok {
|
||||||
hba.fakePassword, err = hasher.Hash([]byte("antitiming"), []byte("fakesalt"))
|
hba.fakePassword = hasher.FakeHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("generating anti-timing password hash: %v", err)
|
return fmt.Errorf("generating anti-timing password hash: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -117,10 +118,19 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
||||||
return fmt.Errorf("account %d: username and password are required", i)
|
return fmt.Errorf("account %d: username and password are required", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
acct.password, err = base64.StdEncoding.DecodeString(acct.Password)
|
// TODO: Remove support for redundantly-encoded b64-encoded hashes
|
||||||
if err != nil {
|
// Passwords starting with '$' are likely in Modular Crypt Format,
|
||||||
return fmt.Errorf("base64-decoding password: %v", err)
|
// so we don't need to base64 decode them. But historically, we
|
||||||
|
// required redundant base64, so we try to decode it otherwise.
|
||||||
|
if strings.HasPrefix(acct.Password, "$") {
|
||||||
|
acct.password = []byte(acct.Password)
|
||||||
|
} else {
|
||||||
|
acct.password, err = base64.StdEncoding.DecodeString(acct.Password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("base64-decoding password: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if acct.Salt != "" {
|
if acct.Salt != "" {
|
||||||
acct.salt, err = base64.StdEncoding.DecodeString(acct.Salt)
|
acct.salt, err = base64.StdEncoding.DecodeString(acct.Salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -271,9 +281,11 @@ type Comparer interface {
|
||||||
// that require a salt). 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.
|
// features. A hasher also returns a fake hash which
|
||||||
|
// can be used for timing side-channel mitigation.
|
||||||
type Hasher interface {
|
type Hasher interface {
|
||||||
Hash(plaintext, salt []byte) ([]byte, error)
|
Hash(plaintext, salt []byte) ([]byte, error)
|
||||||
|
FakeHash() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account contains a username, password, and salt (if applicable).
|
// Account contains a username, password, and salt (if applicable).
|
||||||
|
|
|
@ -42,11 +42,13 @@ 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 script, the default
|
--algorithm may be bcrypt or scrypt. If scrypt, the default
|
||||||
parameters are used.
|
parameters are used.
|
||||||
|
|
||||||
Use the --salt flag for algorithms which require a salt to
|
Use the --salt flag for algorithms which require a salt to
|
||||||
be provided (scrypt).
|
be provided (scrypt).
|
||||||
|
|
||||||
|
Note that scrypt is deprecated. Please use 'bcrypt' instead.
|
||||||
`,
|
`,
|
||||||
Flags: func() *flag.FlagSet {
|
Flags: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("hash-password", flag.ExitOnError)
|
fs := flag.NewFlagSet("hash-password", flag.ExitOnError)
|
||||||
|
@ -112,13 +114,16 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash []byte
|
var hash []byte
|
||||||
|
var hashString string
|
||||||
switch algorithm {
|
switch algorithm {
|
||||||
case "bcrypt":
|
case "bcrypt":
|
||||||
hash, err = BcryptHash{}.Hash(plaintext, nil)
|
hash, err = BcryptHash{}.Hash(plaintext, nil)
|
||||||
|
hashString = string(hash)
|
||||||
case "scrypt":
|
case "scrypt":
|
||||||
def := ScryptHash{}
|
def := ScryptHash{}
|
||||||
def.SetDefaults()
|
def.SetDefaults()
|
||||||
hash, err = def.Hash(plaintext, salt)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -126,9 +131,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
|
||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hashBase64 := base64.StdEncoding.EncodeToString(hash)
|
fmt.Println(hashString)
|
||||||
|
|
||||||
fmt.Println(hashBase64)
|
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package caddyauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
@ -55,7 +56,16 @@ func (BcryptHash) Hash(plaintext, _ []byte) ([]byte, error) {
|
||||||
return bcrypt.GenerateFromPassword(plaintext, 14)
|
return bcrypt.GenerateFromPassword(plaintext, 14)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FakeHash returns a fake hash.
|
||||||
|
func (BcryptHash) FakeHash() []byte {
|
||||||
|
// hashed with the following command:
|
||||||
|
// caddy hash-password --plaintext "antitiming" --algorithm "bcrypt"
|
||||||
|
return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6")
|
||||||
|
}
|
||||||
|
|
||||||
// ScryptHash implements the scrypt KDF as a hash.
|
// ScryptHash implements the scrypt KDF as a hash.
|
||||||
|
//
|
||||||
|
// DEPRECATED, please use 'bcrypt' instead.
|
||||||
type ScryptHash struct {
|
type ScryptHash struct {
|
||||||
// scrypt's N parameter. If unset or 0, a safe default is used.
|
// scrypt's N parameter. If unset or 0, a safe default is used.
|
||||||
N int `json:"N,omitempty"`
|
N int `json:"N,omitempty"`
|
||||||
|
@ -80,8 +90,9 @@ func (ScryptHash) CaddyModule() caddy.ModuleInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision sets up s.
|
// Provision sets up s.
|
||||||
func (s *ScryptHash) Provision(_ caddy.Context) error {
|
func (s *ScryptHash) Provision(ctx caddy.Context) error {
|
||||||
s.SetDefaults()
|
s.SetDefaults()
|
||||||
|
ctx.Logger(s).Warn("use of 'scrypt' is deprecated, please use 'bcrypt' instead")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +134,14 @@ func (s ScryptHash) Hash(plaintext, salt []byte) ([]byte, error) {
|
||||||
return scrypt.Key(plaintext, salt, s.N, s.R, s.P, s.KeyLength)
|
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 {
|
func hashesMatch(pwdHash1, pwdHash2 []byte) bool {
|
||||||
return subtle.ConstantTimeCompare(pwdHash1, pwdHash2) == 1
|
return subtle.ConstantTimeCompare(pwdHash1, pwdHash2) == 1
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue