go-passwd/passwd.go

114 lines
2.0 KiB
Go

package passwd
import (
"bytes"
"errors"
"fmt"
)
type Passwder interface {
Passwd(pass, hash []byte) ([]byte, error)
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 {
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)
}
return algo.Passwd(pass, hash)
}
func (p *Passwd) IsPreferred(hash []byte) bool {
_, algo := p.getAlgo(hash)
if algo != nil && algo == p.d {
// if the algorithm defines its own check for preference.
if ck, ok := algo.(interface{ IsPreferred([]byte) bool }); ok {
return ck.IsPreferred(hash)
}
return true
}
return false
}
func (p *Passwd) getAlgo(hash []byte) (string, Passwder) {
var algo string
if !bytes.HasPrefix(hash, []byte("$")) {
return p.getName(p.f), p.f
}
if _, h, ok := bytes.Cut(hash, []byte("$")); ok {
a, _, ok := bytes.Cut(h, []byte("$"))
if !ok {
return "", nil
}
algo = string(a)
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")