summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorJohn McCrae <mccrae@progress.com>2023-02-05 08:20:57 +0600
committerJohn <john.mccrae@progress.com>2023-02-28 18:27:19 -0800
commit9f8c667ae2b0ccdd9fa0a77a7d215aaf2df98c5f (patch)
tree2b923e5db2ba206ead8717eb6be03795b6fa7691 /lib
parent625558f029d1503d03e7cc42ab9c69d210fa7fa3 (diff)
downloadchef-9f8c667ae2b0ccdd9fa0a77a7d215aaf2df98c5f.tar.gz
Correcting cert retrieval issues for multiple user scenarios
Signed-off-by: John McCrae <mccrae@progress.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/client.rb29
-rw-r--r--lib/chef/http/authenticator.rb151
2 files changed, 140 insertions, 40 deletions
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index e5fcd56eb0..743b5ec473 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -66,6 +66,15 @@ class Chef
class Client
CRYPT_EXPORTABLE = 0x00000001
+ # adding these
+ # certstore 65536 == 0x00010000 == CurrentUser
+ # certstore 131072 == 0x00020000 == LocalMachine
+ # Reference: https://github.com/chef/win32-certstore/blob/main/lib/win32/certstore/mixin/crypto.rb#L90
+ CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x00020000
+ CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000
+ CERT_SYSTEM_STORE_SERVICES = 0x00050000
+ CERT_SYSTEM_STORE_USERS = 0x00060000
+
attr_reader :local_context
extend Chef::Mixin::Deprecation
@@ -674,9 +683,15 @@ class Chef
# In the brave new world of No Certs On Disk, we want to put the pem file into Keychain or the Certstore
# But is it already there?
+ # We're solving the multi-user scenario where both a system/admin user can run on the box but also someone without
+ # admin rights can also run correctly locally.
def check_certstore_for_key(cert_name)
require "win32-certstore"
- win32certstore = ::Win32::Certstore.open("MY")
+ if Chef::Config[:auth_key_registry_type] == "user"
+ win32certstore = ::Win32::Certstore.open("MY", store_location: CERT_SYSTEM_STORE_CURRENT_USER)
+ else
+ win32certstore = ::Win32::Certstore.open("MY")
+ end
win32certstore.search("#{cert_name}")
end
@@ -783,8 +798,6 @@ class Chef
require "time" unless defined?(Time)
autoload :URI, "uri"
- # KeyMigration.instance.key_migrated = true
-
node = Chef::Config[:node_name]
d = Time.now
if d.month == 10 || d.month == 11 || d.month == 12
@@ -818,9 +831,13 @@ class Chef
require "win32-certstore"
tempfile = Tempfile.new("#{Chef::Config[:node_name]}.pfx")
File.open(tempfile, "wb") { |f| f.print new_pfx.to_der }
-
- store = ::Win32::Certstore.open("MY")
- store.add_pfx(tempfile, password, CRYPT_EXPORTABLE)
+ # Need to determine where to store the key
+ if Chef::Config[:auth_key_registry_type] == "user"
+ win32certstore = ::Win32::Certstore.open("MY", store_location: CERT_SYSTEM_STORE_CURRENT_USER)
+ else
+ win32certstore = ::Win32::Certstore.open("MY")
+ end
+ win32certstore.add_pfx(tempfile, password, CRYPT_EXPORTABLE)
tempfile.unlink
end
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index c795297397..10d85db4e6 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -102,12 +102,12 @@ class Chef
self.class.get_cert_password
end
- def encrypt_pfx_pass
- self.class.encrypt_pfx_pass
+ def encrypt_pfx_pass_with_vector
+ self.class.encrypt_pfx_pass_with_vector
end
- def decrypt_pfx_pass
- self.class.decrypt_pfx_pass
+ def decrypt_pfx_pass_with_vector
+ self.class.decrypt_pfx_pass_with_vector
end
# Detects if a private key exists in a certificate repository like Keychain (macOS) or Certificate Store (Windows)
@@ -123,9 +123,18 @@ class Chef
end
end
+ def self.get_cert_user
+ Chef::Config[:auth_key_registry_type] == "user" ? store = "CurrentUser" : store = "LocalMachine"
+ end
+
+ def self.get_registry_user
+ Chef::Config[:auth_key_registry_type] == "user" ? store = "HKEY_CURRENT_USER" : store = "HKEY_LOCAL_MACHINE"
+ end
+
def self.check_certstore_for_key(client_name)
+ store = get_cert_user
powershell_code = <<~CODE
- $cert = Get-ChildItem -path cert:\\LocalMachine\\My -Recurse -Force | Where-Object { $_.Subject -Match "chef-#{client_name}" } -ErrorAction Stop
+ $cert = Get-ChildItem -path cert:\\#{store}\\My -Recurse -Force | Where-Object { $_.Subject -Match "chef-#{client_name}" } -ErrorAction Stop
if (($cert.HasPrivateKey -eq $true) -and ($cert.PrivateKey.Key.ExportPolicy -ne "NonExportable")) {
return $true
}
@@ -164,49 +173,86 @@ class Chef
end
def self.get_cert_password
+ store = get_registry_user
@win32registry = Chef::Win32::Registry.new
- path = "HKEY_LOCAL_MACHINE\\Software\\Progress\\Authentication"
+ path = "#{store}\\Software\\Progress\\Authentication"
# does the registry key even exist?
- present = @win32registry.get_values(path)
- if present.nil? || present.empty?
+ # password_blob should be an array of hashes
+ password_blob = @win32registry.get_values(path)
+ if password_blob.nil? || password_blob.empty?
raise Chef::Exceptions::Win32RegKeyMissing
end
- present.each do |secret|
- if secret[:name] == "PfxPass"
- password = decrypt_pfx_pass(secret[:data])
- return password
+ # Did someone have just the password stored in the registry?
+ raw_data = password_blob.map { |x| x[:data] }
+ vector = raw_data[2]
+ if !!vector
+ decrypted_password = decrypt_pfx_pass_with_vector(password_blob)
+ else
+ decrypted_password = decrypt_pfx_pass_with_password(password_blob)
+ if !!decrypted_password
+ migrate_pass_to_use_vector(decrypted_password)
+ else
+ Chef::Log.error("Failed to retrieve certificate password")
end
end
-
- raise Chef::Exceptions::Win32RegKeyMissing
-
+ decrypted_password
rescue Chef::Exceptions::Win32RegKeyMissing
# if we don't have a password, log that and generate one
- Chef::Log.warn "Authentication Hive and values not present in registry, creating them now"
- new_path = "HKEY_LOCAL_MACHINE\\Software\\Progress\\Authentication"
+ store = get_registry_user
+ new_path = "#{store}\\Software\\Progress\\Authentication"
unless @win32registry.key_exists?(new_path)
@win32registry.create_key(new_path, true)
end
- require "securerandom" unless defined?(SecureRandom)
- size = 14
- password = SecureRandom.alphanumeric(size)
- encrypted_pass = encrypt_pfx_pass(password)
- values = { name: "PfxPass", type: :string, data: encrypted_pass }
- @win32registry.set_value(new_path, values)
- password
+ create_and_store_new_password
end
- def self.encrypt_pfx_pass(password)
+ def self.encrypt_pfx_pass_with_vector(password)
powershell_code = <<~CODE
- $encrypted_string = ConvertTo-SecureString "#{password}" -AsPlainText -Force
- $secure_string = ConvertFrom-SecureString $encrypted_string
- return $secure_string
+ $AES = [System.Security.Cryptography.Aes]::Create()
+ $key_temp = [System.Convert]::ToBase64String($AES.Key)
+ $iv_temp = [System.Convert]::ToBase64String($AES.IV)
+ $encryptor = $AES.CreateEncryptor()
+ [System.Byte[]]$Bytes = [System.Text.Encoding]::Unicode.GetBytes("#{password}")
+ $EncryptedBytes = $encryptor.TransformFinalBlock($Bytes,0,$Bytes.Length)
+ $EncryptedBase64String = [System.Convert]::ToBase64String($EncryptedBytes)
+ # create array of encrypted pass, key, iv
+ $password_blob = @($EncryptedBase64String, $key_temp, $iv_temp)
+ return $password_blob
CODE
powershell_exec!(powershell_code).result
end
- def self.decrypt_pfx_pass(password)
+ def self.decrypt_pfx_pass_with_vector(password_blob)
+ raw_data = password_blob.map { |x| x[:data] }
+ password = raw_data[0]
+ key = raw_data[1]
+ vector = raw_data[2]
+
+ powershell_code = <<~CODE
+ $KeyBytes = [System.Convert]::FromBase64String("#{key}")
+ $IVBytes = [System.Convert]::FromBase64String("#{vector}")
+ $aes = [System.Security.Cryptography.Aes]::Create()
+ $aes.Key = $KeyBytes
+ $aes.IV = $IVBytes
+ $EncryptedBytes = [System.Convert]::FromBase64String("#{password}")
+ $Decryptor = $aes.CreateDecryptor()
+ $DecryptedBytes = $Decryptor.TransformFinalBlock($EncryptedBytes,0,$EncryptedBytes.Length)
+ $DecryptedString = [System.Text.Encoding]::Unicode.GetString($DecryptedBytes)
+ return $DecryptedString
+ CODE
+ results = powershell_exec!(powershell_code).result
+ end
+
+ def self.decrypt_pfx_pass_with_password(password_blob)
+ password = ""
+ password_blob.each do |secret|
+ if secret[:name] == "PfxPass"
+ password = secret[:data]
+ else
+ Chef::Log.error("Failed to retrieve a password for the private key")
+ end
+ end
powershell_code = <<~CODE
$secure_string = "#{password}" | ConvertTo-SecureString
$string = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((($secure_string))))
@@ -215,14 +261,49 @@ class Chef
powershell_exec!(powershell_code).result
end
- def self.retrieve_certificate_key(client_name)
- require "openssl" unless defined?(OpenSSL)
+ def self.migrate_pass_to_use_vector(password)
+ store = get_cert_user
+ corrected_store = (store == "CurrentUser" ? "HKCU" : "HKLM")
+ powershell_code = <<~CODE
+ Remove-ItemProperty -Path "#{corrected_store}:\\Software\\Progress\\Authentication" -Name "PfXPass"
+ CODE
+ powershell_exec!(powershell_code)
+ create_and_store_new_password(password)
+ end
+ # This method name is a bit of a misnomer. We call it to legit create a new password using the vector format.
+ # But we also call it with a password that needs to be migrated to use the vector format too.
+ def self.create_and_store_new_password(password = nil)
+ @win32registry = Chef::Win32::Registry.new
+ store = get_registry_user
+ path = "#{store}\\Software\\Progress\\Authentication"
+ if password.nil?
+ require "securerandom" unless defined?(SecureRandom)
+ size = 14
+ password = SecureRandom.alphanumeric(size)
+ end
+ encrypted_blob = encrypt_pfx_pass_with_vector(password)
+ encrypted_password = encrypted_blob[0]
+ key = encrypted_blob[1]
+ vector = encrypted_blob[2]
+ values = [
+ { name: "PfxPass", type: :string, data: encrypted_password },
+ { name: "PfxKey", type: :string, data: key },
+ { name: "PfxIV", type: :string, data: vector },
+ ]
+ values.each do |i|
+ @win32registry.set_value(path, i)
+ end
+ password
+ end
+
+ def self.retrieve_certificate_key(client_name)
if ChefUtils.windows?
+ require "openssl" unless defined?(OpenSSL)
password = get_cert_password
return false unless password
- if check_certstore_for_key(client_name)
+ if !!check_certstore_for_key(client_name)
ps_blob = powershell_exec!(get_the_key_ps(client_name, password)).result
file_path = ps_blob["PSPath"].split("::")[1]
pkcs = OpenSSL::PKCS12.new(File.binread(file_path), password)
@@ -252,10 +333,11 @@ class Chef
end
def self.get_the_key_ps(client_name, password)
+ store = get_cert_user
powershell_code = <<~CODE
Try {
$my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText;
- $cert = Get-ChildItem -path cert:\\LocalMachine\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } -ErrorAction Stop;
+ $cert = Get-ChildItem -path cert:\\#{store}\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } -ErrorAction Stop;
$tempfile = [System.IO.Path]::GetTempPath() + "export_pfx.pfx";
Export-PfxCertificate -Cert $cert -Password $my_pwd -FilePath $tempfile;
}
@@ -266,8 +348,9 @@ class Chef
end
def self.delete_old_key_ps(client_name)
+ store = get_cert_user
powershell_code = <<~CODE
- Get-ChildItem -path cert:\\LocalMachine\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } | Remove-Item -ErrorAction Stop;
+ Get-ChildItem -path cert:\\#{store}\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } | Remove-Item -ErrorAction Stop;
CODE
end