mirror of
https://github.com/caddyserver/caddy.git
synced 2026-10-31 08:21:39 -04:00
A password piped with a Windows line ending was hashed with the trailing \r, so basic_auth rejected the real password, and input without a final newline failed with EOF.
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package caddyauth
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/pflag"
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
|
)
|
|
|
|
func TestHashPasswordFromStdin(t *testing.T) {
|
|
for _, algorithm := range []string{bcryptName, argon2idName} {
|
|
for _, tc := range []struct{ input, password string }{
|
|
{"secret\n", "secret"},
|
|
{"secret\r\n", "secret"},
|
|
{"secret", "secret"},
|
|
{"secret\r", "secret\r"}, // no line ending, so the \r is part of the password
|
|
} {
|
|
name := algorithm + "/" + strings.NewReplacer("\r", `\r`, "\n", `\n`).Replace(tc.input)
|
|
t.Run(name, func(t *testing.T) {
|
|
hash := hashPasswordWithStdin(t, algorithm, tc.input)
|
|
var ok bool
|
|
var err error
|
|
if algorithm == bcryptName {
|
|
ok, err = BcryptHash{}.Compare([]byte(hash), []byte(tc.password))
|
|
} else {
|
|
ok, err = Argon2idHash{}.Compare([]byte(hash), []byte(tc.password))
|
|
}
|
|
if err != nil || !ok {
|
|
t.Fatalf("hash %q does not match the password (err: %v)", hash, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// hashPasswordWithStdin runs the hash-password command with input
|
|
// piped to stdin and returns what it wrote to stdout.
|
|
func hashPasswordWithStdin(t *testing.T, algorithm, input string) string {
|
|
t.Helper()
|
|
|
|
stdinR, stdinW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stdoutR, stdoutW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
origStdin, origStdout := os.Stdin, os.Stdout
|
|
os.Stdin, os.Stdout = stdinR, stdoutW
|
|
defer func() { os.Stdin, os.Stdout = origStdin, origStdout }()
|
|
|
|
if _, err := stdinW.WriteString(input); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stdinW.Close()
|
|
|
|
fs := pflag.NewFlagSet("hash-password", pflag.ContinueOnError)
|
|
fs.String("plaintext", "", "")
|
|
fs.String("algorithm", algorithm, "")
|
|
fs.Int("bcrypt-cost", bcrypt.MinCost, "")
|
|
fs.Uint32("argon2id-time", defaultArgon2idTime, "")
|
|
fs.Uint32("argon2id-memory", defaultArgon2idMemory, "")
|
|
fs.Uint8("argon2id-threads", defaultArgon2idThreads, "")
|
|
fs.Uint32("argon2id-keylen", defaultArgon2idKeylen, "")
|
|
|
|
code, err := cmdHashPassword(caddycmd.Flags{FlagSet: fs})
|
|
stdoutW.Close()
|
|
if err != nil {
|
|
t.Fatalf("hash-password failed (exit code %d): %v", code, err)
|
|
}
|
|
|
|
out, err := io.ReadAll(stdoutR)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|