Skip to content

Commit 09a9b97

Browse files
authored
Merge pull request #1173 from kjetilho/feature/pw_hash_bcrypt
pw_hash: add support for bcrypt variants
2 parents 276e7b7 + ffc6d96 commit 09a9b97

File tree

2 files changed

+47
-13
lines changed

2 files changed

+47
-13
lines changed

lib/puppet/parser/functions/pw_hash.rb

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,24 @@
1515
The first argument to this function is the password to hash. If it is
1616
undef or an empty string, this function returns undef.
1717
18-
The second argument to this function is which type of hash to use. It
18+
The second argument to this function is which hash algorithm to use. It
1919
will be converted into the appropriate crypt(3) hash specifier. Valid
2020
hash types are:
2121
22-
|Hash type |Specifier|
23-
|---------------------|---------|
24-
|MD5 |1 |
25-
|SHA-256 |5 |
26-
|SHA-512 (recommended)|6 |
22+
|Hash type|Prefix|Note |
23+
|---------|------|---------------------|
24+
|MD5 |1 | |
25+
|SHA-256 |5 | |
26+
|SHA-512 |6 |Recommended |
27+
|bcrypt |2b | |
28+
|bcrypt-a |2a |bug compatible |
29+
|bcrypt-x |2x |bug compatible |
30+
|bcrypt-y |2y |historic alias for 2b|
2731
2832
The third argument to this function is the salt to use.
2933
30-
@return [Hash]
31-
Provides a hash usable on most POSIX systems.
34+
@return [String]
35+
Provides a crypt hash usable on most POSIX systems.
3236
3337
> *Note:*: this uses the Puppet Server's implementation of crypt(3). If your
3438
environment contains several different operating systems, ensure that they
@@ -43,21 +47,31 @@
4347
arg
4448
end
4549
end
50+
51+
hashes = {
52+
'md5' => { prefix: '1' },
53+
'sha-256' => { prefix: '5' },
54+
'sha-512' => { prefix: '6' },
55+
'bcrypt' => { prefix: '2b', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} },
56+
'bcrypt-a' => { prefix: '2a', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} },
57+
'bcrypt-x' => { prefix: '2x', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} },
58+
'bcrypt-y' => { prefix: '2y', salt: %r{^[0-9]{2}\$[./A-Za-z0-9]{22}} },
59+
}
60+
4661
raise ArgumentError, 'pw_hash(): first argument must be a string' unless args[0].is_a?(String) || args[0].nil?
4762
raise ArgumentError, 'pw_hash(): second argument must be a string' unless args[1].is_a? String
48-
hashes = { 'md5' => '1',
49-
'sha-256' => '5',
50-
'sha-512' => '6' }
5163
hash_type = hashes[args[1].downcase]
5264
raise ArgumentError, "pw_hash(): #{args[1]} is not a valid hash type" if hash_type.nil?
5365
raise ArgumentError, 'pw_hash(): third argument must be a string' unless args[2].is_a? String
5466
raise ArgumentError, 'pw_hash(): third argument must not be empty' if args[2].empty?
55-
raise ArgumentError, 'pw_hash(): characters in salt must be in the set [a-zA-Z0-9./]' unless %r{\A[a-zA-Z0-9./]+\z}.match?(args[2])
67+
salt_doc = hash_type.include?(:salt) ? "match #{hash_type[:salt]}" : 'be in the set [a-zA-Z0-9./]'
68+
salt_regex = hash_type.fetch(:salt, %r{\A[a-zA-Z0-9./]+\z})
69+
raise ArgumentError, "pw_hash(): characters in salt must #{salt_doc}" unless salt_regex.match?(args[2])
5670

5771
password = args[0]
5872
return nil if password.nil? || password.empty?
5973

60-
salt = "$#{hash_type}$#{args[2]}"
74+
salt = "$#{hash_type[:prefix]}$#{args[2]}"
6175

6276
# handle weak implementations of String#crypt
6377
# dup the string to get rid of frozen status for testing

spec/functions/pw_hash_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252

5353
context 'when the third argument contains invalid characters' do
5454
it { is_expected.to run.with_params('password', 'sha-512', 'one%').and_raise_error(ArgumentError, %r{characters in salt must be in the set}) }
55+
it { is_expected.to run.with_params('password', 'bcrypt', '1234').and_raise_error(ArgumentError, %r{characters in salt must match}) }
56+
it { is_expected.to run.with_params('password', 'bcrypt-a', '1234').and_raise_error(ArgumentError, %r{characters in salt must match}) }
57+
it { is_expected.to run.with_params('password', 'bcrypt-x', '1234').and_raise_error(ArgumentError, %r{characters in salt must match}) }
58+
it { is_expected.to run.with_params('password', 'bcrypt-y', '1234').and_raise_error(ArgumentError, %r{characters in salt must match}) }
5559
end
5660

5761
context 'when running on a platform with a weak String#crypt implementation' do
@@ -60,6 +64,22 @@
6064
it { is_expected.to run.with_params('password', 'sha-512', 'salt').and_raise_error(Puppet::ParseError, %r{system does not support enhanced salts}) }
6165
end
6266

67+
begin
68+
require 'etc'
69+
if Etc.confstr(Etc::CS_GNU_LIBC_VERSION) =~ %r{(\d+\.\d+)} && Puppet::Util::Package.versioncmp(Regexp.last_match(1), '2.28') >= 0
70+
context 'when running on platform with bcrypt' do
71+
it { is_expected.to run.with_params('password', 'bcrypt', '05$salt.salt.salt.salt.sa').and_return('$2b$05$salt.salt.salt.salt.sO5QUgeeLRANZyvfNiKJW5amLo3cVD8nW') }
72+
it { is_expected.to run.with_params('password', 'bcrypt-a', '05$salt.salt.salt.salt.sa').and_return('$2a$05$salt.salt.salt.salt.sO5QUgeeLRANZyvfNiKJW5amLo3cVD8nW') }
73+
it { is_expected.to run.with_params('password', 'bcrypt-x', '05$salt.salt.salt.salt.sa').and_return('$2x$05$salt.salt.salt.salt.sO5QUgeeLRANZyvfNiKJW5amLo3cVD8nW') }
74+
it { is_expected.to run.with_params('password', 'bcrypt-y', '05$salt.salt.salt.salt.sa').and_return('$2y$05$salt.salt.salt.salt.sO5QUgeeLRANZyvfNiKJW5amLo3cVD8nW') }
75+
end
76+
else
77+
pending('Only testing bcrypt results on glibc 2.28 and later')
78+
end
79+
rescue NameError
80+
pending('Only testing bcrypt results on glibc')
81+
end
82+
6383
if RUBY_PLATFORM == 'java' || 'test'.crypt('$1$1') == '$1$1$Bp8CU9Oujr9SSEw53WV6G.'
6484
describe 'on systems with enhanced salts support' do
6585
it { is_expected.to run.with_params('password', 'md5', 'salt').and_return('$1$salt$qJH7.N4xYta3aEG/dfqo/0') }

0 commit comments

Comments
 (0)