From 8f42235f255812660a14aaf653a5c503232370f8 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 29 Jan 2025 15:47:02 -0500 Subject: [PATCH 1/2] Update Azure.ps1 | Support for Linux CertThumbprints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Believe it or not, there is a way to add certs to Linux systems using PowerShell. However, this method does not present the PS-drive provider Microsoft.PowerShell.Security\Certificate. So the current Azure plugin code checking if the cert is there using cert:\ doesn’t work. I left existing code and just added a "well you didn’t find it, try these two new methods" sort of code. Technically I am pretty sure my method would work in Windows but I don’t have the time to test (I use a Mac anyways). Anyone is welcome to take what I did and make it better. Anyone wanting to import a cert in Linux can use this example code: #import the PFX to your machines cert store $StoreName = [System.Security.Cryptography.X509Certificates.StoreName]::My $StoreLocation = [System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser $Store = [System.Security.Cryptography.X509Certificates.X509Store]::new($StoreName, $StoreLocation) $Flag = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable # Prompt for the password $password = Read-Host -Prompt "Enter the password for the PFX file" -AsSecureString $Certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new("./something.pfx", $password, $Flag) $Store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $Store.Add($Certificate) $Store.Close() write-host "Certificate installed: " $Certificate.thumbprint --- Posh-ACME/Plugins/Azure.ps1 | 47 +++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/Posh-ACME/Plugins/Azure.ps1 b/Posh-ACME/Plugins/Azure.ps1 index a66c2ce1..908ada79 100644 --- a/Posh-ACME/Plugins/Azure.ps1 +++ b/Posh-ACME/Plugins/Azure.ps1 @@ -547,13 +547,52 @@ function Connect-AZTenant { if ('CertThumbprint' -eq $PSCmdlet.ParameterSetName) { Write-Debug "Looking for cert thumbprint $AZCertThumbprint" # Look up the cert based on the thumbprint + $cert = $null + # check CurrentUser first - if (-not ($cert = Get-Item "Cert:\CurrentUser\My\$AZCertThumbprint" -EA Ignore)) { + $cert = Get-Item "Cert:\CurrentUser\My\$AZCertThumbprint" -EA Ignore + + if (-not $cert) { # check LocalMachine - if (-not ($cert = Get-Item "Cert:\LocalMachine\My\$AZCertThumbprint" -EA Ignore)) { - throw "Certificate with thumbprint $AZCertThumbprint not found in CurrentUser or LocalMachine stores." - } + $cert = Get-Item "Cert:\LocalMachine\My\$AZCertThumbprint" -EA Ignore + } + + ### + # This is the new method I added in to support linux systems + # its entirely possible this method would work for Windows and if so may be better as its not dependant on the cert: PS-Drive provider + # I dont know enough to say for sure though so I just add this below, only triggers if $cert wasnt found already by previous methods + + $CurrentErrorAction = $ErrorActionPreference + + if (-not $cert) { + # Setting Error Action to Ignore to prevent the error message from being displayed will set back after block + $ErrorActionPreference = "Ignore" + } + + if (-not $cert) { + # check CurrentUser .NET Style + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) + $cert = $store.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $AZCertThumbprint, $false) + $store.Close() + } + + if (-not $cert) { + # check LocalMachine .NET Style + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) + $cert = $store.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $AZCertThumbprint, $false) + $store.Close() + } + + if (-not $cert) { + throw "Certificate with thumbprint $AZCertThumbprint not found in CurrentUser or LocalMachine stores." + } + + if ($ErrorActionPreference -ne $CurrentErrorAction) { + $ErrorActionPreference = $CurrentErrorAction } + } else { Write-Debug "Looking for cert pfx $AZCertPfx" $AZCertPfx = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($AZCertPfx) From 7bb4ccdd55b2add2a11e5fcb9873241c8ec87259 Mon Sep 17 00:00:00 2001 From: Ryan Bolger Date: Fri, 7 Feb 2025 00:12:42 -0800 Subject: [PATCH 2/2] replaced the old cert:\ based thumbprint code with X509Store based code so it can work on Linux --- Posh-ACME/Plugins/Azure.ps1 | 61 +++++++++++++++---------------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/Posh-ACME/Plugins/Azure.ps1 b/Posh-ACME/Plugins/Azure.ps1 index 908ada79..3cf4abd7 100644 --- a/Posh-ACME/Plugins/Azure.ps1 +++ b/Posh-ACME/Plugins/Azure.ps1 @@ -545,54 +545,42 @@ function Connect-AZTenant { } elseif ($PSCmdlet.ParameterSetName -in 'CertThumbprint','CertFile','DeprecatedCertFile') { if ('CertThumbprint' -eq $PSCmdlet.ParameterSetName) { - Write-Debug "Looking for cert thumbprint $AZCertThumbprint" # Look up the cert based on the thumbprint $cert = $null - - # check CurrentUser first - $cert = Get-Item "Cert:\CurrentUser\My\$AZCertThumbprint" -EA Ignore - - if (-not $cert) { - # check LocalMachine - $cert = Get-Item "Cert:\LocalMachine\My\$AZCertThumbprint" -EA Ignore - } - - ### - # This is the new method I added in to support linux systems - # its entirely possible this method would work for Windows and if so may be better as its not dependant on the cert: PS-Drive provider - # I dont know enough to say for sure though so I just add this below, only triggers if $cert wasnt found already by previous methods - - $CurrentErrorAction = $ErrorActionPreference - - if (-not $cert) { - # Setting Error Action to Ignore to prevent the error message from being displayed will set back after block - $ErrorActionPreference = "Ignore" - } - if (-not $cert) { - # check CurrentUser .NET Style - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) - $cert = $store.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $AZCertThumbprint, $false) - $store.Close() + # check CurrentUser first using .NET instead of Cert:\ since the latter + # doesn't work on Linux + Write-Debug "Checking for cert thumbprint $AZCertThumbprint in CurrentUser store." + $cuStore = [Security.Cryptography.X509Certificates.X509Store]::new('My', 'CurrentUser') + try { + $cuStore.Open('ReadOnly') + $cert = $cuStore.Certificates.Find('FindByThumbprint', $AZCertThumbprint, $false) + } catch { + $PSCmdlet.WriteWarning($_) + } finally { + $cuStore.Close() } + # check LocalMachine next. This works on Windows, and maybe on MacOS, + # but not on Linux as of PS 7.5. + # MethodInvocationException: Exception calling "Open" with "1" argument(s): "Unix LocalMachine X509Store is limited to the Root and CertificateAuthority stores." if (-not $cert) { - # check LocalMachine .NET Style - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) - $cert = $store.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $AZCertThumbprint, $false) - $store.Close() + Write-Debug "Checking for cert thumbprint $AZCertThumbprint in LocalMachine store." + $lmStore = [Security.Cryptography.X509Certificates.X509Store]::new('My', 'LocalMachine') + try { + $lmStore.Open('ReadOnly') + $cert = $lmStore.Certificates.Find('FindByThumbprint', $AZCertThumbprint, $false) + } catch { + $PSCmdlet.WriteWarning($_) + } finally { + $lmStore.Close() + } } if (-not $cert) { throw "Certificate with thumbprint $AZCertThumbprint not found in CurrentUser or LocalMachine stores." } - if ($ErrorActionPreference -ne $CurrentErrorAction) { - $ErrorActionPreference = $CurrentErrorAction - } - } else { Write-Debug "Looking for cert pfx $AZCertPfx" $AZCertPfx = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($AZCertPfx) @@ -652,6 +640,7 @@ function Connect-AZTenant { # which for some reason doesn't allow reading of the KeySize attribute # which then breaks New-Jws's internal validation checks. So we need # to convert it to an RSACryptoServiceProvider object instead. + Write-Debug "Converting privatekey to RSACryptoServiceProvider" $keyParams = $privKey.ExportParameters($true) $privKey = [Security.Cryptography.RSACryptoServiceProvider]::new() $privKey.ImportParameters($keyParams)