-
Notifications
You must be signed in to change notification settings - Fork 0
/
random.go
130 lines (100 loc) · 3.27 KB
/
random.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package acopw
import (
"crypto/rand"
"io"
mrand "math/rand/v2"
"git.sr.ht/~jamesponddotco/xstd-go/xerrors"
"git.sr.ht/~jamesponddotco/xstd-go/xstrings"
)
// ErrInvalidCharset is returned when the internal character set is empty.
const ErrInvalidCharset xerrors.Error = "no characters to build password in the charset"
// DefaultRandomLength is the default length of a random password.
const DefaultRandomLength int = 128
// Random is a policy for generating ChaCha8-based cryptographically strong
// random passwords.
type Random struct {
// random provides the source of entropy for generating the password.
random *mrand.Rand
// characters is the character set to use for generating the password.
characters []string
// ExcludedCharset is a list of characters that should not be included in
// the generated password.
ExcludedCharset []string
// Length is the length of the password. If less than 1, it defaults to 128.
Length int
// UseLower, UseUpper, UseNumbers, and UseSymbols specify whether or not to
// use the corresponding character class in the generated password.
//
// If none of these are true, it defaults to true for all four.
UseLower bool
UseUpper bool
UseNumbers bool
UseSymbols bool
}
// Generate returns a cryptographically strong random password for the policy.
// It panics if it can't get entropy from the source of randomness or if the
// internally generated character set is empty.
func (r *Random) Generate() string { //nolint:unparam // appears to be a false positive
if r.random == nil {
var seed [32]byte
if _, err := io.ReadFull(rand.Reader, seed[:]); err != nil {
panic(err)
}
r.random = mrand.New(mrand.NewChaCha8(seed)) //nolint:gosec // we seed with crypto/rand
}
if r.Length < 1 {
r.Length = DefaultRandomLength
}
if !r.UseLower && !r.UseUpper && !r.UseNumbers && !r.UseSymbols {
r.UseLower = true
r.UseUpper = true
r.UseNumbers = true
r.UseSymbols = true
}
charset := r.charset()
if len(charset) == 0 {
panic(ErrInvalidCharset)
}
password := make([]string, 0, r.Length)
for range r.Length {
var (
index = r.random.IntN(len(charset))
char = charset[index]
)
password = append(password, char)
}
return xstrings.Join(password...)
}
// Charset returns the character set to use for generating the password.
func (r *Random) charset() []string {
if r.characters == nil { //nolint:nestif // what other way is there?
charset := make([]string, 0, len(_charsetLower)+len(_charsetUpper)+len(_charsetNumbers)+len(_charsetSymbols))
if r.UseLower {
charset = append(charset, _charsetLower...)
}
if r.UseUpper {
charset = append(charset, _charsetUpper...)
}
if r.UseNumbers {
charset = append(charset, _charsetNumbers...)
}
if r.UseSymbols {
charset = append(charset, _charsetSymbols...)
}
if len(r.ExcludedCharset) > 0 {
excludedChars := make(map[string]bool, len(r.ExcludedCharset))
for _, char := range r.ExcludedCharset {
excludedChars[char] = true
}
filteredCharset := make([]string, 0, len(charset))
for _, char := range charset { //nolint:wsl // looks like a false positive to me
if !excludedChars[char] {
filteredCharset = append(filteredCharset, char)
}
}
charset = filteredCharset
}
r.characters = charset
}
return r.characters
}