@marcel91200 / caddy Mirror · read-only / commits / 6e3063b1
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 13, 14:15 UTC

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>

Francis Lavoie committed Sep 5, 2022 at 15:32 UTC 6e3063b15aa88179fefcf6f75001224de68c5dd2
3 files changed +44 -10
modules/caddyhttp/caddyauth/basicauth.go
+17 -5
@@ -21,6 +21,7 @@ import (
21 "fmt"
22 weakrand "math/rand"
23 "net/http"
24 + "strings"
25 "sync"
26 "time"
27
@@ -94,7 +95,7 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
95
96 // if supported, generate a fake password we can compare against if needed
97 if hasher, ok := hba.Hash.(Hasher); ok {
97 - hba.fakePassword, err = hasher.Hash([]byte("antitiming"), []byte("fakesalt"))
98 + hba.fakePassword = hasher.FakeHash()
99 if err != nil {
100 return fmt.Errorf("generating anti-timing password hash: %v", err)
101 }
@@ -117,10 +118,19 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
118 return fmt.Errorf("account %d: username and password are required", i)
119 }
120
120 - acct.password, err = base64.StdEncoding.DecodeString(acct.Password)
121 - if err != nil {
122 - return fmt.Errorf("base64-decoding password: %v", err)
121 + // TODO: Remove support for redundantly-encoded b64-encoded hashes
122 + // Passwords starting with '$' are likely in Modular Crypt Format,
123 + // so we don't need to base64 decode them. But historically, we
124 + // required redundant base64, so we try to decode it otherwise.
125 + if strings.HasPrefix(acct.Password, "$") {
126 + acct.password = []byte(acct.Password)
127 + } else {
128 + acct.password, err = base64.StdEncoding.DecodeString(acct.Password)
129 + if err != nil {
130 + return fmt.Errorf("base64-decoding password: %v", err)
131 + }
132 }
133 +
134 if acct.Salt != "" {
135 acct.salt, err = base64.StdEncoding.DecodeString(acct.Salt)
136 if err != nil {
@@ -271,9 +281,11 @@ type Comparer interface {
281 // that require a salt). Hashing modules which implement
282 // this interface can be used with the hash-password
283 // subcommand as well as benefitting from anti-timing
274 -// features.
284 +// features. A hasher also returns a fake hash which
285 +// can be used for timing side-channel mitigation.
286 type Hasher interface {
287 Hash(plaintext, salt []byte) ([]byte, error)
288 + FakeHash() []byte
289 }
290
291 // Account contains a username, password, and salt (if applicable).
modules/caddyhttp/caddyauth/command.go
+7 -4
@@ -42,11 +42,13 @@ hash is written to stdout as a base64 string.
42 Caddy is attached to a controlling tty, the plaintext will
43 not be echoed.
44
45 ---algorithm may be bcrypt or scrypt. If script, the default
45 +--algorithm may be bcrypt or scrypt. If scrypt, the default
46 parameters are used.
47
48 Use the --salt flag for algorithms which require a salt to
49 be provided (scrypt).
50 +
51 +Note that scrypt is deprecated. Please use 'bcrypt' instead.
52 `,
53 Flags: func() *flag.FlagSet {
54 fs := flag.NewFlagSet("hash-password", flag.ExitOnError)
@@ -112,13 +114,16 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
114 }
115
116 var hash []byte
117 + var hashString string
118 switch algorithm {
119 case "bcrypt":
120 hash, err = BcryptHash{}.Hash(plaintext, nil)
121 + 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)
127 default:
128 return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
129 }
@@ -126,9 +131,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
131 return caddy.ExitCodeFailedStartup, err
132 }
133
129 - hashBase64 := base64.StdEncoding.EncodeToString(hash)
130 -
131 - fmt.Println(hashBase64)
134 + fmt.Println(hashString)
135
136 return 0, nil
137 }
modules/caddyhttp/caddyauth/hashes.go
+20 -1
@@ -16,6 +16,7 @@ package caddyauth
16
17 import (
18 "crypto/subtle"
19 + "encoding/base64"
20
21 "github.com/caddyserver/caddy/v2"
22 "golang.org/x/crypto/bcrypt"
@@ -55,7 +56,16 @@ func (BcryptHash) Hash(plaintext, _ []byte) ([]byte, error) {
56 return bcrypt.GenerateFromPassword(plaintext, 14)
57 }
58
59 +// FakeHash returns a fake hash.
60 +func (BcryptHash) FakeHash() []byte {
61 + // hashed with the following command:
62 + // caddy hash-password --plaintext "antitiming" --algorithm "bcrypt"
63 + return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6")
64 +}
65 +
66 // ScryptHash implements the scrypt KDF as a hash.
67 +//
68 +// DEPRECATED, please use 'bcrypt' instead.
69 type ScryptHash struct {
70 // scrypt's N parameter. If unset or 0, a safe default is used.
71 N int `json:"N,omitempty"`
@@ -80,8 +90,9 @@ func (ScryptHash) CaddyModule() caddy.ModuleInfo {
90 }
91
92 // Provision sets up s.
83 -func (s *ScryptHash) Provision(_ caddy.Context) error {
93 +func (s *ScryptHash) Provision(ctx caddy.Context) error {
94 s.SetDefaults()
95 + ctx.Logger(s).Warn("use of 'scrypt' is deprecated, please use 'bcrypt' instead")
96 return nil
97 }
98
@@ -123,6 +134,14 @@ func (s ScryptHash) Hash(plaintext, salt []byte) ([]byte, error) {
134 return scrypt.Key(plaintext, salt, s.N, s.R, s.P, s.KeyLength)
135 }
136
137 +// FakeHash returns a fake hash.
138 +func (ScryptHash) FakeHash() []byte {
139 + // hashed with the following command:
140 + // caddy hash-password --plaintext "antitiming" --salt "fakesalt" --algorithm "scrypt"
141 + bytes, _ := base64.StdEncoding.DecodeString("kFbjiVemlwK/ZS0tS6/UQqEDeaNMigyCs48KEsGUse8=")
142 + return bytes
143 +}
144 +
145 func hashesMatch(pwdHash1, pwdHash2 []byte) bool {
146 return subtle.ConstantTimeCompare(pwdHash1, pwdHash2) == 1
147 }