package pwgenerator import ( "encoding/xml" "strings" "time" "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 var errs *multierr.MultiError = multierr.NewMultiError(nil) if o.Count == 0 { o.Count = DefCount } if o.LengthMax == 0 { o.LengthMax = DefMaxLen } if err = o.sanChk(); err != nil { return } if charset, err = o.Chars(); err != nil { return } 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 } // MustGenerate is like Generate, but will panic on error instead of returning. func (o *GenOpts) MustGenerate() (passwords []string) { var err error if passwords, err = o.Generate(); err != nil { panic(err) } return } /* GenOnce generates a *single* password from a GenOpts. This method is particularly useful/convenient if GenOpts.Count is 1 and you don't want to have to assign to a slice and then get the first index. */ func (o GenOpts) GenerateOnce() (password string, err error) { var passwds []string o.Count = 1 if passwds, err = o.Generate(); err != nil { return } password = passwds[0] return } // MustGenerateOnce is like GenerateOnce, but will panic on error instead of returning. func (o *GenOpts) MustGenerateOnce() (password string) { var err error if password, err = o.GenerateOnce(); err != nil { panic(err) } return } /* GenerateCollection returns a PwCollection instead of a slice of password text. It contains extended information about a password, hashes, etc. Note: hashing support currently under construction and not implemented. TODO. */ func (o *GenOpts) GenerateCollection(hashAlgos []pwHash) (collection *PwCollection, err error) { var charset CharSet var errs *multierr.MultiError = multierr.NewMultiError(nil) var passwd string collection = &PwCollection{ XMLName: xml.Name{ Space: "", // TODO? Local: "collection", }, Passwords: make([]*PwDef, o.Count), } if o.Count == 0 { o.Count = DefCount } if o.LengthMax == 0 { o.LengthMax = DefMaxLen } if err = o.sanChk(); err != nil { return } if charset, err = o.Chars(); err != nil { return } for idx, _ := range collection.Passwords { if passwd, err = o.generatePassword(charset); err != nil { errs.AddError(err) err = nil } collection.Passwords[idx] = &PwDef{ XMLName: xml.Name{ Space: "", // TODO? Local: "password", }, Password: passwd, Generated: time.Now(), Hashes: nil, // TODO } } return } // generatePassword generates a single password from CharSet c (plus any minimum requirements). func (o *GenOpts) generatePassword(c CharSet) (password string, err error) { var trueMinLen uint var passLenGap uint var passLen int var passAlloc []rune var filter *selectFilter = o.getFilter() var isDisabled bool if err = o.sanChk(); err != nil { 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, err error) { chars = make(CharSet, 0) if err = o.sanChk(); err != nil { return } 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...) } if chars == nil || len(chars) == 0 { err = ErrEmptyCharsets return } 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 } func (o *GenOpts) sanChk() (err error) { var maxMin uint // 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 } return }