package crypto import ( "regexp" "testing" "time" "github.com/stretchr/testify/assert" "golang.org/x/crypto/bcrypt" "edge-infra.dev/pkg/lib/crypto/validation" ) var ( runCostTimeTests = false minimumPassLen = validation.MinimumPassLen maximumPassLen = validation.MaximumPassLen minimumSaltLen = validation.MinimumSaltLen minimumHashIterations = validation.MinimumHashIterations ) func TestBcryptPwdLenBounds(t *testing.T) { // Fail lower bounds _, err := GenerateRandomBcryptPassword(minimumPassLen-1, minimumPassLen+4, bcrypt.DefaultCost) assertPwdError(t, err) // Fail upper bounds _, err = GenerateRandomBcryptPassword(minimumPassLen, maximumPassLen+1, bcrypt.DefaultCost) assertPwdError(t, err) // Fail upper and lower bounds _, err = GenerateRandomBcryptPassword(minimumPassLen-1, maximumPassLen+1, bcrypt.DefaultCost) assertPwdError(t, err) // Fail lower bound greater than upper bounds _, err = GenerateRandomBcryptPassword(maximumPassLen, minimumPassLen, bcrypt.DefaultCost) assertPwdError(t, err) // Bounds cannot be equal (password must have random length) _, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen, bcrypt.DefaultCost) assertPwdError(t, err) } func TestBcryptPwdCostBounds(t *testing.T) { // Accepted default bounds _, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost) assert.NoError(t, err) // Fail lower bounds _, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost-1) assertCostError(t, err) // Fail upper bounds _, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.MaxCost+1) assertCostError(t, err) } func TestBcryptPwd(t *testing.T) { pwdResponse, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost) assert.NoError(t, err) match := CompareBcryptHashAndPassword(pwdResponse.Hashed(), pwdResponse.Plain()) assert.True(t, match) } func TestBcryptPwdRandom(t *testing.T) { pwdResponseFirst, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost) assert.NoError(t, err) pwdResponseSecond, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost) assert.NoError(t, err) assert.NotEqual(t, pwdResponseFirst, pwdResponseSecond) } func TestBycryptCostTime(t *testing.T) { if !runCostTimeTests { t.SkipNow() } start := bcrypt.DefaultCost end := bcrypt.DefaultCost + 6 for i := start; i <= end; i++ { pwdResponse, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, i) assert.NoError(t, err) t1 := time.Now() match := CompareBcryptHashAndPassword(pwdResponse.Hashed(), pwdResponse.Plain()) assert.True(t, match) t2 := time.Now() diff := t2.Sub(t1) t.Logf("Cost: %d took %s", i, diff.String()) } } func TestPbkdf2PwdLenBounds(t *testing.T) { // Fail lower bounds _, err := GenerateRandomPbkdf2Password(minimumPassLen-1, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen) assertPwdError(t, err) // Fail upper bounds _, err = GenerateRandomPbkdf2Password(minimumPassLen, maximumPassLen+1, minimumHashIterations, minimumSaltLen, minimumSaltLen) assertPwdError(t, err) // Fail upper and lower bounds _, err = GenerateRandomPbkdf2Password(minimumPassLen-1, maximumPassLen+1, minimumHashIterations, minimumSaltLen, minimumSaltLen) assertPwdError(t, err) // Fail lower bound greater than upper bounds _, err = GenerateRandomPbkdf2Password(maximumPassLen, minimumPassLen, minimumHashIterations, minimumSaltLen, minimumSaltLen) assertPwdError(t, err) // Bounds cannot be equal (password must have random length) _, err = GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen, minimumHashIterations, minimumSaltLen, minimumSaltLen) assertPwdError(t, err) } func TestPbkdf2InvalidSaltLength(t *testing.T) { _, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen-1, minimumSaltLen) assertSaltError(t, err) } func TestPbkdf2InvalidIterationLength(t *testing.T) { _, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations-10000, minimumSaltLen, minimumSaltLen) assertIterationsError(t, err) } func TestPbkdf2InvalidKeyLength(t *testing.T) { // Key length less than 256-bits (32 bytes) _, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen-10) assertKeyError(t, err) // Key length less than salt length _, err = GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, 64, minimumSaltLen) assertKeyError(t, err) } func TestPbkdf2CompareHash(t *testing.T) { // Key length less than salt length pwdResponse, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen) assert.NoError(t, err) salt, err := pwdResponse.Salt() assert.NoError(t, err) iterations, err := pwdResponse.Iterations() assert.NoError(t, err) match := ComparePbkdf2HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain(), iterations, minimumSaltLen) assert.True(t, match) } func TestPbkdf2IterationsTime(t *testing.T) { if !runCostTimeTests { t.SkipNow() } start := 310000 end := start + 90000 for i := start; i <= end; i = i + 10000 { pwdResponse, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, i, minimumSaltLen, minimumSaltLen) assert.NoError(t, err) t1 := time.Now() salt, err := pwdResponse.Salt() assert.NoError(t, err) iterations, err := pwdResponse.Iterations() assert.NoError(t, err) match := ComparePbkdf2HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain(), iterations, minimumSaltLen) assert.True(t, match) t2 := time.Now() diff := t2.Sub(t1) t.Logf("Iterations: %d took %s", i, diff.String()) } } func TestSha512PwdLenBounds(t *testing.T) { // Fail lower bounds _, err := GenerateRandomSha512Password(minimumPassLen-1, minimumPassLen+4, minimumSaltLen) assertPwdError(t, err) // Fail upper bounds _, err = GenerateRandomSha512Password(minimumPassLen, maximumPassLen+1, minimumSaltLen) assertPwdError(t, err) // Fail upper and lower bounds _, err = GenerateRandomSha512Password(minimumPassLen-1, maximumPassLen+1, minimumSaltLen) assertPwdError(t, err) // Fail lower bound greater than upper bounds _, err = GenerateRandomSha512Password(maximumPassLen, minimumPassLen, minimumSaltLen) assertPwdError(t, err) // Bounds cannot be equal (password must have random length) _, err = GenerateRandomSha512Password(minimumPassLen, minimumPassLen, minimumSaltLen) assertPwdError(t, err) } func TestSha512InvalidSaltLength(t *testing.T) { _, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen-1) assertSaltError(t, err) } func TestSha512HashComparison(t *testing.T) { pwdResponse, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen) assert.NoError(t, err) salt, err := pwdResponse.Salt() assert.NoError(t, err) match := CompareSha512HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain()) assert.True(t, match) } func TestSha512Time(t *testing.T) { if !runCostTimeTests { t.SkipNow() } pwdResponse, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen) assert.NoError(t, err) t1 := time.Now() salt, err := pwdResponse.Salt() assert.NoError(t, err) match := CompareSha512HashAndPassword(pwdResponse.Hashed(), salt, pwdResponse.Plain()) assert.True(t, match) t2 := time.Now() diff := t2.Sub(t1) t.Logf("Sha512 hash took %s", diff.String()) } func TestSerializationAndDeserializationPbkdf2(t *testing.T) { pwdResponse, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen) assert.NoError(t, err) salt, err := pwdResponse.Salt() assert.NoError(t, err) iterations, err := pwdResponse.Iterations() assert.NoError(t, err) secret := NewSecret( pwdResponse.Hashed(), salt, "recovery", pwdResponse.HashFunction(), pwdResponse.HashType(), iterations, 1, false, ) serializedHashedSecretDataMap, err := secret.SerializeAndBase64Encode() assert.NoError(t, err) deserializedHashedSecretDataMap, err := NewSecretFromString(serializedHashedSecretDataMap) assert.NoError(t, err) assert.Equal(t, *secret, deserializedHashedSecretDataMap) } func TestSerializationAndDeserializationSha512(t *testing.T) { pwdResponse, err := GenerateRandomSha512Password(minimumPassLen, minimumPassLen+4, minimumSaltLen) assert.NoError(t, err) salt, err := pwdResponse.Salt() assert.NoError(t, err) secret := NewSecret( pwdResponse.Hashed(), salt, "recovery", pwdResponse.HashFunction(), pwdResponse.HashType(), 1, 1, false, ) serializedHashedSecretDataMap, err := secret.SerializeAndBase64Encode() assert.NoError(t, err) deserializedHashedSecretDataMap, err := NewSecretFromString(serializedHashedSecretDataMap) assert.NoError(t, err) assert.Equal(t, *secret, deserializedHashedSecretDataMap) } func TestActivationCodes(t *testing.T) { pwdResponse, err := GenerateRandomActivationCode() assert.NoError(t, err) match, err := regexp.MatchString("[A-Z1-9]+", pwdResponse.Plain()) assert.NoError(t, err) assert.True(t, match) assert.Equal(t, activationLength, len(pwdResponse.Plain())) resultHash := HashActivation(pwdResponse.Plain()) assert.Equal(t, resultHash, pwdResponse.Hashed()) } func TestEdgeBootstrapTokens(t *testing.T) { pwdResponse, err := GenerateRandomEdgeBootstrapToken() assert.NoError(t, err) resultHash := HashEdgeBootstrapToken([]byte(pwdResponse.Plain())) assert.Equal(t, resultHash, pwdResponse.Hashed()) } func assertPwdError(t *testing.T, err error) { pwdLengthError := validation.InvalidPasswordLengthError().Error() assert.Equal(t, err.Error(), pwdLengthError) } func assertCostError(t *testing.T, err error) { costError := validation.InvalidCostError().Error() assert.Equal(t, err.Error(), costError) } func assertSaltError(t *testing.T, err error) { saltLengthError := validation.InvalidSaltLengthError().Error() assert.Equal(t, err.Error(), saltLengthError) } func assertKeyError(t *testing.T, err error) { keyLengthError := validation.InvalidKeyLengthError().Error() assert.Equal(t, err.Error(), keyLengthError) } func assertIterationsError(t *testing.T, err error) { iterationError := validation.InvalidHashIterationsError().Error() assert.Equal(t, err.Error(), iterationError) }