Compare commits
1 Commits
master
..
56008698b2
| Author | SHA1 | Date | |
|---|---|---|---|
|
56008698b2
|
Vendored
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"cSpell.words": [
|
|
||||||
"hasher",
|
|
||||||
"htpasswd",
|
|
||||||
"Passwds"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
module asciigoat.org/httools
|
module asciigoat.org/httools
|
||||||
|
|
||||||
go 1.20
|
go 1.21.1
|
||||||
|
|
||||||
require github.com/mgechev/revive v1.3.3
|
require github.com/mgechev/revive v1.3.3
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
|||||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package htpasswd
|
|
||||||
|
|
||||||
import "github.com/GehirnInc/crypt/apr1_crypt"
|
|
||||||
|
|
||||||
// Apr1 facilitates apr1 style hashing
|
|
||||||
type Apr1 struct{}
|
|
||||||
|
|
||||||
// Hash returns the hashed variant of the password or an error
|
|
||||||
func (*Apr1) Hash(passwd string) (string, error) {
|
|
||||||
return apr1_crypt.New().Generate([]byte(passwd), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match verifier the hashed password using the original
|
|
||||||
func (*Apr1) Match(password, hashedPassword string) error {
|
|
||||||
return apr1_crypt.New().Verify(hashedPassword, []byte(password))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the hasher
|
|
||||||
func (*Apr1) Name() string { return "apr1" }
|
|
||||||
|
|
||||||
// Prefix returns the hasher's prefix
|
|
||||||
func (*Apr1) Prefix() string { return "$apr1$" }
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package htpasswd
|
|
||||||
|
|
||||||
import "golang.org/x/crypto/bcrypt"
|
|
||||||
|
|
||||||
// Bcrypt facilitates bcrypt style hashing
|
|
||||||
type Bcrypt struct{}
|
|
||||||
|
|
||||||
// Hash returns the hashed variant of the password or an error
|
|
||||||
func (*Bcrypt) Hash(password string) (string, error) {
|
|
||||||
passwordBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(passwordBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match verifier the hashed password using the original
|
|
||||||
func (*Bcrypt) Match(password, hashedPassword string) error {
|
|
||||||
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the hasher
|
|
||||||
func (*Bcrypt) Name() string { return "bcrypt" }
|
|
||||||
|
|
||||||
// Prefix returns the hasher's prefix
|
|
||||||
func (*Bcrypt) Prefix() string { return "$2a$" }
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package htpasswd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrExists indicates the specified user already exists
|
|
||||||
ErrExists = errors.New("user already exists")
|
|
||||||
// ErrNotExists indicates the specified user does not exist
|
|
||||||
ErrNotExists = errors.New("user does not exist")
|
|
||||||
// ErrInvalidAlgorithm indicates the htpasswd entry doesn't contain a
|
|
||||||
// valid algorithm signature
|
|
||||||
ErrInvalidAlgorithm = errors.New("invalid algorithm")
|
|
||||||
// ErrInvalidPassword indicates the offered password doesn't match
|
|
||||||
ErrInvalidPassword = errors.New("invalid password")
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserError indicates an error associated to the specified user
|
|
||||||
type UserError struct {
|
|
||||||
Name string
|
|
||||||
Hint string
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UserError) Error() string {
|
|
||||||
var buf strings.Builder
|
|
||||||
var reason string
|
|
||||||
|
|
||||||
if e.Hint != "" {
|
|
||||||
reason = e.Hint
|
|
||||||
} else {
|
|
||||||
reason = e.Err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Name == "" {
|
|
||||||
return reason
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = buf.WriteString(e.Name)
|
|
||||||
_, _ = buf.WriteString(": ")
|
|
||||||
_, _ = buf.WriteString(reason)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UserError) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
+312
-95
@@ -2,160 +2,167 @@
|
|||||||
package htpasswd
|
package htpasswd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GehirnInc/crypt/apr1_crypt"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Passwds name => hash
|
// Passwds name => hash
|
||||||
type Passwds map[string]string
|
type Passwds map[string]string
|
||||||
|
|
||||||
// Hasher interface implemented by hash algos
|
// HashAlgorithm enum for hashing algorithms
|
||||||
type Hasher interface {
|
type HashAlgorithm string
|
||||||
Hash(password string) (string, error)
|
|
||||||
Match(password, hashedPassword string) error
|
|
||||||
Name() string
|
|
||||||
Prefix() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFile parses a .htpasswd file
|
const (
|
||||||
|
// HashAPR1 Apache MD5 crypt
|
||||||
|
HashAPR1 HashAlgorithm = "apr1"
|
||||||
|
// HashBCrypt bcrypt
|
||||||
|
HashBCrypt HashAlgorithm = "bcrypt"
|
||||||
|
// HashSHA SHA
|
||||||
|
HashSHA HashAlgorithm = "sha"
|
||||||
|
// HashSSHA Salted SHA
|
||||||
|
HashSSHA HashAlgorithm = "ssha"
|
||||||
|
// HashSHA256 256 variant of SHA
|
||||||
|
HashSHA256 HashAlgorithm = "sha256"
|
||||||
|
// HashSHA512 512 variant of SHA
|
||||||
|
HashSHA512 HashAlgorithm = "sha512"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseHtpasswdFile parses a .htpasswd file
|
||||||
// and returns a Passwd type
|
// and returns a Passwd type
|
||||||
func ParseFile(file string) (Passwds, error) {
|
func ParseHtpasswdFile(file string) (Passwds, error) {
|
||||||
htpasswdBytes, err := os.ReadFile(file)
|
htpasswdBytes, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return Parse(htpasswdBytes)
|
return ParseHtpasswd(htpasswdBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses a slice of bytes in htpasswd style
|
// ParseHtpasswd parses a slice of bytes in htpasswd style
|
||||||
func Parse(htpasswdBytes []byte) (Passwds, error) {
|
func ParseHtpasswd(htpasswdBytes []byte) (Passwds, error) {
|
||||||
lines := strings.Split(string(htpasswdBytes), "\n")
|
lines := strings.Split(string(htpasswdBytes), "\n")
|
||||||
passwords := make(map[string]string)
|
passwords := make(map[string]string)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
for lineNumber, line := range lines {
|
for lineNumber, line := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.Trim(line, " ")
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
// skipping empty lines
|
// skipping empty lines
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
user, password, err := splitLine(line, lineNumber)
|
parts := strings.Split(line, ":")
|
||||||
if err != nil {
|
if ok, err := validLine(parts, lineNumber, line); !ok {
|
||||||
return passwords, err
|
return passwords, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists := passwords[user]
|
parts = trimParts(parts)
|
||||||
|
_, exists := passwords[parts[0]]
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
err = &UserError{
|
err = errors.New("invalid htpasswords file - user " +
|
||||||
Name: user,
|
parts[0] + " defined more than once")
|
||||||
Err: ErrExists,
|
|
||||||
}
|
|
||||||
return passwords, err
|
return passwords, err
|
||||||
}
|
}
|
||||||
|
passwords[parts[0]] = parts[1]
|
||||||
passwords[user] = password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwords, err
|
return passwords, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a record in the named file with
|
// CreateUser creates a record in the named file with
|
||||||
// the named password and hash algorithm
|
// the named password and hash algorithm
|
||||||
func CreateUser(file, user, passwd string, algo Hasher) error {
|
func CreateUser(file, user, passwd string, algo HashAlgorithm) error {
|
||||||
pp, err := ParseFile(file)
|
pp, err := ParseHtpasswdFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = pp.CreateUser(user, passwd, algo)
|
newpp, err := pp.CreateUser(user, passwd, algo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return pp.WriteFile(file)
|
return newpp.Write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser will create a new user in the given Passwd object
|
// CreateUser will create a new user in the given Passwd object
|
||||||
// using the given name, password and hashing algorithm
|
// using the given name, password and hashing algorithm
|
||||||
func (pp Passwds) CreateUser(user, passwd string, algo Hasher) error {
|
func (pp Passwds) CreateUser(user, passwd string, algo HashAlgorithm) (Passwds, error) {
|
||||||
if _, exists := pp[user]; exists {
|
if _, exists := pp[user]; exists {
|
||||||
return &UserError{
|
return nil, fmt.Errorf("user %s already exists", user)
|
||||||
Name: user,
|
|
||||||
Err: ErrExists,
|
|
||||||
}
|
}
|
||||||
}
|
h, err := createHash(passwd, algo)
|
||||||
|
|
||||||
h, err := algo.Hash(passwd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
pp[user] = h
|
pp[user] = h
|
||||||
return nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser will update the password for the named user
|
// UpdateUser will update the password for the named user
|
||||||
// in the named file
|
// in the named file
|
||||||
func UpdateUser(file, user, passwd string, algo Hasher) error {
|
func UpdateUser(file, user, passwd string, algo HashAlgorithm) error {
|
||||||
pp, err := ParseFile(file)
|
pp, err := ParseHtpasswdFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = pp.UpdateUser(user, passwd, algo)
|
newpp, err := pp.UpdateUser(user, passwd, algo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return pp.WriteFile(file)
|
return newpp.Write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser will update the password for the named user
|
// UpdateUser will update the password for the named user
|
||||||
// using the given name, password and hashing algorithm
|
// using the given name, password and hashing algorithm
|
||||||
func (pp Passwds) UpdateUser(user, passwd string, algo Hasher) error {
|
func (pp Passwds) UpdateUser(user, passwd string, algo HashAlgorithm) (Passwds, error) {
|
||||||
if _, exists := pp[user]; !exists {
|
if _, exists := pp[user]; !exists {
|
||||||
return &UserError{
|
return nil, fmt.Errorf("user %s does not exist", user)
|
||||||
Name: user,
|
|
||||||
Err: ErrNotExists,
|
|
||||||
}
|
}
|
||||||
}
|
h, err := createHash(passwd, algo)
|
||||||
|
|
||||||
h, err := algo.Hash(passwd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
pp[user] = h
|
pp[user] = h
|
||||||
return nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser deletes the named user from the named file
|
// DeleteUser deletes the named user from the named file
|
||||||
func DeleteUser(file, user string) error {
|
func DeleteUser(file, user string) error {
|
||||||
pp, err := ParseFile(file)
|
pp, err := ParseHtpasswdFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = pp.DeleteUser(user)
|
newpp, err := pp.DeleteUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return pp.WriteFile(file)
|
return newpp.Write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser deletes the named user from the named file
|
// DeleteUser deletes the named user from the named file
|
||||||
func (pp Passwds) DeleteUser(user string) error {
|
func (pp Passwds) DeleteUser(user string) (Passwds, error) {
|
||||||
if _, exists := pp[user]; !exists {
|
if _, exists := pp[user]; !exists {
|
||||||
return &UserError{
|
return nil, fmt.Errorf("user %s does not exist", user)
|
||||||
Name: user,
|
|
||||||
Err: ErrNotExists,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(pp, user)
|
delete(pp, user)
|
||||||
return nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyUser will check if the given user and password are matching
|
// VerifyUser will check if the given user and password are matching
|
||||||
// with the content of the given file
|
// with the content of the given file
|
||||||
func VerifyUser(file, user, passwd string) error {
|
func VerifyUser(file, user, passwd string) error {
|
||||||
pp, err := ParseFile(file)
|
pp, err := ParseHtpasswdFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -166,30 +173,22 @@ func VerifyUser(file, user, passwd string) error {
|
|||||||
// with the given Passwd object
|
// with the given Passwd object
|
||||||
func (pp Passwds) VerifyUser(user, passwd string) error {
|
func (pp Passwds) VerifyUser(user, passwd string) error {
|
||||||
if _, ok := pp[user]; !ok {
|
if _, ok := pp[user]; !ok {
|
||||||
return &UserError{
|
return fmt.Errorf("user %s does not exist", user)
|
||||||
Name: user,
|
|
||||||
Err: ErrNotExists,
|
|
||||||
}
|
}
|
||||||
|
alg, err := identifyHash(pp[user])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot identify algo %v", alg)
|
||||||
|
}
|
||||||
|
return verifyPass(passwd, pp[user], alg)
|
||||||
}
|
}
|
||||||
|
|
||||||
alg := identifyHash(pp[user])
|
// Write will cwrite the Passwd object to the given file
|
||||||
if alg == nil {
|
func (pp Passwds) Write(file string) error {
|
||||||
return &UserError{
|
return os.WriteFile(file, pp.Byte(), os.ModePerm)
|
||||||
Name: user,
|
|
||||||
Err: ErrInvalidAlgorithm,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return alg.Match(passwd, pp[user])
|
// Byte will return the Passwd as a byte slice
|
||||||
}
|
func (pp Passwds) Byte() []byte {
|
||||||
|
|
||||||
// WriteFile will write the Passwds object to the given file
|
|
||||||
func (pp Passwds) WriteFile(file string) error {
|
|
||||||
return os.WriteFile(file, pp.Bytes(), os.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes will return the Passwd as a byte slice
|
|
||||||
func (pp Passwds) Bytes() []byte {
|
|
||||||
pass := []byte{}
|
pass := []byte{}
|
||||||
for name, hash := range pp {
|
for name, hash := range pp {
|
||||||
pass = append(pass, []byte(name+":"+hash+"\n")...)
|
pass = append(pass, []byte(name+":"+hash+"\n")...)
|
||||||
@@ -197,38 +196,256 @@ func (pp Passwds) Bytes() []byte {
|
|||||||
return pass
|
return pass
|
||||||
}
|
}
|
||||||
|
|
||||||
func identifyHash(h string) Hasher {
|
func createHash(passwd string, algo HashAlgorithm) (string, error) {
|
||||||
|
hash := ""
|
||||||
|
prefix := ""
|
||||||
|
var err error
|
||||||
|
switch algo {
|
||||||
|
case HashAPR1:
|
||||||
|
hash, err = hashApr1(passwd)
|
||||||
|
case HashBCrypt:
|
||||||
|
hash, err = hashBcrypt(passwd)
|
||||||
|
case HashSHA:
|
||||||
|
prefix = "{SHA}"
|
||||||
|
hash, err = hashSha(passwd)
|
||||||
|
case HashSSHA:
|
||||||
|
prefix = "{SSHA}"
|
||||||
|
hash, err = hashSSha(passwd)
|
||||||
|
case HashSHA256:
|
||||||
|
prefix = "$5$"
|
||||||
|
hash, err = hashSha256(passwd)
|
||||||
|
case HashSHA512:
|
||||||
|
prefix = "$6$"
|
||||||
|
hash, err = hashSha512(passwd)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix + hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPass(pass, hash string, alg HashAlgorithm) error {
|
||||||
|
switch alg {
|
||||||
|
case HashAPR1:
|
||||||
|
return verifyAPR1(pass, hash)
|
||||||
|
case HashBCrypt:
|
||||||
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pass))
|
||||||
|
case HashSHA:
|
||||||
|
return verifySHA(pass, hash)
|
||||||
|
case HashSSHA:
|
||||||
|
return verifySSHA(pass, hash)
|
||||||
|
case HashSHA256:
|
||||||
|
return verifySHA256(pass, hash)
|
||||||
|
case HashSHA512:
|
||||||
|
return verifySHA512(pass, hash)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unsupported hash algorithm %v", alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func identifyHash(h string) (HashAlgorithm, error) {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"),
|
case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"),
|
||||||
strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"):
|
strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"):
|
||||||
return new(Bcrypt)
|
return HashBCrypt, nil
|
||||||
case strings.HasPrefix(h, "$apr1$"):
|
case strings.HasPrefix(h, "$apr1$"):
|
||||||
return new(Apr1)
|
return HashAPR1, nil
|
||||||
case strings.HasPrefix(h, "{SHA}"):
|
case strings.HasPrefix(h, "{SHA}"):
|
||||||
return new(Sha)
|
return HashSHA, nil
|
||||||
case strings.HasPrefix(h, "{SSHA}"):
|
case strings.HasPrefix(h, "{SSHA}"):
|
||||||
return new(Ssha)
|
return HashSSHA, nil
|
||||||
case strings.HasPrefix(h, "$5$"):
|
case strings.HasPrefix(h, "$5$"):
|
||||||
return new(Sha256)
|
return HashSHA256, nil
|
||||||
case strings.HasPrefix(h, "$6$"):
|
case strings.HasPrefix(h, "$6$"):
|
||||||
return new(Sha512)
|
return HashSHA512, nil
|
||||||
default:
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("unsupported hash algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashApr1(passwd string) (string, error) {
|
||||||
|
return apr1_crypt.New().Generate([]byte(passwd), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyAPR1(password, hashedPassword string) error {
|
||||||
|
return apr1_crypt.New().Verify(hashedPassword, []byte(password))
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashBcrypt(passwd string) (string, error) {
|
||||||
|
passwordBytes, err := bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(passwordBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func verifyBcrypt does not exist as its place
|
||||||
|
// is taken by `bcrypt.CompareHashAndPassword`
|
||||||
|
|
||||||
|
func hashSha(passwd string) (string, error) {
|
||||||
|
s := sha1.New()
|
||||||
|
_, err := s.Write([]byte(passwd))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
passwordSum := []byte(s.Sum(nil))
|
||||||
|
return base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifySHA(password, hashedPassword string) error {
|
||||||
|
eppS := hashedPassword[5:]
|
||||||
|
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot base64 decode")
|
||||||
|
}
|
||||||
|
|
||||||
|
sha := sha1.New()
|
||||||
|
_, err = sha.Write([]byte(password))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha.Sum(nil)
|
||||||
|
|
||||||
|
if !bytes.Equal(sum, hash) {
|
||||||
|
return fmt.Errorf("wrong password")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hashSSha(passwd string) (string, error) {
|
||||||
|
s := sha1.New()
|
||||||
|
_, err := s.Write([]byte(passwd))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
salt := make([]byte, 4)
|
||||||
|
_, err = rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_, err = s.Write([]byte(salt))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
passwordSum := []byte(s.Sum(nil))
|
||||||
|
ret := append(passwordSum, salt...)
|
||||||
|
return base64.StdEncoding.EncodeToString(ret), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitLine(line string, lineNumber int) (user, password string, err error) {
|
func verifySSHA(password, hashedPassword string) error {
|
||||||
user, password, ok := strings.Cut(line, ":")
|
eppS := hashedPassword[6:]
|
||||||
if !ok {
|
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||||
return "", "", fmt.Errorf("invalid line %v", lineNumber+1)
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot base64 decode")
|
||||||
}
|
}
|
||||||
|
|
||||||
user = strings.TrimSpace(user)
|
salt := hash[len(hash)-4:]
|
||||||
password = strings.TrimSpace(password)
|
sha := sha1.New()
|
||||||
|
_, err = sha.Write([]byte(password))
|
||||||
|
|
||||||
if h := identifyHash(password); h != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("invalid algorithm on line %v", lineNumber+1)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, password, nil
|
_, err = sha.Write(salt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha.Sum(nil)
|
||||||
|
|
||||||
|
if !bytes.Equal(sum, hash[:len(hash)-4]) {
|
||||||
|
return fmt.Errorf("wrong password")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashSha256(passwd string) (string, error) {
|
||||||
|
s := sha256.New()
|
||||||
|
_, err := s.Write([]byte(passwd))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
passwordSum := []byte(s.Sum(nil))
|
||||||
|
return base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifySHA256(password, hashedPassword string) error {
|
||||||
|
eppS := hashedPassword[3:]
|
||||||
|
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot base64 decode")
|
||||||
|
}
|
||||||
|
|
||||||
|
sha := sha256.New()
|
||||||
|
_, err = sha.Write([]byte(password))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha.Sum(nil)
|
||||||
|
|
||||||
|
if !bytes.Equal(sum, hash) {
|
||||||
|
return fmt.Errorf("wrong password")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashSha512(passwd string) (string, error) {
|
||||||
|
s := sha512.New()
|
||||||
|
_, err := s.Write([]byte(passwd))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
passwordSum := []byte(s.Sum(nil))
|
||||||
|
return base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifySHA512(password, hashedPassword string) error {
|
||||||
|
eppS := hashedPassword[3:]
|
||||||
|
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot base64 decode")
|
||||||
|
}
|
||||||
|
|
||||||
|
sha := sha512.New()
|
||||||
|
_, err = sha.Write([]byte(password))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha.Sum(nil)
|
||||||
|
|
||||||
|
if !bytes.Equal(sum, hash) {
|
||||||
|
return fmt.Errorf("wrong password")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validLine(parts []string, lineNumber int, line string) (bool, error) {
|
||||||
|
var err error
|
||||||
|
if len(parts) != 2 {
|
||||||
|
err = errors.New(fmt.Sprintln("invalid line", lineNumber+1,
|
||||||
|
"unexpected number of parts split by", ":", len(parts),
|
||||||
|
"instead of 2 in\"", line, "\""))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimParts(parts []string) []string {
|
||||||
|
for i, part := range parts {
|
||||||
|
parts[i] = strings.Trim(part, " ")
|
||||||
|
}
|
||||||
|
return parts
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestParseHtpasswd(t *testing.T) {
|
func TestParseHtpasswd(t *testing.T) {
|
||||||
passwords, err := Parse([]byte("sha:{SHA}IRRjboXT92QSYXm8lpGPCZUvU1E=\n"))
|
passwords, err := ParseHtpasswd([]byte("sha:{SHA}IRRjboXT92QSYXm8lpGPCZUvU1E=\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -26,15 +26,15 @@ func TestCreateUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
user, password string
|
user, password string
|
||||||
hash Hasher
|
hash HashAlgorithm
|
||||||
expected error
|
expected error
|
||||||
}{
|
}{
|
||||||
{"apr1", "123456@", new(Apr1), nil},
|
{"apr1", "123456@", HashAPR1, nil},
|
||||||
{"bcrypt", "123456@", new(Bcrypt), nil},
|
{"bcrypt", "123456@", HashBCrypt, nil},
|
||||||
{"ssha", "123456@", new(Ssha), nil},
|
{"ssha", "123456@", HashSSHA, nil},
|
||||||
{"sha", "123456@", new(Sha), nil},
|
{"sha", "123456@", HashSHA, nil},
|
||||||
{"sha256", "123456@", new(Sha256), nil},
|
{"sha256", "123456@", HashSHA256, nil},
|
||||||
{"sha512", "123456@", new(Sha512), nil},
|
{"sha512", "123456@", HashSHA512, nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -51,7 +51,7 @@ func TestVerifyPassword(t *testing.T) {
|
|||||||
TestCreateUser(t)
|
TestCreateUser(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ParseFile(f.Name())
|
_, err = ParseHtpasswdFile(f.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ func TestVerifyPassword(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
err := VerifyUser(f.Name(), tc.user, tc.password)
|
err := VerifyUser(f.Name(), tc.user, tc.password)
|
||||||
if err != tc.expected {
|
if err != tc.expected {
|
||||||
t.Errorf("VerifyUser(%v %v, %v) = %v; want %v",
|
t.Errorf("VerifyUser(%s %s, %s) = %v; want %v",
|
||||||
f, tc.user, tc.password, err, tc.expected)
|
f, tc.user, tc.password, err, tc.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ func TestVerifyUser(t *testing.T) {
|
|||||||
TestCreateUser(t)
|
TestCreateUser(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
pp, err := ParseFile(f.Name())
|
pp, err := ParseHtpasswdFile(f.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
bcrypt:$2a$10$YNEmQcVCrvcA8m1DNCwR9eXaTySDEa1sC/T5xUUTFnVnP.RIP3O2u
|
|
||||||
ssha:{SSHA}fSLbdty+JHr+q3p/lHAPvNkOU3H8NLmI
|
|
||||||
sha:{SHA}YEn9/RmoXLdbyB9TEDJ0OqWoPy8=
|
|
||||||
sha256:$5$ufJ2S16IOhB6XLTGrVLqGQBKv/odjE3rypxnUDLqaS0=
|
sha256:$5$ufJ2S16IOhB6XLTGrVLqGQBKv/odjE3rypxnUDLqaS0=
|
||||||
|
apr1:$apr1$paiD.yqE$4lKqGF77KuaaEDY.bBpnv0
|
||||||
|
bcrypt:$2a$10$imLJRfdHQkOg4/cleE31Re5Xp73DQctfXtlqeZc2Mcs2I9YdjXUl2
|
||||||
|
ssha:{SSHA}m2iNELVYJhxKppu7FZHXXkGcKSlUkA/t
|
||||||
|
sha:{SHA}YEn9/RmoXLdbyB9TEDJ0OqWoPy8=
|
||||||
sha512:$6$78RjySv19bx/knbdL6q1cpoV8WblZwc3x+wmPGQUvrSycxc4liKbksvDr9HZj76hgRuZZCyEngP+WEJmePArCQ==
|
sha512:$6$78RjySv19bx/knbdL6q1cpoV8WblZwc3x+wmPGQUvrSycxc4liKbksvDr9HZj76hgRuZZCyEngP+WEJmePArCQ==
|
||||||
apr1:$apr1$mO.FA9Gg$1LbPaKe7HCVHezEYzMCnn.
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
package htpasswd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sha facilitates sha1 style hashing
|
|
||||||
type Sha struct{}
|
|
||||||
|
|
||||||
// Hash returns the hashed variant of the password or an error
|
|
||||||
func (ss *Sha) Hash(passwd string) (string, error) {
|
|
||||||
s := sha1.New()
|
|
||||||
_, err := s.Write([]byte(passwd))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
passwordSum := []byte(s.Sum(nil))
|
|
||||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(passwordSum), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match verifier the hashed password using the original
|
|
||||||
func (*Sha) Match(password, hashedPassword string) error {
|
|
||||||
eppS := hashedPassword[5:]
|
|
||||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot base64 decode")
|
|
||||||
}
|
|
||||||
|
|
||||||
sha := sha1.New()
|
|
||||||
_, err = sha.Write([]byte(password))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sum := sha.Sum(nil)
|
|
||||||
|
|
||||||
if !bytes.Equal(sum, hash) {
|
|
||||||
return fmt.Errorf("wrong password")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the hasher
|
|
||||||
func (*Sha) Name() string { return "sha" }
|
|
||||||
|
|
||||||
// Prefix returns the hasher's prefix
|
|
||||||
func (*Sha) Prefix() string { return "{SHA}" }
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package htpasswd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sha256 facilitates sha256 style hashing
|
|
||||||
type Sha256 struct{}
|
|
||||||
|
|
||||||
// Hash returns the hashed variant of the password or an error
|
|
||||||
func (ss *Sha256) Hash(passwd string) (string, error) {
|
|
||||||
s := sha256.New()
|
|
||||||
_, err := s.Write([]byte(passwd))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
passwordSum := []byte(s.Sum(nil))
|
|
||||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(passwordSum), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match verifier the hashed password using the original
|
|
||||||
func (*Sha256) Match(password, hashedPassword string) error {
|
|
||||||
eppS := hashedPassword[3:]
|
|
||||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot base64 decode")
|
|
||||||
}
|
|
||||||
|
|
||||||
sha := sha256.New()
|
|
||||||
_, err = sha.Write([]byte(password))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sum := sha.Sum(nil)
|
|
||||||
|
|
||||||
if !bytes.Equal(sum, hash) {
|
|
||||||
return fmt.Errorf("wrong password")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the hasher
|
|
||||||
func (*Sha256) Name() string { return "sha256" }
|
|
||||||
|
|
||||||
// Prefix returns the hasher's prefix
|
|
||||||
func (*Sha256) Prefix() string { return "$5$" }
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package htpasswd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sha512 facilitates sha512 style hashing
|
|
||||||
type Sha512 struct{}
|
|
||||||
|
|
||||||
// Hash returns the hashed variant of the password or an error
|
|
||||||
func (ss *Sha512) Hash(passwd string) (string, error) {
|
|
||||||
s := sha512.New()
|
|
||||||
_, err := s.Write([]byte(passwd))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
passwordSum := []byte(s.Sum(nil))
|
|
||||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(passwordSum), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match verifier the hashed password using the original
|
|
||||||
func (*Sha512) Match(password, hashedPassword string) error {
|
|
||||||
eppS := hashedPassword[3:]
|
|
||||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot base64 decode")
|
|
||||||
}
|
|
||||||
|
|
||||||
sha := sha512.New()
|
|
||||||
_, err = sha.Write([]byte(password))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sum := sha.Sum(nil)
|
|
||||||
|
|
||||||
if !bytes.Equal(sum, hash) {
|
|
||||||
return fmt.Errorf("wrong password")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the hasher
|
|
||||||
func (*Sha512) Name() string { return "sha512" }
|
|
||||||
|
|
||||||
// Prefix returns the hasher's prefix
|
|
||||||
func (*Sha512) Prefix() string { return "$6$" }
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package htpasswd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ssha facilitates ssha style hashing
|
|
||||||
type Ssha struct{}
|
|
||||||
|
|
||||||
// Hash returns the hashed variant of the password or an error
|
|
||||||
func (ss *Ssha) Hash(passwd string) (string, error) {
|
|
||||||
s := sha1.New()
|
|
||||||
_, err := s.Write([]byte(passwd))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
salt := make([]byte, 4)
|
|
||||||
_, err = rand.Read(salt)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
_, err = s.Write([]byte(salt))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
passwordSum := []byte(s.Sum(nil))
|
|
||||||
ret := append(passwordSum, salt...)
|
|
||||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(ret), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match verifier the hashed password using the original
|
|
||||||
func (*Ssha) Match(password, hashedPassword string) error {
|
|
||||||
eppS := hashedPassword[6:]
|
|
||||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot base64 decode")
|
|
||||||
}
|
|
||||||
|
|
||||||
salt := hash[len(hash)-4:]
|
|
||||||
sha := sha1.New()
|
|
||||||
_, err = sha.Write([]byte(password))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sha.Write(salt)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sum := sha.Sum(nil)
|
|
||||||
|
|
||||||
if !bytes.Equal(sum, hash[:len(hash)-4]) {
|
|
||||||
return fmt.Errorf("wrong password")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the hasher
|
|
||||||
func (*Ssha) Name() string { return "ssha" }
|
|
||||||
|
|
||||||
// Prefix returns the hasher's prefix
|
|
||||||
func (*Ssha) Prefix() string { return "{SSHA}" }
|
|
||||||
Reference in New Issue
Block a user