diff --git a/algorithm/bcrypt/const.go b/algorithm/bcrypt/const.go index 50dea1e..d55264e 100644 --- a/algorithm/bcrypt/const.go +++ b/algorithm/bcrypt/const.go @@ -55,6 +55,8 @@ const ( ) const ( + versionSHA256 = "2" + oV = "v" oT = "t" oR = "r" diff --git a/algorithm/bcrypt/decoder.go b/algorithm/bcrypt/decoder.go index bc3ff1a..07ebf81 100644 --- a/algorithm/bcrypt/decoder.go +++ b/algorithm/bcrypt/decoder.go @@ -170,7 +170,10 @@ func decode(variant Variant, parts []string) (digest algorithm.Digest, err error return nil, fmt.Errorf("%w: key is expected to be %d bytes but it has %d bytes", algorithm.ErrEncodedHashKeyEncoding, bcrypt.EncodedHashSize, n) } - var params []encoding.Parameter + var ( + params []encoding.Parameter + version bool + ) if params, err = encoding.DecodeParameterStr(parts[0]); err != nil { return nil, err @@ -179,7 +182,11 @@ func decode(variant Variant, parts []string) (digest algorithm.Digest, err error for _, param := range params { switch param.Key { case oV: - break + if param.Value != versionSHA256 { + return nil, fmt.Errorf("%w: version %s is supported but encoded hash is version %s", algorithm.ErrEncodedHashInvalidVersion, versionSHA256, param.Value) + } + + version = true case oT: decoded.version = param.Value case oR: @@ -193,6 +200,10 @@ func decode(variant Variant, parts []string) (digest algorithm.Digest, err error } } + if !version { + return nil, fmt.Errorf("%w: version %s is supported but encoded hash has no version", algorithm.ErrEncodedHashInvalidVersion, versionSHA256) + } + if err = validateCost(decoded.iterations); err != nil { return nil, err } diff --git a/algorithm/bcrypt/regression_test.go b/algorithm/bcrypt/regression_test.go index e345b84..06221bc 100644 --- a/algorithm/bcrypt/regression_test.go +++ b/algorithm/bcrypt/regression_test.go @@ -218,3 +218,36 @@ func TestSHA256VariantVersion2xMatches(t *testing.T) { assert.NoError(t, err) assert.False(t, match) } + +func TestDecodeSHA256VariantRequiresVersion2(t *testing.T) { + testCases := []struct { + name string + digest string + err string + }{ + {"Version1", "$bcrypt-sha256$v=1,t=2b,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", "bcrypt decode error: provided encoded hash has an invalid version: version 2 is supported but encoded hash is version 1"}, + {"Version3", "$bcrypt-sha256$v=3,t=2b,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", "bcrypt decode error: provided encoded hash has an invalid version: version 2 is supported but encoded hash is version 3"}, + {"VersionEmpty", "$bcrypt-sha256$v=,t=2b,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", "bcrypt decode error: provided encoded hash has an invalid version: version 2 is supported but encoded hash is version "}, + {"VersionMissing", "$bcrypt-sha256$t=2b,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC", "bcrypt decode error: provided encoded hash has an invalid version: version 2 is supported but encoded hash has no version"}, + {"PasslibVersion1Format", "$bcrypt-sha256$2a,12$LrmaIX5x4TRtAwEfwJZa1.$2ehnw6LvuIUTM0iz4iz9hTxv21B6KFO", "bcrypt decode error: parameter pair '2a' is not properly encoded: does not contain kv separator '='"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + digest, err := Decode(tc.digest) + + assert.Nil(t, digest) + assert.EqualError(t, err, tc.err) + }) + } +} + +func TestDecodeSHA256VariantAcceptsVersion2(t *testing.T) { + const encoded = "$bcrypt-sha256$v=2,t=2b,r=10$oYmTNJVOBi3hdhUYy4JqOe$jCuMDm.Pw9hhoF/FDC6sOi48yBAoWvC" + + digest, err := Decode(encoded) + require.NoError(t, err) + + assert.Equal(t, encoded, digest.Encode()) + assert.True(t, digest.Match("password")) +}