307 lines
6.6 KiB
Go
307 lines
6.6 KiB
Go
package pwgenerator
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"r00t2.io/goutils/multierr"
|
|
)
|
|
|
|
// Generate generates a list of passwords for a GenOpts.
|
|
func (o *GenOpts) Generate() (passwords []string, err error) {
|
|
|
|
var passwds []string = make([]string, o.Count)
|
|
var charset CharSet = o.Chars()
|
|
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
|
|
|
if o.Count == 0 {
|
|
o.Count = DefCount
|
|
}
|
|
if o.LengthMax == 0 {
|
|
o.LengthMax = DefMaxLen
|
|
}
|
|
|
|
for idx, _ := range passwds {
|
|
if passwds[idx], err = o.generatePassword(charset); err != nil {
|
|
errs.AddError(err)
|
|
err = nil
|
|
}
|
|
}
|
|
|
|
passwords = passwds
|
|
|
|
if !errs.IsEmpty() {
|
|
err = errs
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// generatePassword generates a single password from CharSet c (plus any minimum requirements).
|
|
func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
|
|
|
|
var maxMin uint
|
|
var trueMinLen uint
|
|
var passLenGap uint
|
|
var passLen int
|
|
var passAlloc []rune
|
|
var filter *selectFilter = o.getFilter()
|
|
var isDisabled bool
|
|
|
|
// Sanity checks/error conditions.
|
|
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
|
|
if o.CountUpper > 0 && !o.Alpha {
|
|
err = ErrIncompatCharsetFilter
|
|
return
|
|
}
|
|
if o.CountLower > 0 && !o.Alpha {
|
|
err = ErrIncompatCharsetFilter
|
|
return
|
|
}
|
|
if o.CountNumbers > 0 && !o.Numeric {
|
|
err = ErrIncompatCharsetFilter
|
|
return
|
|
}
|
|
if o.CountSymbols > 0 && !o.Symbols {
|
|
err = ErrIncompatCharsetFilter
|
|
return
|
|
}
|
|
if o.CountExtended > 0 && !o.ExtendedSymbols {
|
|
err = ErrIncompatCharsetFilter
|
|
return
|
|
}
|
|
}
|
|
maxMin = o.CountUpper + o.CountLower + o.CountNumbers + o.CountSymbols + o.CountExtended
|
|
if maxMin > o.LengthMax {
|
|
err = ErrTooSmall
|
|
return
|
|
}
|
|
if o.LengthMin > o.LengthMax {
|
|
err = ErrSwitchedLenLimits
|
|
return
|
|
}
|
|
|
|
// Defaults.
|
|
trueMinLen = o.LengthMin
|
|
if trueMinLen == 0 {
|
|
trueMinLen = DefMinLin
|
|
}
|
|
|
|
// Get a fixed password length...
|
|
passLenGap = o.LengthMax - trueMinLen
|
|
if passLen, err = saferRandInt(int(passLenGap)); err != nil {
|
|
return
|
|
}
|
|
passLen = passLen + int(trueMinLen) // (We need to re-add; it was subtracted above to get a zero-shifted number for start in saferRandInt.)
|
|
// And make the rune slice of that length.
|
|
passAlloc = make([]rune, passLen)
|
|
|
|
idx := 0
|
|
for idx < passLen {
|
|
var char Char
|
|
|
|
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
|
|
// Add upperCounter chars (if necessary).
|
|
for filter.upperCounter > 0 {
|
|
if char, err = upper.RandChar(); err != nil {
|
|
continue
|
|
}
|
|
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
|
|
err = nil
|
|
continue
|
|
}
|
|
passAlloc[idx] = rune(char)
|
|
filter.upperCounter--
|
|
idx++
|
|
continue
|
|
}
|
|
// Add lowerCounter chars (if necessary).
|
|
for filter.lowerCounter > 0 {
|
|
if char, err = lower.RandChar(); err != nil {
|
|
continue
|
|
}
|
|
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
|
|
err = nil
|
|
continue
|
|
}
|
|
passAlloc[idx] = rune(char)
|
|
filter.lowerCounter--
|
|
idx++
|
|
continue
|
|
}
|
|
// Add numberCounter chars (if necessary).
|
|
for filter.numberCounter > 0 {
|
|
if char, err = numeric.RandChar(); err != nil {
|
|
continue
|
|
}
|
|
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
|
|
err = nil
|
|
continue
|
|
}
|
|
passAlloc[idx] = rune(char)
|
|
filter.numberCounter--
|
|
idx++
|
|
continue
|
|
}
|
|
// Add symbolCounter chars (if necessary).
|
|
for filter.symbolCounter > 0 {
|
|
if char, err = symbols.RandChar(); err != nil {
|
|
continue
|
|
}
|
|
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
|
|
err = nil
|
|
continue
|
|
}
|
|
passAlloc[idx] = rune(char)
|
|
filter.symbolCounter--
|
|
idx++
|
|
continue
|
|
}
|
|
// Add extendedCounter chars (if necessary).
|
|
for filter.extendedCounter > 0 {
|
|
if char, err = extendedSymbols.RandChar(); err != nil {
|
|
continue
|
|
}
|
|
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
|
|
err = nil
|
|
continue
|
|
}
|
|
passAlloc[idx] = rune(char)
|
|
filter.extendedCounter--
|
|
idx++
|
|
continue
|
|
}
|
|
|
|
// Break here if we've reached the full password with just the filter.
|
|
if idx > passLen {
|
|
err = ErrTooSmall
|
|
return
|
|
} else if idx == passLen {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Minimum requirements satisfied; continue with default GenOpts-specific charset.
|
|
for {
|
|
|
|
if char, err = c.RandChar(); err != nil {
|
|
return
|
|
}
|
|
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
|
|
err = nil
|
|
continue
|
|
}
|
|
|
|
passAlloc[idx] = rune(char)
|
|
idx++
|
|
break
|
|
}
|
|
}
|
|
|
|
password = string(passAlloc)
|
|
passwordShuffle(&password)
|
|
|
|
return
|
|
}
|
|
|
|
// Chars returns the list of evaluated characters that would be used in a GenOpts.
|
|
func (o *GenOpts) Chars() (chars CharSet) {
|
|
|
|
chars = make(CharSet, 0)
|
|
|
|
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
|
|
chars = o.explicitCharset
|
|
return
|
|
}
|
|
|
|
if o.Alpha {
|
|
chars = append(chars, alpha...)
|
|
}
|
|
if o.Numeric {
|
|
chars = append(chars, numeric...)
|
|
}
|
|
if o.Symbols {
|
|
chars = append(chars, symbols...)
|
|
}
|
|
if o.ExtendedSymbols {
|
|
chars = append(chars, extendedSymbols...)
|
|
}
|
|
|
|
// TODO: Count* fields
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
SetExplicitCharset sets a GenOpts' charset to an explicit selection. It is deduplicated before being used unless inclDupes is true.
|
|
(Duplicate runes can be used to increase the likelihood of specific characters.)
|
|
|
|
Note that if an explicit charset is defined, the following opts will have NO effect:
|
|
|
|
Alpha
|
|
Numeric
|
|
Symbols
|
|
ExtendedSymbols
|
|
CountUpper
|
|
CountLower
|
|
CountSymbols
|
|
CountExtended
|
|
DisabledChars
|
|
|
|
To have these fields be used again, call GenOpts.UnsetExplicitCharset.
|
|
|
|
chars can be a CharSet, []rune, []byte, []string, or string
|
|
*/
|
|
func (o *GenOpts) SetExplicitCharset(chars interface{}, inclDupes bool) (err error) {
|
|
|
|
var charset CharSet
|
|
|
|
switch t := chars.(type) {
|
|
case []rune:
|
|
charset = CharSet(string(t))
|
|
case []byte:
|
|
charset = CharSet(string(t))
|
|
case []string:
|
|
s := strings.Join(t, "")
|
|
charset = CharSet(s)
|
|
case string:
|
|
charset = CharSet(t)
|
|
default:
|
|
err = ErrBadType
|
|
return
|
|
}
|
|
|
|
if !inclDupes {
|
|
sortDedupe(&charset)
|
|
}
|
|
|
|
o.explicitCharset = charset
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
UnsetExplicitCharset removes the explicit charset used for generation and instead uses the fields specified in the actual GenOpts.
|
|
|
|
See GenOpts.SetExplicitCharset for more details.
|
|
*/
|
|
func (o *GenOpts) UnsetExplicitCharset() {
|
|
|
|
o.explicitCharset = nil
|
|
|
|
return
|
|
}
|
|
|
|
// getFilter gets a filter counter.
|
|
func (o *GenOpts) getFilter() (f *selectFilter) {
|
|
|
|
f = &selectFilter{
|
|
upperCounter: o.CountUpper,
|
|
lowerCounter: o.CountLower,
|
|
numberCounter: o.CountNumbers,
|
|
symbolCounter: o.CountSymbols,
|
|
extendedCounter: o.CountExtended,
|
|
}
|
|
|
|
return
|
|
}
|