@@ -11,7 +11,6 @@ import (
1111 "errors"
1212 "fmt"
1313 "math/big"
14- "strings"
1514 "syscall"
1615 "unsafe"
1716
@@ -20,7 +19,16 @@ import (
2019)
2120
2221const (
23- passwordLength = 127 // maximum length allowed by Windows
22+ // Reference: https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/password-must-meet-complexity-requirements
23+ passwordMinLength = 64 // Minimum length for better security
24+ passwordMaxLength = 127 // Upper limit to avoid policy issues
25+ // Character pools - ensuring a mix of character categories
26+ passwordCharsLower = "abcdefghijklmnopqrstuvwxyz"
27+ passwordCharsUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
28+ passwordCharsDigits = "1234567890"
29+ passwordCharsSpecial = "'-!\" #$%&()*,./:;?@[]^_`{|}~+<=>" //nolint:gosec // G101: False positive on Potential hardcoded credentials
30+ // Combined pool for general randomization
31+ passwordAllChars = passwordCharsLower + passwordCharsUpper + passwordCharsDigits + passwordCharsSpecial
2432)
2533
2634var (
@@ -238,19 +246,81 @@ func SetUserPassword(name string, password string) error {
238246 return nil
239247}
240248
241- // RandomPassword generates a random password.
249+ // RandomPassword generates a secure password that meets Windows policy requirements .
242250func RandomPassword () (string , error ) {
243- runes := []rune ("abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*ABCDEFGHIJKLMNOPQRSTUVWXYZ" )
244- maxN := big .NewInt (int64 (len (runes )))
245- var sb strings.Builder
246- for i := 0 ; i < passwordLength ; i ++ {
247- n , err := rand .Int (rand .Reader , maxN )
251+ // Generate a random length within the allowed range
252+ length , err := rand .Int (rand .Reader , big .NewInt (passwordMaxLength - passwordMinLength + 1 ))
253+ if err != nil {
254+ return "" , fmt .Errorf ("failed to generate random length: %w" , err )
255+ }
256+ passwordLength := int (length .Int64 ()) + passwordMinLength
257+ password := make ([]rune , passwordLength )
258+
259+ // Ensure Windows password constraints are met by guaranteeing at least one character from each category
260+ filledPositions := make (map [int ]any )
261+ for _ , chars := range []string {passwordCharsUpper , passwordCharsLower , passwordCharsDigits , passwordCharsSpecial } {
262+ // Generate a random position for the character
263+ var position int
264+ for {
265+ posBigInt , err := rand .Int (rand .Reader , big .NewInt (int64 (passwordLength )))
266+ if err != nil {
267+ return "" , fmt .Errorf ("failed to generate random position: %w" , err )
268+ }
269+ position = int (posBigInt .Int64 ())
270+ if _ , filled := filledPositions [position ]; filled {
271+ // Position already has a missing character, generate a new one
272+ continue
273+ }
274+ break
275+ }
276+ // Generate a random character from the given characters
277+ char , err := randomChar (chars )
278+ if err != nil {
279+ // err information is added by randomChar
280+ return "" , err
281+ }
282+ // Insert the character into the password
283+ password [position ] = char
284+ filledPositions [position ] = nil
285+ }
286+
287+ // Fill the remaining positions with random characters
288+ for i := 0 ; i < passwordLength ; {
289+ // Check if the current position has already been filled
290+ if _ , filled := filledPositions [i ]; filled {
291+ // Position already has a character
292+ i ++
293+ continue
294+ }
295+ // Generate a random character from the combined pool of characters
296+ char , err := randomChar (passwordAllChars )
248297 if err != nil {
249- return "" , fmt .Errorf ("failed to generate random integer: %w" , err )
298+ // err information is added by randomChar
299+ return "" , err
300+ }
301+ // Ensure no consecutive duplicate characters to the left
302+ if i > 0 && password [i - 1 ] == char {
303+ continue
250304 }
251- sb .WriteRune (runes [n .Int64 ()])
305+ // Ensure no consecutive duplicate characters to the right
306+ if i + 1 < passwordLength && password [i + 1 ] == char {
307+ continue
308+ }
309+ // Insert the character into the password
310+ password [i ] = char
311+ i ++
312+ }
313+
314+ return string (password ), nil
315+ }
316+
317+ // randomChar selects a random character from a given string.
318+ func randomChar (charset string ) (rune , error ) {
319+ n , err := rand .Int (rand .Reader , big .NewInt (int64 (len (charset ))))
320+ if err != nil {
321+ return 0 , fmt .Errorf ("failed to generate random character: %w" , err )
252322 }
253- return sb . String ( ), nil
323+ return rune ( charset [ n . Int64 ()] ), nil
254324}
255325
256326// LOCALGROUP_INFO_0 structure contains a local group name.
0 commit comments