diff --git a/REFERENCE.md b/REFERENCE.md index f3022232..99b8f8fc 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1745,7 +1745,7 @@ Type: Ruby 4.x API The openldap_password function. -#### `openldap_password(String $secret, Optional[Enum["CRYPT","MD5","SMD5","SSHA","SHA"]] $scheme)` +#### `openldap_password(String $secret, Optional[Enum["PBKDF2","CRYPT","MD5","SMD5","SSHA","SHA"]] $scheme, Optional[Integer] $iterations, Optional[Enum["SHA256", "SHA512"]] $hash_type)` The openldap_password function. @@ -1759,10 +1759,24 @@ The secret to be hashed. ##### `scheme` -Data type: `Optional[Enum["CRYPT","MD5","SMD5","SSHA","SHA"]]` +Data type: `Optional[Enum["PBKDF2","CRYPT","MD5","SMD5","SSHA","SHA"]]` The optional scheme to use (defaults to SSHA). +##### `iterations` + +Data type: `Optional[Integer]` + +The number of iterations to use for the hashing (defaults to 60000). +Only applicable for PBKDF2. + +##### `hash_type` + +Data type: `Optional[Enum["SHA256", "SHA512"]]` + +The hash algorithm to use: 'SHA256' (32 bytes) or 'SHA512' (64 bytes). +Defaults to 'SHA512'. Only applicable for PBKDF2. + ## Data types ### `Openldap::Access_hash` diff --git a/lib/puppet/functions/openldap_password.rb b/lib/puppet/functions/openldap_password.rb index 207bf520..466c65d2 100644 --- a/lib/puppet/functions/openldap_password.rb +++ b/lib/puppet/functions/openldap_password.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'openssl' +require 'digest' require 'base64' # # @summary @@ -13,18 +15,53 @@ # @param scheme # The optional scheme to use (defaults to SSHA). # + # @param iterations + # The number of iterations to use for the hashing (defaults to 60000). + # Only applicable for PBKDF2. + # + # @param hash_type + # The hash algorithm to use: 'SHA256' (32 bytes) or 'SHA512' (64 bytes). + # Defaults to 'SHA512'. Only applicable for PBKDF2. + # # @return [String] # The hashed secret. # dispatch :generate_password do required_param 'String', :secret - optional_param 'Enum["CRYPT","MD5","SMD5","SSHA","SHA"]', :scheme + optional_param 'Enum["PBKDF2","CRYPT","MD5","SMD5","SSHA","SHA"]', :scheme + optional_param 'Integer', :iterations + optional_param 'Enum["SHA256", "SHA512"]', :hash_type return_type 'String' end - def generate_password(secret, scheme = 'SSHA') + def generate_password(secret, scheme = 'SSHA', iterations = 60_000, hash_type = 'SHA512') case scheme[%r{([A-Z,0-9]+)}, 1] + when 'PBKDF2' + salt = OpenSSL::Random.random_bytes(16) + + digest_map = { + 'SHA256' => { name: 'SHA256', length: 32, obj: OpenSSL::Digest.new('SHA256') }, + 'SHA512' => { name: 'SHA512', length: 64, obj: OpenSSL::Digest.new('SHA512') } + } + + config = digest_map[hash_type] + + derived_key = OpenSSL::PKCS5.pbkdf2_hmac( + secret, + salt, + iterations, + config[:length], + config[:obj] + ) + + value = [ + salt, + iterations.to_s, + derived_key + ].join('$') + + password = "{PBKDF2-#{config[:name]}}#{Base64.strict_encode64(value)}" when 'CRYPT' salt = call_function('fqdn_rand_string', 2) password = "{CRYPT}#{secret.crypt(salt)}"