Following Matt Bongiovi's post at the Hey, Scripting Guy! Blog about PowerShell support for certificate credentials, I ported the main parts of the c# code he references in his post to PowerShell.
So here you have, a quick-and-dirty Get-CertificateFromCredential function you can use to get the certificate for the credentials the user selected from the drop down in the Get-Credential window:
function Get-CertificateFromCredential {
param([PSCredential]$Credential)
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
public static class NativeMethods {
public enum CRED_MARSHAL_TYPE {
CertCredential = 1,
UsernameTargetCredential
}
[StructLayout(LayoutKind.Sequential)]
public struct CERT_CREDENTIAL_INFO {
public uint cbSize;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] rgbHashOfCert;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredUnmarshalCredential(
IntPtr MarshaledCredential,
out CRED_MARSHAL_TYPE CredType,
out IntPtr Credential
);
}
'@ -ReferencedAssemblies System.Runtime.InteropServices
$credData = [IntPtr]::Zero
$credInfo = [IntPtr]::Zero
$credType = [NativeMethods+CRED_MARSHAL_TYPE]::CertCredential
try {
$credData = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($Credential.UserName);
$success = [NativeMethods]::CredUnmarshalCredential($credData, [ref] $credType, [ref] $credInfo)
if ($success) {
[NativeMethods+CERT_CREDENTIAL_INFO] $certStruct = [NativeMethods+CERT_CREDENTIAL_INFO][System.Runtime.InteropServices.Marshal]::PtrToStructure(
$credInfo, [System.Type][NativeMethods+CERT_CREDENTIAL_INFO])
[byte[]] $rgbHash = $certStruct.rgbHashOfCert
[string] $hex = [BitConverter]::ToString($rgbHash) -replace '-'
$certCredential = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @(
[System.Security.Cryptography.X509Certificates.StoreName]::My,
[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser
)
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
$certsReturned = $store.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $hex, $false)
if($null -eq $certsReturned) {
throw ('Could not find a certificate with thumbprint {0}' -f $hex)
}
$certsReturned[0]
}
} catch {
throw ('An error occured: {0}' -f $_.Exception.Message)
}
finally {
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($credData)
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($credInfo)
if($null -ne $store) { $store.Close() }
}
}
Then, you can use the function:
$cred = Get-Credential -Message 'Select the SMARTCARD'
Get-CertificateFromCredential -Credential $cred
Important: Keep in mind that Get-Credential cmdlet doesn't verify the credentials anywhere, it just opens the $Host.UI.PromptForCredential popup and returns a PSCredential object. The credentials themselves are verified only when used with another cmdlet.
This means that the user can select a certificate from the dropdown in the Get-Credential window, enter an incorrect PIN and this function will still return the certificate.
I've also been following issue #3048 in the PowerShell repository on github. Hopefully, native support for certificate authentication will be added in a future version (6.1.0?)
HTH,
Martin.