Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

caddyauth: Speed up basicauth provision, deprecate scrypt #4720

Merged
merged 3 commits into from
Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions modules/caddyhttp/caddyauth/basicauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
weakrand "math/rand"
"net/http"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -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 hasher, ok := hba.Hash.(Hasher); ok {
hba.fakePassword, err = hasher.Hash([]byte("antitiming"), []byte("fakesalt"))
hba.fakePassword = hasher.FakeHash()
if err != nil {
return fmt.Errorf("generating anti-timing password hash: %v", err)
}
Expand All @@ -117,10 +118,19 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
return fmt.Errorf("account %d: username and password are required", i)
}

acct.password, err = base64.StdEncoding.DecodeString(acct.Password)
if err != nil {
return fmt.Errorf("base64-decoding password: %v", err)
// TODO: Remove support for redundantly-encoded b64-encoded hashes
// Passwords starting with '$' are likely in Modular Crypt Format,
// 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)
}
francislavoie marked this conversation as resolved.
Show resolved Hide resolved
}

if acct.Salt != "" {
acct.salt, err = base64.StdEncoding.DecodeString(acct.Salt)
if err != nil {
Expand Down Expand Up @@ -271,9 +281,11 @@ type Comparer interface {
// that require a salt). Hashing modules which implement
// this interface can be used with the hash-password
// 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 {
Hash(plaintext, salt []byte) ([]byte, error)
FakeHash() []byte
}

// Account contains a username, password, and salt (if applicable).
Expand Down
11 changes: 7 additions & 4 deletions modules/caddyhttp/caddyauth/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ hash is written to stdout as a base64 string.
Caddy is attached to a controlling tty, the plaintext will
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.

Use the --salt flag for algorithms which require a salt to
be provided (scrypt).

Note that scrypt is deprecated. Please use 'bcrypt' instead.
`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("hash-password", flag.ExitOnError)
Expand Down Expand Up @@ -112,23 +114,24 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
}

var hash []byte
var hashString string
switch algorithm {
case "bcrypt":
hash, err = BcryptHash{}.Hash(plaintext, nil)
hashString = string(hash)
case "scrypt":
def := ScryptHash{}
def.SetDefaults()
hash, err = def.Hash(plaintext, salt)
hashString = base64.StdEncoding.EncodeToString(hash)
default:
return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
}
if err != nil {
return caddy.ExitCodeFailedStartup, err
}

hashBase64 := base64.StdEncoding.EncodeToString(hash)

fmt.Println(hashBase64)
fmt.Println(hashString)

return 0, nil
}
21 changes: 20 additions & 1 deletion modules/caddyhttp/caddyauth/hashes.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package caddyauth

import (
"crypto/subtle"
"encoding/base64"

"github.com/caddyserver/caddy/v2"
"golang.org/x/crypto/bcrypt"
Expand Down Expand Up @@ -55,7 +56,16 @@ func (BcryptHash) Hash(plaintext, _ []byte) ([]byte, error) {
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.
//
// 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"`
Expand All @@ -80,8 +90,9 @@ func (ScryptHash) CaddyModule() caddy.ModuleInfo {
}

// Provision sets up s.
func (s *ScryptHash) Provision(_ caddy.Context) error {
func (s *ScryptHash) Provision(ctx caddy.Context) error {
s.SetDefaults()
ctx.Logger(s).Warn("use of 'scrypt' is deprecated, please use 'bcrypt' instead")
return nil
}

Expand Down Expand Up @@ -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)
}

// 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
}
Expand Down