Get the certificate selected in Get-Credential


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.

Skip to main content