Skip to content

Adding str2saltedpbkdf2 function #1040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ the provided regular expression.
last Period).
* [`stdlib::ip_in_range`](#stdlibip_in_range): Returns true if the ipaddress is within the given CIDRs
* [`str2bool`](#str2bool): This converts a string to a boolean.
* [`str2saltedpbkdf2`](#str2saltedpbkdf2): Convert a string into a salted SHA512 PBKDF2 password hash like requred for OS X / macOS 10.8+
* [`str2saltedsha512`](#str2saltedsha512): This converts a string to a salted-SHA512 password hash (which is used for
OS X versions >= 10.7).
* [`strftime`](#strftime): This function returns formatted time.
Expand Down Expand Up @@ -4477,6 +4478,80 @@ See the function new() in Puppet for details what the Boolean data type supports
Returns: `Any` This attempt to convert to boolean strings that contain things like: Y,y, 1, T,t, TRUE,true to 'true' and strings that contain things
like: 0, F,f, N,n, false, FALSE, no to 'false'.

### str2saltedpbkdf2

Type: Ruby 3.x API

Convert a string into a salted SHA512 PBKDF2 password hash like requred for OS X / macOS 10.8+.
Note, however, that Apple changes what's required periodically and this may not work for the latest
version of macOS. If that is the case you should get a helpful error message when Puppet tries to set
the pasword using the parameters you provide to the user resource.

#### Examples

##### Plain text password and salt

```puppet
$pw_info = str2saltedpbkdf2('Pa55w0rd', 'Using s0m3 s@lt', 50000)
user { 'jdoe':
ensure => present,
iterations => $pw_info['interations'],
password => $pw_info['password_hex'],
salt => $pw_info['salt_hex'],
}
```

##### Sensitive password and salt

```puppet
$pw = Sensitive.new('Pa55w0rd')
$salt = Sensitive.new('Using s0m3 s@lt')
$pw_info = Sensitive.new(str2saltedpbkdf2($pw, $salt, 50000))
user { 'jdoe':
ensure => present,
iterations => unwrap($pw_info)['interations'],
password => unwrap($pw_info)['password_hex'],
salt => unwrap($pw_info)['salt_hex'],
}
```

#### `str2saltedpbkdf2()`

Convert a string into a salted SHA512 PBKDF2 password hash like requred for OS X / macOS 10.8+.
Note, however, that Apple changes what's required periodically and this may not work for the latest
version of macOS. If that is the case you should get a helpful error message when Puppet tries to set
the pasword using the parameters you provide to the user resource.

Returns: `Hash` Provides a hash containing the hex version of the password, the hex version of the salt, and iterations.

##### Examples

###### Plain text password and salt

```puppet
$pw_info = str2saltedpbkdf2('Pa55w0rd', 'Using s0m3 s@lt', 50000)
user { 'jdoe':
ensure => present,
iterations => $pw_info['interations'],
password => $pw_info['password_hex'],
salt => $pw_info['salt_hex'],
}
```

###### Sensitive password and salt

```puppet
$pw = Sensitive.new('Pa55w0rd')
$salt = Sensitive.new('Using s0m3 s@lt')
$pw_info = Sensitive.new(str2saltedpbkdf2($pw, $salt, 50000))
user { 'jdoe':
ensure => present,
iterations => unwrap($pw_info)['interations'],
password => unwrap($pw_info)['password_hex'],
salt => unwrap($pw_info)['salt_hex'],
}
```

### str2saltedsha512

Type: Ruby 3.x API
Expand Down
68 changes: 68 additions & 0 deletions lib/puppet/parser/functions/str2saltedpbkdf2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# str2saltedpbkdf2.rb
# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085.
#
module Puppet::Parser::Functions
newfunction(:str2saltedpbkdf2, :type => :rvalue, :doc => <<-DOC
@summary Convert a string into a salted SHA512 PBKDF2 password hash like requred for OS X / macOS 10.8+

Convert a string into a salted SHA512 PBKDF2 password hash like requred for OS X / macOS 10.8+.
Note, however, that Apple changes what's required periodically and this may not work for the latest
version of macOS. If that is the case you should get a helpful error message when Puppet tries to set
the pasword using the parameters you provide to the user resource.

@example Plain text password and salt
$pw_info = str2saltedpbkdf2('Pa55w0rd', 'Using s0m3 s@lt', 50000)
user { 'jdoe':
ensure => present,
iterations => $pw_info['interations'],
password => $pw_info['password_hex'],
salt => $pw_info['salt_hex'],
}

@example Sensitive password and salt
$pw = Sensitive.new('Pa55w0rd')
$salt = Sensitive.new('Using s0m3 s@lt')
$pw_info = Sensitive.new(str2saltedpbkdf2($pw, $salt, 50000))
user { 'jdoe':
ensure => present,
iterations => unwrap($pw_info)['interations'],
password => unwrap($pw_info)['password_hex'],
salt => unwrap($pw_info)['salt_hex'],
}

@return [Hash]
Provides a hash containing the hex version of the password, the hex version of the salt, and iterations.
DOC
) do |args|
require 'openssl'

raise ArgumentError, "str2saltedpbkdf2(): wrong number of arguments (#{args.size} for 3)" if args.size != 3

args.map! do |arg|
if (defined? Puppet::Pops::Types::PSensitiveType::Sensitive) && (arg.is_a? Puppet::Pops::Types::PSensitiveType::Sensitive)
arg.unwrap
else
arg
end
end

raise ArgumentError, 'str2saltedpbkdf2(): first argument must be a string' unless args[0].is_a?(String)
raise ArgumentError, 'str2saltedpbkdf2(): second argument must be a string' unless args[1].is_a?(String)
raise ArgumentError, 'str2saltedpbkdf2(): second argument must be at least 8 bytes long' unless args[1].bytesize >= 8
raise ArgumentError, 'str2saltedpbkdf2(): third argument must be an integer' unless args[2].is_a?(Integer)
raise ArgumentError, 'str2saltedpbkdf2(): third argument must be between 40,000 and 70,000' unless args[2] > 40_000 && args[2] < 70_000

password = args[0]
salt = args[1]
iterations = args[2]
keylen = 128
digest = OpenSSL::Digest::SHA512.new
hash = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, keylen, digest)

{
'password_hex' => hash.unpack('H*').first,
'salt_hex' => salt.unpack('H*').first,
'iterations' => iterations,
}
end
end
23 changes: 23 additions & 0 deletions spec/functions/str2saltedpbkdf2_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'spec_helper'

describe 'str2saltedpbkdf2' do
it { is_expected.not_to eq(nil) }
it { is_expected.to run.with_params.and_raise_error(ArgumentError, %r{wrong number of arguments}i) }
it { is_expected.to run.with_params('Pa55w0rd', 2).and_raise_error(ArgumentError, %r{wrong number of arguments}i) }
it { is_expected.to run.with_params(1, 'Using s0m3 s@lt', 50_000).and_raise_error(ArgumentError, %r{first argument must be a string}) }
it { is_expected.to run.with_params('Pa55w0rd', 1, 50_000).and_raise_error(ArgumentError, %r{second argument must be a string}) }
it { is_expected.to run.with_params('Pa55w0rd', 'U', 50_000).and_raise_error(ArgumentError, %r{second argument must be at least 8 bytes long}) }
it { is_expected.to run.with_params('Pa55w0rd', 'Using s0m3 s@lt', '50000').and_raise_error(ArgumentError, %r{third argument must be an integer}) }
it { is_expected.to run.with_params('Pa55w0rd', 'Using s0m3 s@lt', 1).and_raise_error(ArgumentError, %r{third argument must be between 40,000 and 70,000}) }

context 'when running with "Pa55w0rd", "Using s0m3 s@lt",and "50000" as params' do
# rubocop:disable Metrics/LineLength
it {
is_expected.to run.with_params('Pa55w0rd', 'Using s0m3 s@lt', 50_000)
.and_return('password_hex' => '3577f79f7d2e73df1cf1eecc36da16fffcd3650126d79e797a8b227492d13de4cdd0656933b43118b7361692f755e5b3c1e0536f826d12442400f3467bcc8fb4ac2235d5648b0f1b0906d0712aecd265834319b5a42e98af2ced81597fd78d1ac916f6eff6122c3577bb120a9f534e2a5c9a58c7d1209e3914c967c6a467b594',
'salt_hex' => '5573696e672073306d332073406c74',
'iterations' => 50_000)
}
# rubocop:enable Metrics/LineLength
end
end