char mins done; need to shuffle some error condition checks before
This commit is contained in:
@@ -5,7 +5,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadType error = errors.New("cannot typeswitch; unsupported type")
|
||||
ErrTooSmall error = errors.New("password max length too short for specified required chars")
|
||||
ErrSwitchedLenLimits error = errors.New("the max password length is shorter than the minimum password length")
|
||||
ErrBadType error = errors.New("cannot typeswitch; unsupported type")
|
||||
ErrIncompatCharsetFilter error = errors.New("the selected minimum requirements are not possible with selected/enabled charsets")
|
||||
ErrSwitchedLenLimits error = errors.New("the max password length is shorter than the minimum password length")
|
||||
ErrTooSmall error = errors.New("password max length too short for specified required chars")
|
||||
)
|
||||
|
||||
@@ -3,10 +3,16 @@ package pwgenerator
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
insecureRand "math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// newCryptoShuffler returns a new cryptoShuffler.
|
||||
func newCryptoShuffler() *cryptoShuffler {
|
||||
return new(cryptoShuffler)
|
||||
}
|
||||
|
||||
/*
|
||||
GetCharset returns a CharSet from a set of characters.
|
||||
|
||||
@@ -86,3 +92,22 @@ func saferRandInt(max int) (randInt int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// passwordShuffle shuffles a password's characters ordering so we don't have the condition satisfiers in the beginning.
|
||||
func passwordShuffle(passwd *string) {
|
||||
|
||||
var r *insecureRand.Rand = insecureRand.New(newCryptoShuffler())
|
||||
|
||||
r.Shuffle(
|
||||
len(*passwd),
|
||||
func(i, j int) {
|
||||
var chars []rune = []rune(*passwd)
|
||||
|
||||
chars[i], chars[j] = chars[j], chars[i]
|
||||
|
||||
*passwd = string(chars)
|
||||
},
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,11 +82,7 @@ func (c *CharSet) String() (s string) {
|
||||
// Swap will swap the position of the item at index i and the item at index j in a CharSet (needed for sort.Interface).
|
||||
func (c *CharSet) Swap(i, j int) {
|
||||
|
||||
var iVal Char = (*c)[i]
|
||||
var jVal Char = (*c)[j]
|
||||
|
||||
(*c)[i] = jVal
|
||||
(*c)[j] = iVal
|
||||
(*c)[j], (*c)[i] = (*c)[i], (*c)[j]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
27
pwgenerator/funcs_cryptoshuffer.go
Normal file
27
pwgenerator/funcs_cryptoshuffer.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package pwgenerator
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// Int63 is used to interface as a *(math/rand).Rand but backed with a cryptographically sound generation.
|
||||
func (c *cryptoShuffler) Int63() (i int64) {
|
||||
|
||||
var b [8]byte
|
||||
|
||||
_, _ = rand.Read(b[:])
|
||||
|
||||
// Mask is used to ensure a positive number.
|
||||
i = int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Seed is used to interface as a *(math/rand).Rand.
|
||||
func (c *cryptoShuffler) Seed(_ int64) {
|
||||
|
||||
// It's 100% OK that this is a no-op because we seed from the crypto methodology itself within cryptoShuffler.Int63 on each call.
|
||||
|
||||
return
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func (o *GenOpts) Generate() (passwords []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// generatePassword generates a single password from CharSet c.
|
||||
// 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
|
||||
@@ -44,9 +44,33 @@ func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
|
||||
var passLenGap uint
|
||||
var passLen int
|
||||
var passAlloc []rune
|
||||
var filter *selectFilter = o.getFilter()
|
||||
var isDisabled bool
|
||||
|
||||
// Sanity checks/error conditions.
|
||||
maxMin = o.CountUpper + o.CountLower + o.CountSymbols + o.CountExtended
|
||||
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
|
||||
@@ -71,11 +95,93 @@ func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
|
||||
// And make the rune slice of that length.
|
||||
passAlloc = make([]rune, passLen)
|
||||
|
||||
for idx, _ := range passAlloc {
|
||||
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 {
|
||||
var isDisabled bool
|
||||
|
||||
if char, err = c.RandChar(); err != nil {
|
||||
return
|
||||
@@ -86,11 +192,13 @@ func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
|
||||
}
|
||||
|
||||
passAlloc[idx] = rune(char)
|
||||
idx++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
password = string(passAlloc)
|
||||
passwordShuffle(&password)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -182,3 +290,17 @@ func (o *GenOpts) UnsetExplicitCharset() {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package pwgenerator
|
||||
|
||||
// cryptoShuffler is used to shuffle a slice in a cryptographically sane way.
|
||||
type cryptoShuffler struct{}
|
||||
|
||||
// Char is implemented as a rune.
|
||||
type Char rune
|
||||
|
||||
@@ -32,6 +35,8 @@ type GenOpts struct {
|
||||
CountUpper uint `json:"uppers"`
|
||||
// CountLower specifies how many lowercase letters (0x61 to 0x7a) should be specified at a minimum.
|
||||
CountLower uint `json:"lowers"`
|
||||
// CountNumbers specifies how many numbers (0x30 to 0x39) should be specified at a minimum.
|
||||
CountNumbers uint `json:"numbers"`
|
||||
// CountSymbols specifies how many symbols (0x21 to 0x7e) should be specified at a minimum.
|
||||
CountSymbols uint `json:"symbols"`
|
||||
// CountExtended specifies how many extended symbols (0x80 to 0xff) should be specified at a minimum.
|
||||
@@ -50,3 +55,12 @@ type GenOpts struct {
|
||||
// explicitCharset is the collection of acceptable characters as explicitly defined by the caller, if any.
|
||||
explicitCharset CharSet
|
||||
}
|
||||
|
||||
// selectFilter is used to include specified number of characters.
|
||||
type selectFilter struct {
|
||||
upperCounter uint
|
||||
lowerCounter uint
|
||||
numberCounter uint
|
||||
symbolCounter uint
|
||||
extendedCounter uint
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user