From 8b9269b1b9bf2c37eb738df2c3457ff52dc300c8 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 21 Mar 2021 15:28:23 +0100 Subject: [PATCH] Added spelling of pws. Ready for v0.2.4 --- apg.go | 23 ++++++++++- apg_test.go | 81 ++++++++++++++++++++++++++++++++++++++ convert.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ params.go | 82 ++++++++++++++++++++++----------------- 4 files changed, 258 insertions(+), 38 deletions(-) create mode 100644 convert.go diff --git a/apg.go b/apg.go index 1b33884..b10f48c 100644 --- a/apg.go +++ b/apg.go @@ -30,8 +30,10 @@ type cliOpts struct { humanReadable bool excludeChars string newStyleModes string + spellPassword bool showHelp bool showVersion bool + outputMode int } var config cliOpts @@ -43,7 +45,8 @@ func init() { flag.BoolVar(&config.useUpperCase, "U", false, "Use upper case characters in passwords") flag.BoolVar(&config.useNumber, "N", false, "Use numbers in passwords") flag.BoolVar(&config.useSpecial, "S", false, "Use special characters in passwords") - flag.BoolVar(&config.useComplex, "C", true, "Generate complex passwords (implies -L -U -N -S, disables -H)") + flag.BoolVar(&config.useComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)") + flag.BoolVar(&config.spellPassword, "l", false, "Spell generated password") flag.BoolVar(&config.humanReadable, "H", false, "Generate human-readable passwords") flag.BoolVar(&config.showVersion, "v", false, "Show version") @@ -76,6 +79,22 @@ func main() { fmt.Printf("getRandChar returned an error: %q\n", err.Error()) os.Exit(1) } - fmt.Println(pwString) + + switch config.outputMode { + case 1: + { + spelledPw, err := spellPasswordString(pwString) + if err != nil { + fmt.Printf("spellPasswordString returned an error: %q\n", err.Error()) + os.Exit(1) + } + fmt.Printf("%v (%v)\n", pwString, spelledPw) + } + default: + { + fmt.Println(pwString) + break + } + } } } diff --git a/apg_test.go b/apg_test.go index 779e3a1..ad9f0a0 100644 --- a/apg_test.go +++ b/apg_test.go @@ -207,6 +207,72 @@ func TestGetCharRange(t *testing.T) { }) } +// Test Conversions +func TestConvert(t *testing.T) { + t.Run("convert_A_to_Alfa", func(t *testing.T) { + charToString, err := convertCharToName('A') + if err != nil { + t.Errorf("Character to string conversion failed: %v", err.Error()) + } + if charToString != "Alfa" { + t.Errorf("Converting 'A' to string did not return the correct value of 'Alfa': %q", charToString) + } + }) + t.Run("convert_a_to_alfa", func(t *testing.T) { + charToString, err := convertCharToName('a') + if err != nil { + t.Errorf("Character to string conversion failed: %v", err.Error()) + } + if charToString != "alfa" { + t.Errorf("Converting 'a' to string did not return the correct value of 'alfa': %q", charToString) + } + }) + t.Run("convert_0_to_ZERO", func(t *testing.T) { + charToString, err := convertCharToName('0') + if err != nil { + t.Errorf("Character to string conversion failed: %v", err.Error()) + } + if charToString != "ZERO" { + t.Errorf("Converting '0' to string did not return the correct value of 'ZERO': %q", charToString) + } + }) + t.Run("convert_/_to_SLASH", func(t *testing.T) { + charToString, err := convertCharToName('/') + if err != nil { + t.Errorf("Character to string conversion failed: %v", err.Error()) + } + if charToString != "SLASH" { + t.Errorf("Converting '/' to string did not return the correct value of 'SLASH': %q", charToString) + } + }) + t.Run("all_chars_convert_to_string", func(t *testing.T) { + config.useUpperCase = true + config.useLowerCase = true + config.useNumber = true + config.useSpecial = true + config.humanReadable = false + charRange := getCharRange() + for _, curChar := range charRange { + _, err := convertCharToName(byte(curChar)) + if err != nil { + t.Errorf("Character to string conversion failed: %v", err.Error()) + } + } + }) + t.Run("spell_Ab!_to_strings", func(t *testing.T) { + pwString := "Ab!" + spelledString, err := spellPasswordString(pwString) + if err != nil { + t.Errorf("password spelling failed: %v", err.Error()) + } + if spelledString != "Alfa/bravo/EXCLAMATION_POINT" { + t.Errorf( + "Spelling pwString 'Ab!' is expected to provide 'Alfa/bravo/EXCLAMATION_POINT', but returned: %q", + spelledString) + } + }) +} + // Forced failures func TestForceFailures(t *testing.T) { t.Run("too_big_big.NewInt_value", func(t *testing.T) { @@ -241,6 +307,21 @@ func BenchmarkGetRandChar(b *testing.B) { } } +// Benchmark: Random char generation +func BenchmarkConvertChar(b *testing.B) { + config.useUpperCase = true + config.useLowerCase = true + config.useNumber = true + config.useSpecial = true + config.humanReadable = false + charRange := getCharRange() + for i := 0; i < b.N; i++ { + charToConv, _ := getRandChar(&charRange, 1) + charBytes := []byte(charToConv) + _, _ = convertCharToName(charBytes[0]) + } +} + // Contains function to search a given slice for values func containsByte(allowedBytes []int, currentChar int) bool { for _, charInt := range allowedBytes { diff --git a/convert.go b/convert.go new file mode 100644 index 0000000..c9436fe --- /dev/null +++ b/convert.go @@ -0,0 +1,110 @@ +package main + +import ( + "fmt" + "strings" +) + +var ( + symbNumNames = map[byte]string{ + '1': "ONE", + '2': "TWO", + '3': "THREE", + '4': "FOUR", + '5': "FIVE", + '6': "SIX", + '7': "SEVEN", + '8': "EIGHT", + '9': "NINE", + '0': "ZERO", + 33: "EXCLAMATION_POINT", + 34: "QUOTATION_MARK", + 35: "CROSSHATCH", + 36: "DOLLAR_SIGN", + 37: "PERCENT_SIGN", + 38: "AMPERSAND", + 39: "APOSTROPHE", + 40: "LEFT_PARENTHESIS", + 41: "RIGHT_PARENTHESIS", + 42: "ASTERISK", + 43: "PLUS_SIGN", + 44: "COMMA", + 45: "HYPHEN", + 46: "PERIOD", + 47: "SLASH", + 58: "COLON", + 59: "SEMICOLON", + 60: "LESS_THAN", + 61: "EQUAL_SIGN", + 62: "GREATER_THAN", + 63: "QUESTION_MARK", + 64: "AT_SIGN", + 91: "LEFT_BRACKET", + 92: "BACKSLASH", + 93: "RIGHT_BRACKET", + 94: "CIRCUMFLEX", + 95: "UNDERSCORE", + 96: "GRAVE", + 123: "LEFT_BRACE", + 124: "VERTICAL_BAR", + 125: "RIGHT_BRACE", + 126: "TILDE", + } + alphabetNames = map[byte]string{ + 'A': "Alfa", + 'B': "Bravo", + 'C': "Charlie", + 'D': "Delta", + 'E': "Echo", + 'F': "Foxtrot", + 'G': "Golf", + 'H': "Hotel", + 'I': "India", + 'J': "Juliett", + 'K': "Kilo", + 'L': "Lima", + 'M': "Mike", + 'N': "November", + 'O': "Oscar", + 'P': "Papa", + 'Q': "Quebec", + 'R': "Romeo", + 'S': "Sierra", + 'T': "Tango", + 'U': "Uniform", + 'V': "Victor", + 'W': "Whiskey", + 'X': "X_ray", + 'Y': "Yankee", + 'Z': "Zulu", + } +) + +func spellPasswordString(pwString string) (string, error) { + var returnString []string + for _, curChar := range pwString { + curSpellString, err := convertCharToName(byte(curChar)) + if err != nil { + return "", err + } + returnString = append(returnString, curSpellString) + } + return strings.Join(returnString, "/"), nil +} + +func convertCharToName(charByte byte) (string, error) { + var returnString string + if charByte > 64 && charByte < 91 { + returnString = alphabetNames[charByte] + } else if charByte > 96 && charByte < 123 { + returnString = strings.ToLower(alphabetNames[charByte-32]) + } else { + returnString = symbNumNames[charByte] + } + + if returnString == "" { + err := fmt.Errorf("cannot convert to character to name: %q is an unknown character", charByte) + return "", err + } + return returnString, nil +} diff --git a/params.go b/params.go index 7d213f3..48b2ae0 100644 --- a/params.go +++ b/params.go @@ -5,8 +5,52 @@ import ( "os" ) +// Parse the parameters and set the according config flags +func parseParams() { + parseNewStyleParams() + + // Complex overrides everything + if config.useComplex { + config.useUpperCase = true + config.useLowerCase = true + config.useSpecial = true + config.useNumber = true + config.humanReadable = false + } + + if config.useUpperCase == false && + config.useLowerCase == false && + config.useNumber == false && + config.useSpecial == false { + fmt.Printf("No password mode set. Cannot generate password from empty character set.") + os.Exit(1) + } + + // Set output mode + if config.spellPassword { + config.outputMode = 1 + } +} + +// Get the password length from the given cli flags +func getPwLengthFromParams() int { + pwLength := config.minPassLen + if pwLength < config.minPassLen { + pwLength = config.minPassLen + } + if pwLength > config.maxPassLen { + pwLength = config.maxPassLen + } + + return pwLength +} + // Parse the new style parameters func parseNewStyleParams() { + if config.newStyleModes == "" { + return + } + for _, curParam := range config.newStyleModes { switch curParam { case 'S': @@ -39,12 +83,11 @@ func parseNewStyleParams() { case 'h': config.humanReadable = false break - // APG compatbilitly case 'C': - config.useUpperCase = true + config.useComplex = true break case 'c': - config.useUpperCase = false + config.useComplex = false break default: fmt.Printf("Unknown password style parameter: %q\n", string(curParam)) @@ -52,36 +95,3 @@ func parseNewStyleParams() { } } } - -func getPwLengthFromParams() int { - pwLength := config.minPassLen - if pwLength < config.minPassLen { - pwLength = config.minPassLen - } - if pwLength > config.maxPassLen { - pwLength = config.maxPassLen - } - - return pwLength -} - -func parseParams() { - if config.useComplex { - config.useUpperCase = true - config.useLowerCase = true - config.useSpecial = true - config.useNumber = true - config.humanReadable = false - } - if config.newStyleModes != "" { - parseNewStyleParams() - } - - if config.useUpperCase == false && - config.useLowerCase == false && - config.useNumber == false && - config.useSpecial == false { - fmt.Printf("No password mode set. Cannot generate password from empty character set.") - os.Exit(1) - } -}