go-passwd/passwd.go

114 lines
2.0 KiB
Go
Raw Normal View History

2022-12-07 14:19:04 -07:00
package passwd
import (
"bytes"
2022-12-07 14:19:04 -07:00
"errors"
"fmt"
)
type Passwder interface {
Passwd(pass, hash []byte) ([]byte, error)
2022-12-07 14:19:04 -07:00
ApplyPasswd(*Passwd)
}
type Passwd struct {
m map[string]Passwder
d Passwder
f Passwder
}
func New(opts ...Passwder) *Passwd {
p := &Passwd{m: make(map[string]Passwder)}
p.Options(opts...)
return p
}
func (p *Passwd) Options(opts ...Passwder) {
for _, o := range opts {
o.ApplyPasswd(p)
}
}
func (p *Passwd) Register(name string, pass Passwder) {
p.m[name] = pass
if p.d == nil {
p.SetDefault(pass)
}
}
func (p *Passwd) SetDefault(pass Passwder) {
p.d = pass
}
func (p *Passwd) SetFallthrough(pass Passwder) {
p.f = pass
}
func (p *Passwd) Passwd(pass, hash []byte) ([]byte, error) {
if hash == nil {
2022-12-07 14:19:04 -07:00
return p.d.Passwd(pass, hash)
}
name, algo := p.getAlgo(hash)
if algo == nil {
algo = p.f
}
if algo == nil {
return nil, fmt.Errorf("%w: %s", ErrNoHandler, name)
2022-12-07 14:19:04 -07:00
}
return algo.Passwd(pass, hash)
}
func (p *Passwd) IsPreferred(hash []byte) bool {
2022-12-07 14:19:04 -07:00
_, algo := p.getAlgo(hash)
if algo != nil && algo == p.d {
2022-12-07 14:30:32 -07:00
2022-12-07 15:53:33 -07:00
// if the algorithm defines its own check for preference.
if ck, ok := algo.(interface{ IsPreferred([]byte) bool }); ok {
2022-12-07 14:30:32 -07:00
return ck.IsPreferred(hash)
}
2022-12-07 14:19:04 -07:00
return true
}
return false
}
func (p *Passwd) getAlgo(hash []byte) (string, Passwder) {
2022-12-07 14:19:04 -07:00
var algo string
if !bytes.HasPrefix(hash, []byte("$")) {
2022-12-07 18:44:58 -07:00
return p.getName(p.f), p.f
}
if _, h, ok := bytes.Cut(hash, []byte("$")); ok {
a, _, ok := bytes.Cut(h, []byte("$"))
2022-12-07 14:19:04 -07:00
if !ok {
return "", nil
}
algo = string(a)
2022-12-07 14:19:04 -07:00
if passwd, ok := p.m[algo]; ok {
return algo, passwd
}
return algo, nil
}
return p.getName(p.f), p.f
}
func (p *Passwd) getName(n Passwder) string {
if n == nil {
return ""
}
for k, v := range p.m {
if v == n {
return k
}
}
return "none"
}
var ErrNoMatch = errors.New("password does not match")
var ErrBadHash = errors.New("password hash is malformed")
var ErrNoHandler = errors.New("password handler not registered")