...

Source file src/edge-infra.dev/pkg/lib/crypto/crypto_test.go

Documentation: edge-infra.dev/pkg/lib/crypto

     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  	// Fail lower bounds
    24  	_, err := GenerateRandomBcryptPassword(minimumPassLen-1, minimumPassLen+4, bcrypt.DefaultCost)
    25  	assertPwdError(t, err)
    26  
    27  	// Fail upper bounds
    28  	_, err = GenerateRandomBcryptPassword(minimumPassLen, maximumPassLen+1, bcrypt.DefaultCost)
    29  	assertPwdError(t, err)
    30  
    31  	// Fail upper and lower bounds
    32  	_, err = GenerateRandomBcryptPassword(minimumPassLen-1, maximumPassLen+1, bcrypt.DefaultCost)
    33  	assertPwdError(t, err)
    34  
    35  	// Fail lower bound greater than upper bounds
    36  	_, err = GenerateRandomBcryptPassword(maximumPassLen, minimumPassLen, bcrypt.DefaultCost)
    37  	assertPwdError(t, err)
    38  
    39  	// Bounds cannot be equal (password must have random length)
    40  	_, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen, bcrypt.DefaultCost)
    41  	assertPwdError(t, err)
    42  }
    43  
    44  func TestBcryptPwdCostBounds(t *testing.T) {
    45  	// Accepted default bounds
    46  	_, err := GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost)
    47  	assert.NoError(t, err)
    48  
    49  	// Fail lower bounds
    50  	_, err = GenerateRandomBcryptPassword(minimumPassLen, minimumPassLen+4, bcrypt.DefaultCost-1)
    51  	assertCostError(t, err)
    52  
    53  	// Fail upper bounds
    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  	// Fail lower bounds
    96  	_, err := GenerateRandomPbkdf2Password(minimumPassLen-1, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen)
    97  	assertPwdError(t, err)
    98  
    99  	// Fail upper bounds
   100  	_, err = GenerateRandomPbkdf2Password(minimumPassLen, maximumPassLen+1, minimumHashIterations, minimumSaltLen, minimumSaltLen)
   101  	assertPwdError(t, err)
   102  
   103  	// Fail upper and lower bounds
   104  	_, err = GenerateRandomPbkdf2Password(minimumPassLen-1, maximumPassLen+1, minimumHashIterations, minimumSaltLen, minimumSaltLen)
   105  	assertPwdError(t, err)
   106  
   107  	// Fail lower bound greater than upper bounds
   108  	_, err = GenerateRandomPbkdf2Password(maximumPassLen, minimumPassLen, minimumHashIterations, minimumSaltLen, minimumSaltLen)
   109  	assertPwdError(t, err)
   110  
   111  	// Bounds cannot be equal (password must have random length)
   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  	// Key length less than 256-bits (32 bytes)
   128  	_, err := GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, minimumSaltLen, minimumSaltLen-10)
   129  	assertKeyError(t, err)
   130  
   131  	// Key length less than salt length
   132  	_, err = GenerateRandomPbkdf2Password(minimumPassLen, minimumPassLen+4, minimumHashIterations, 64, minimumSaltLen)
   133  	assertKeyError(t, err)
   134  }
   135  
   136  func TestPbkdf2CompareHash(t *testing.T) {
   137  	// Key length less than salt length
   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  	// Fail lower bounds
   180  	_, err := GenerateRandomSha512Password(minimumPassLen-1, minimumPassLen+4, minimumSaltLen)
   181  	assertPwdError(t, err)
   182  
   183  	// Fail upper bounds
   184  	_, err = GenerateRandomSha512Password(minimumPassLen, maximumPassLen+1, minimumSaltLen)
   185  	assertPwdError(t, err)
   186  
   187  	// Fail upper and lower bounds
   188  	_, err = GenerateRandomSha512Password(minimumPassLen-1, maximumPassLen+1, minimumSaltLen)
   189  	assertPwdError(t, err)
   190  
   191  	// Fail lower bound greater than upper bounds
   192  	_, err = GenerateRandomSha512Password(maximumPassLen, minimumPassLen, minimumSaltLen)
   193  	assertPwdError(t, err)
   194  
   195  	// Bounds cannot be equal (password must have random length)
   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