@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 226 lines 7.32 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddyauth
16
17 import (
18 "bufio"
19 "bytes"
20 "context"
21 "encoding/base64"
22 "errors"
23 "fmt"
24 "io"
25 "os"
26 "os/signal"
27
28 "github.com/spf13/cobra"
29 "golang.org/x/term"
30
31 caddycmd "github.com/caddyserver/caddy/v2/cmd"
32
33 "github.com/caddyserver/caddy/v2"
34 )
35
36 func init() {
37 caddycmd.RegisterCommand(caddycmd.Command{
38 Name: "hash-password",
39 Usage: "[--plaintext <password>] [--algorithm <argon2id|bcrypt|...>] [--bcrypt-cost <difficulty>] [--argon2id-time <iterations>] [--argon2id-memory <KiB>] [--argon2id-threads <n>] [--argon2id-keylen <bytes>]",
40 Short: "Hashes a password and writes base64",
41 Long: `
42 Convenient way to hash a plaintext password. The resulting
43 hash is written to stdout as a base64 string.
44
45 --plaintext
46 The password to hash. If omitted, it will be read from stdin.
47 If Caddy is attached to a controlling TTY, the input will not be echoed.
48
49 --algorithm
50 Selects the hashing algorithm. Valid options are:
51 * 'argon2id' (recommended for modern security)
52 * 'bcrypt' (legacy, slower, configurable cost)
53 * the name of another installed http.authentication.hashes
54 module that can generate hashes with its default
55 configuration; its output is base64-encoded
56
57 bcrypt-specific parameters:
58
59 --bcrypt-cost
60 Sets the bcrypt hashing difficulty. Higher values increase security by
61 making the hash computation slower and more CPU-intensive.
62 Must be within the valid range [bcrypt.MinCost, bcrypt.MaxCost].
63 If omitted or invalid, the default cost is used.
64
65 Argon2id-specific parameters:
66
67 --argon2id-time
68 Number of iterations to perform. Increasing this makes
69 hashing slower and more resistant to brute-force attacks.
70
71 --argon2id-memory
72 Amount of memory to use during hashing.
73 Larger values increase resistance to GPU/ASIC attacks.
74
75 --argon2id-threads
76 Number of CPU threads to use. Increase for faster hashing
77 on multi-core systems.
78
79 --argon2id-keylen
80 Length of the resulting hash in bytes. Longer keys increase
81 security but slightly increase storage size.
82 `,
83 CobraFunc: func(cmd *cobra.Command) {
84 cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
85 cmd.Flags().StringP("algorithm", "a", bcryptName, "Name of the hash algorithm")
86 cmd.Flags().Int("bcrypt-cost", defaultBcryptCost, "Bcrypt hashing cost (only used with 'bcrypt' algorithm)")
87 cmd.Flags().Uint32("argon2id-time", defaultArgon2idTime, "Number of iterations for Argon2id hashing. Increasing this makes the hash slower and more resistant to brute-force attacks.")
88 cmd.Flags().Uint32("argon2id-memory", defaultArgon2idMemory, "Memory to use in KiB for Argon2id hashing. Larger values increase resistance to GPU/ASIC attacks.")
89 cmd.Flags().Uint8("argon2id-threads", defaultArgon2idThreads, "Number of CPU threads to use for Argon2id hashing. Increase for faster hashing on multi-core systems.")
90 cmd.Flags().Uint32("argon2id-keylen", defaultArgon2idKeylen, "Length of the resulting Argon2id hash in bytes. Longer hashes increase security but slightly increase storage size.")
91 cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
92 },
93 })
94 }
95
96 func cmdHashPassword(fs caddycmd.Flags) (int, error) {
97 var err error
98
99 algorithm := fs.String("algorithm")
100 plaintext := []byte(fs.String("plaintext"))
101 bcryptCost := fs.Int("bcrypt-cost")
102
103 if len(plaintext) == 0 {
104 fd := int(os.Stdin.Fd())
105 if term.IsTerminal(fd) {
106 // ensure the terminal state is restored on SIGINT
107 state, _ := term.GetState(fd)
108 c := make(chan os.Signal, 1)
109 signal.Notify(c, os.Interrupt)
110 go func() {
111 <-c
112 _ = term.Restore(fd, state)
113 os.Exit(caddy.ExitCodeFailedStartup)
114 }()
115 defer signal.Stop(c)
116
117 fmt.Fprint(os.Stderr, "Enter password: ")
118 plaintext, err = term.ReadPassword(fd)
119 fmt.Fprintln(os.Stderr)
120 if err != nil {
121 return caddy.ExitCodeFailedStartup, err
122 }
123
124 fmt.Fprint(os.Stderr, "Confirm password: ")
125 confirmation, err := term.ReadPassword(fd)
126 fmt.Fprintln(os.Stderr)
127 if err != nil {
128 return caddy.ExitCodeFailedStartup, err
129 }
130
131 if !bytes.Equal(plaintext, confirmation) {
132 return caddy.ExitCodeFailedStartup, fmt.Errorf("password does not match")
133 }
134 } else {
135 rd := bufio.NewReader(os.Stdin)
136 line, err := rd.ReadBytes('\n')
137 if err != nil && !errors.Is(err, io.EOF) {
138 return caddy.ExitCodeFailedStartup, err
139 }
140
141 // drop the line ending (\n or \r\n), if any; a lone trailing \r is kept
142 if trimmed, ok := bytes.CutSuffix(line, []byte("\n")); ok {
143 line = bytes.TrimSuffix(trimmed, []byte("\r"))
144 }
145 plaintext = line
146 }
147
148 if len(plaintext) == 0 {
149 return caddy.ExitCodeFailedStartup, fmt.Errorf("plaintext is required")
150 }
151 }
152
153 var hash []byte
154 var hashString string
155 switch algorithm {
156 case bcryptName:
157 hash, err = BcryptHash{cost: bcryptCost}.Hash(plaintext)
158 hashString = string(hash)
159 case argon2idName:
160 time, err := fs.GetUint32("argon2id-time")
161 if err != nil {
162 return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id time parameter: %w", err)
163 }
164 memory, err := fs.GetUint32("argon2id-memory")
165 if err != nil {
166 return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id memory parameter: %w", err)
167 }
168 threads, err := fs.GetUint8("argon2id-threads")
169 if err != nil {
170 return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id threads parameter: %w", err)
171 }
172 keyLen, err := fs.GetUint32("argon2id-keylen")
173 if err != nil {
174 return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id keylen parameter: %w", err)
175 }
176
177 hash, _ = Argon2idHash{
178 time: time,
179 memory: memory,
180 threads: threads,
181 keyLen: keyLen,
182 }.Hash(plaintext)
183
184 hashString = string(hash)
185 default:
186 hashString, err = hashWithModule(algorithm, plaintext)
187 }
188 if err != nil {
189 return caddy.ExitCodeFailedStartup, err
190 }
191
192 fmt.Println(hashString)
193
194 return 0, nil
195 }
196
197 // hashWithModule hashes plaintext using the installed password
198 // hashing module with the given name, loaded with an empty
199 // configuration. The module must implement Hasher to be usable for
200 // generating hashes. Its output is arbitrary bytes, so it is always
201 // base64-encoded, which is the form HTTPBasicAuth reads passwords in
202 // when they do not start with "$".
203 func hashWithModule(algorithm string, plaintext []byte) (string, error) {
204 modInfo, err := hashModuleInfo(algorithm)
205 if err != nil {
206 return "", err
207 }
208
209 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
210 defer cancel()
211
212 mod, err := ctx.LoadModuleByID(string(modInfo.ID), nil)
213 if err != nil {
214 return "", err
215 }
216 hasher, ok := mod.(Hasher)
217 if !ok {
218 return "", fmt.Errorf("hash algorithm %s does not support generating hashes", algorithm)
219 }
220
221 hash, err := hasher.Hash(plaintext)
222 if err != nil {
223 return "", err
224 }
225 return base64.StdEncoding.EncodeToString(hash), nil
226 }