0
Fork 0
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:
Francis Lavoie 2022-09-05 15:32:58 -04:00 committed by GitHub
parent d6b3c7d262
commit 6e3063b15a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 10 deletions

View file

@ -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).

View file

@ -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
} }

View file

@ -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
} }