diff --git a/lib/puppet/parser/functions/pw_hash.rb b/lib/puppet/parser/functions/pw_hash.rb index 67346efeb..a99a3ea29 100644 --- a/lib/puppet/parser/functions/pw_hash.rb +++ b/lib/puppet/parser/functions/pw_hash.rb @@ -25,7 +25,9 @@ |bcrypt-x |2x |bug compatible | |bcrypt-y |2y |historic alias for 2b| - The third argument to this function is the salt to use. + The third argument to this function is the salt to use. For bcrypt-type hashes, + the first two characters of the salt represent a strength parameter, with a value + between 4 and 31 inclusive. @return [String] Provides a crypt hash usable on most POSIX systems. @@ -48,10 +50,10 @@ 'md5' => { prefix: '1' }, 'sha-256' => { prefix: '5' }, 'sha-512' => { prefix: '6' }, - 'bcrypt' => { prefix: '2b', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} }, - 'bcrypt-a' => { prefix: '2a', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} }, - 'bcrypt-x' => { prefix: '2x', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} }, - 'bcrypt-y' => { prefix: '2y', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} }, + 'bcrypt' => { prefix: '2b', salt: %r{^(0[4-9]|[12][0-9]|3[01])\$[./A-Za-z0-9]{22}} }, + 'bcrypt-a' => { prefix: '2a', salt: %r{^(0[4-9]|[12][0-9]|3[01])\$[./A-Za-z0-9]{22}} }, + 'bcrypt-x' => { prefix: '2x', salt: %r{^(0[4-9]|[12][0-9]|3[01])\$[./A-Za-z0-9]{22}} }, + 'bcrypt-y' => { prefix: '2y', salt: %r{^(0[4-9]|[12][0-9]|3[01])\$[./A-Za-z0-9]{22}} }, } raise ArgumentError, 'pw_hash(): first argument must be a string' unless args[0].is_a?(String) || args[0].nil? diff --git a/spec/functions/pw_hash_spec.rb b/spec/functions/pw_hash_spec.rb index f7a827d7f..595f136eb 100644 --- a/spec/functions/pw_hash_spec.rb +++ b/spec/functions/pw_hash_spec.rb @@ -58,6 +58,17 @@ it { is_expected.to run.with_params('password', 'bcrypt-y', '1234').and_raise_error(ArgumentError, %r{characters in salt must match}) } end + context 'when the third argument has an invalid strength parameter for bcrypt' do + it { is_expected.to run.with_params('password', 'bcrypt', '03$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + it { is_expected.to run.with_params('password', 'bcrypt-a', '03$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + it { is_expected.to run.with_params('password', 'bcrypt-x', '03$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + it { is_expected.to run.with_params('password', 'bcrypt-y', '03$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + it { is_expected.to run.with_params('password', 'bcrypt', '32$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + it { is_expected.to run.with_params('password', 'bcrypt-a', '32$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + it { is_expected.to run.with_params('password', 'bcrypt-x', '32$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + it { is_expected.to run.with_params('password', 'bcrypt-y', '32$salt.salt.salt.salt.sa').and_raise_error(ArgumentError, %r{characters in salt must match}) } + end + context 'when running on a platform with a weak String#crypt implementation' do before(:each) { allow_any_instance_of(String).to receive(:crypt).with('$1$1').and_return('a bad hash') } # rubocop:disable RSpec/AnyInstance : Unable to find a viable replacement