airshipctl/pkg/secret/passphrases.go
Ian Howell 9e2d6932f6 Add document command and its first subcommand
This adds the "airshipctl document" tree of commands, as well as one of
its leaf commands, "generate masterpassphrase".

Change-Id: I2365ebe67b38ebbbe4873c6e1beb6407f0e000eb
2019-08-28 09:42:31 -05:00

99 lines
2.1 KiB
Go

package secret
import (
"math/rand"
"strings"
)
const (
defaultLength = 24
asciiLowers = "abcdefghijklmnopqrstuvwxyz"
asciiUppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
asciiNumbers = "0123456789"
asciiSymbols = "@#&-+=?"
)
var (
// pool is the complete collection of characters that can be used for a
// passphrase
pool []byte
)
func init() {
pool = append(pool, []byte(asciiLowers)...)
pool = append(pool, []byte(asciiUppers)...)
pool = append(pool, []byte(asciiNumbers)...)
pool = append(pool, []byte(asciiSymbols)...)
}
// PassphraseEngine is used to generate secure random passphrases
type PassphraseEngine struct {
rng *rand.Rand
pool []byte
}
// NewPassphraseEngine creates an PassphraseEngine using src. If src is nil,
// the returned PassphraseEngine will use the default Source
func NewPassphraseEngine(src rand.Source) *PassphraseEngine {
if src == nil {
src = &Source{}
}
return &PassphraseEngine{
rng: rand.New(src),
pool: pool,
}
}
// GeneratePassphrase returns a secure random string of length 24,
// containing at least one of each from the following sets:
// [0-9]
// [a-z]
// [A-Z]
// [@#&-+=?]
func (e *PassphraseEngine) GeneratePassphrase() string {
return e.GeneratePassphraseN(defaultLength)
}
// GeneratePassphraseN returns a secure random string containing at least
// one of each from the following sets. Its length will be max(length, 24)
// [0-9]
// [a-z]
// [A-Z]
// [@#&-+=?]
func (e *PassphraseEngine) GeneratePassphraseN(length int) string {
if length < defaultLength {
length = defaultLength
}
var passPhrase string
for !e.isValidPassphrase(passPhrase) {
var sb strings.Builder
for i := 0; i < length; i++ {
randIndex := e.rng.Intn(len(e.pool))
randChar := e.pool[randIndex]
sb.WriteString(string(randChar))
}
passPhrase = sb.String()
}
return passPhrase
}
func (e *PassphraseEngine) isValidPassphrase(passPhrase string) bool {
if len(passPhrase) < defaultLength {
return false
}
charSets := []string{
asciiLowers,
asciiUppers,
asciiNumbers,
asciiSymbols,
}
for _, charSet := range charSets {
if !strings.ContainsAny(passPhrase, charSet) {
return false
}
}
return true
}