1 package crypto
2
3 import (
4 "regexp"
5 "testing"
6 "time"
7
8 "github.com/stretchr/testify/assert"
9 "golang.org/x/crypto/bcrypt"
10
11 "edge-infra.dev/pkg/lib/crypto/validation"
12 )
13
14 var (
15 runCostTimeTests = false
16 minimumPassLen = validation.MinimumPassLen
17 maximumPassLen = validation.MaximumPassLen
18 minimumSaltLen = validation.MinimumSaltLen
19 minimumHashIterations = validation.MinimumHashIterations
20 )
21
22 func TestBcryptPwdLenBounds(t *testing.T) {
23
24 _, err := GenerateRandomBcryptPassword(minimumPassLen-1, minimumPassLen+4, bcrypt.DefaultCost)
25 assertPwdError(t, err)
26
27
28 _, err = GenerateRandomBcryptPassword(minimumPassLen, maximumPassLen+1, bcrypt.DefaultCost)
29 assertPwdError(t, err)
30
31
32 _, err = GenerateRandomBcryptPassword(minimumPassLen-1, maximumPassLen+1, bcrypt.DefaultCost)
33 assertPwdError(t, err)
34
35
36 _, err = GenerateRandomBcryptPassword(maximumPassLen, minimumPassLen, bcrypt.DefaultCost)
37 assertPwdError(t, err)
38
39
40 _, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen, bcrypt.DefaultCost)
41 assertPwdError(t, err)
42 }
43
44 func TestBcryptPwdCostBounds(t *testing.T) {
45
46 _, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost)
47 assert.NoError(t, err)
48
49
50 _, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost-1)
51 assertCostError(t, err)
52
53
54 _, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.MaxCost+1)
55 assertCostError(t, err)
56 }
57
58 func TestBcryptPwd(t *testing.T) {
59 pwdResponse, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost)
60 assert.NoError(t, err)
61 match := CompareBcryptHashAndPassword(pwdResponse.Hashed(), pwdResponse.Plain())
62 assert.True(t, match)
63 }
64
65 func TestBcryptPwdRandom(t *testing.T) {
66 pwdResponseFirst, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost)
67 assert.NoError(t, err)
68
69 pwdResponseSecond, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost)
70 assert.NoError(t, err)
71
72 assert.NotEqual(t, pwdResponseFirst, pwdResponseSecond)
73 }
74
75 func TestBycryptCostTime(t *testing.T) {
76 if !runCostTimeTests {
77 t.SkipNow()
78 }
79
80 start := bcrypt.DefaultCost
81 end := bcrypt.DefaultCost + 6
82 for i := start; i <= end; i++ {
83 pwdResponse, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, i)
84 assert.NoError(t, err)
85 t1 := time.Now()
86 match := CompareBcryptHashAndPassword(pwdResponse.Hashed(), pwdResponse.Plain())
87 assert.True(t, match)
88 t2 := time.Now()
89 diff := t2.Sub(t1)
90 t.Logf("Cost: %d took %s", i, diff.String())
91 }
92 }
93
94 func TestPbkdf2PwdLenBounds(t *testing.T) {
95
96 _, err := GenerateRandomPbkdf2Password(minimumPassLen-1, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen)
97 assertPwdError(t, err)
98
99
100 _, err = GenerateRandomPbkdf2Password(minimumPassLen, maximumPassLen+1, minimumHashIterations, minimumSaltLen, minimumSaltLen)
101 assertPwdError(t, err)
102
103
104 _, err = GenerateRandomPbkdf2Password(minimumPassLen-1, maximumPassLen+1, minimumHashIterations, minimumSaltLen, minimumSaltLen)
105 assertPwdError(t, err)
106
107
108 _, err = GenerateRandomPbkdf2Password(maximumPassLen, minimumPassLen, minimumHashIterations, minimumSaltLen, minimumSaltLen)
109 assertPwdError(t, err)
110
111
112 _, err = GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen, minimumHashIterations, minimumSaltLen, minimumSaltLen)
113 assertPwdError(t, err)
114 }
115
116 func TestPbkdf2InvalidSaltLength(t *testing.T) {
117 _, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen-1, minimumSaltLen)
118 assertSaltError(t, err)
119 }
120
121 func TestPbkdf2InvalidIterationLength(t *testing.T) {
122 _, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations-10000, minimumSaltLen, minimumSaltLen)
123 assertIterationsError(t, err)
124 }
125
126 func TestPbkdf2InvalidKeyLength(t *testing.T) {
127
128 _, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen-10)
129 assertKeyError(t, err)
130
131
132 _, err = GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, 64, minimumSaltLen)
133 assertKeyError(t, err)
134 }
135
136 func TestPbkdf2CompareHash(t *testing.T) {
137
138 pwdResponse, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen)
139 assert.NoError(t, err)
140
141 salt, err := pwdResponse.Salt()
142 assert.NoError(t, err)
143
144 iterations, err := pwdResponse.Iterations()
145 assert.NoError(t, err)
146
147 match := ComparePbkdf2HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain(), iterations, minimumSaltLen)
148 assert.True(t, match)
149 }
150
151 func TestPbkdf2IterationsTime(t *testing.T) {
152 if !runCostTimeTests {
153 t.SkipNow()
154 }
155
156 start := 310000
157 end := start + 90000
158 for i := start; i <= end; i = i + 10000 {
159 pwdResponse, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, i, minimumSaltLen, minimumSaltLen)
160 assert.NoError(t, err)
161
162 t1 := time.Now()
163
164 salt, err := pwdResponse.Salt()
165 assert.NoError(t, err)
166
167 iterations, err := pwdResponse.Iterations()
168 assert.NoError(t, err)
169
170 match := ComparePbkdf2HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain(), iterations, minimumSaltLen)
171 assert.True(t, match)
172 t2 := time.Now()
173 diff := t2.Sub(t1)
174 t.Logf("Iterations: %d took %s", i, diff.String())
175 }
176 }
177
178 func TestSha512PwdLenBounds(t *testing.T) {
179
180 _, err := GenerateRandomSha512Password(minimumPassLen-1, minimumPassLen+4, minimumSaltLen)
181 assertPwdError(t, err)
182
183
184 _, err = GenerateRandomSha512Password(minimumPassLen, maximumPassLen+1, minimumSaltLen)
185 assertPwdError(t, err)
186
187
188 _, err = GenerateRandomSha512Password(minimumPassLen-1, maximumPassLen+1, minimumSaltLen)
189 assertPwdError(t, err)
190
191
192 _, err = GenerateRandomSha512Password(maximumPassLen, minimumPassLen, minimumSaltLen)
193 assertPwdError(t, err)
194
195
196 _, err = GenerateRandomSha512Password(minimumPassLen, minimumPassLen, minimumSaltLen)
197 assertPwdError(t, err)
198 }
199
200 func TestSha512InvalidSaltLength(t *testing.T) {
201 _, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen-1)
202 assertSaltError(t, err)
203 }
204
205 func TestSha512HashComparison(t *testing.T) {
206 pwdResponse, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen)
207 assert.NoError(t, err)
208
209 salt, err := pwdResponse.Salt()
210 assert.NoError(t, err)
211
212 match := CompareSha512HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain())
213 assert.True(t, match)
214 }
215
216 func TestSha512Time(t *testing.T) {
217 if !runCostTimeTests {
218 t.SkipNow()
219 }
220
221 pwdResponse, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen)
222 assert.NoError(t, err)
223
224 t1 := time.Now()
225 salt, err := pwdResponse.Salt()
226 assert.NoError(t, err)
227 match := CompareSha512HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain())
228 assert.True(t, match)
229 t2 := time.Now()
230 diff := t2.Sub(t1)
231 t.Logf("Sha512 hash took %s", diff.String())
232 }
233
234 func TestSerializationAndDeserializationPbkdf2(t *testing.T) {
235 pwdResponse, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen)
236 assert.NoError(t, err)
237
238 salt, err := pwdResponse.Salt()
239 assert.NoError(t, err)
240
241 iterations, err := pwdResponse.Iterations()
242 assert.NoError(t, err)
243
244 secret := NewSecret(
245 pwdResponse.Hashed(),
246 salt,
247 "recovery",
248 pwdResponse.HashFunction(),
249 pwdResponse.HashType(),
250 iterations,
251 1,
252 false,
253 )
254
255 serializedHashedSecretDataMap, err := secret.SerializeAndBase64Encode()
256 assert.NoError(t, err)
257
258 deserializedHashedSecretDataMap, err := NewSecretFromString(serializedHashedSecretDataMap)
259 assert.NoError(t, err)
260
261 assert.Equal(t, *secret, deserializedHashedSecretDataMap)
262 }
263
264 func TestSerializationAndDeserializationSha512(t *testing.T) {
265 pwdResponse, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen)
266 assert.NoError(t, err)
267
268 salt, err := pwdResponse.Salt()
269 assert.NoError(t, err)
270
271 secret := NewSecret(
272 pwdResponse.Hashed(),
273 salt,
274 "recovery",
275 pwdResponse.HashFunction(),
276 pwdResponse.HashType(),
277 1,
278 1,
279 false,
280 )
281
282 serializedHashedSecretDataMap, err := secret.SerializeAndBase64Encode()
283 assert.NoError(t, err)
284
285 deserializedHashedSecretDataMap, err := NewSecretFromString(serializedHashedSecretDataMap)
286 assert.NoError(t, err)
287
288 assert.Equal(t, *secret, deserializedHashedSecretDataMap)
289 }
290
291 func TestActivationCodes(t *testing.T) {
292 pwdResponse, err := GenerateRandomActivationCode()
293 assert.NoError(t, err)
294
295 match, err := regexp.MatchString("[A-Z1-9]+", pwdResponse.Plain())
296 assert.NoError(t, err)
297 assert.True(t, match)
298
299 assert.Equal(t, activationLength, len(pwdResponse.Plain()))
300
301 resultHash := HashActivation(pwdResponse.Plain())
302 assert.Equal(t, resultHash, pwdResponse.Hashed())
303 }
304
305 func TestEdgeBootstrapTokens(t *testing.T) {
306 pwdResponse, err := GenerateRandomEdgeBootstrapToken()
307 assert.NoError(t, err)
308
309 resultHash := HashEdgeBootstrapToken([]byte(pwdResponse.Plain()))
310 assert.Equal(t, resultHash, pwdResponse.Hashed())
311 }
312
313 func assertPwdError(t *testing.T, err error) {
314 pwdLengthError := validation.InvalidPasswordLengthError().Error()
315 assert.Equal(t, err.Error(), pwdLengthError)
316 }
317
318 func assertCostError(t *testing.T, err error) {
319 costError := validation.InvalidCostError().Error()
320 assert.Equal(t, err.Error(), costError)
321 }
322
323 func assertSaltError(t *testing.T, err error) {
324 saltLengthError := validation.InvalidSaltLengthError().Error()
325 assert.Equal(t, err.Error(), saltLengthError)
326 }
327
328 func assertKeyError(t *testing.T, err error) {
329 keyLengthError := validation.InvalidKeyLengthError().Error()
330 assert.Equal(t, err.Error(), keyLengthError)
331 }
332
333 func assertIterationsError(t *testing.T, err error) {
334 iterationError := validation.InvalidHashIterationsError().Error()
335 assert.Equal(t, err.Error(), iterationError)
336 }
337
View as plain text