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
}