Skip to content

Instantly share code, notes, and snippets.

@awakecoding
Last active January 19, 2024 16:03
Show Gist options
  • Save awakecoding/d19afb26788e7664fe3428214c2494ae to your computer and use it in GitHub Desktop.
Save awakecoding/d19afb26788e7664fe3428214c2494ae to your computer and use it in GitHub Desktop.

Revisions

  1. awakecoding revised this gist Jan 19, 2024. 1 changed file with 32 additions and 25 deletions.
    57 changes: 32 additions & 25 deletions CertCredentialMarshaledString.ps1
    Original file line number Diff line number Diff line change
    @@ -45,26 +45,26 @@ function ConvertTo-CertCredentialMarshaledString {
    )

    if ($CertHash.Length -ne 40) {
    throw "Unexpected certificate hash string length: $($CertHash.Length) (SHA1 hex string is 40 characters or 20 bytes)"
    throw "Unexpected certificate hash string length: $($CertHash.Length) (SHA1 hex string is 40 characters or 20 bytes)"
    }

    $hashBytes = [System.Linq.Enumerable]::Range(0, $CertHash.Length / 2).ForEach({
    [Byte]::Parse($CertHash.Substring($_ * 2, 2), [System.Globalization.NumberStyles]::HexNumber)
    })
    $hashBytes = [System.Linq.Enumerable]::Range(0, $CertHash.Length / 2).ForEach({
    [Byte]::Parse($CertHash.Substring($_ * 2, 2), [System.Globalization.NumberStyles]::HexNumber)
    })

    $certCredential = [WinCred.CERT_CREDENTIAL_INFO]@{
    cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf([type][WinCred.CERT_CREDENTIAL_INFO])
    rgbHashOfCert = $hashBytes
    }
    $certCredential = [WinCred.CERT_CREDENTIAL_INFO]@{
    cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf([type][WinCred.CERT_CREDENTIAL_INFO])
    rgbHashOfCert = $hashBytes
    }

    $marshaledCredentialPtr = [IntPtr]::Zero
    $result = [WinCred.pinvoke]::CredMarshalCredentialW([WinCred.CRED_MARSHAL_TYPE]::CertCredential, [ref]$certCredential, [ref]$marshaledCredentialPtr)
    $marshaledCredentialPtr = [IntPtr]::Zero
    $result = [WinCred.pinvoke]::CredMarshalCredentialW([WinCred.CRED_MARSHAL_TYPE]::CertCredential, [ref]$certCredential, [ref]$marshaledCredentialPtr)

    if ($result) {
    $marshaledCredential = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($marshaledCredentialPtr)
    [System.Runtime.InteropServices.Marshal]::FreeCoTaskMem($marshaledCredentialPtr)
    return $marshaledCredential
    }
    if ($result) {
    $marshaledCredential = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($marshaledCredentialPtr)
    [System.Runtime.InteropServices.Marshal]::FreeCoTaskMem($marshaledCredentialPtr)
    return $marshaledCredential
    }
    }

    function ConvertFrom-CertCredentialMarshaledString {
    @@ -74,19 +74,19 @@ function ConvertFrom-CertCredentialMarshaledString {
    )

    if (-Not ($MarshaledCredential.StartsWith('@@') -and $MarshaledCredential.Length -eq 30)) {
    throw "Unexpected certificate credential marshaled string format: 30 characters, starting with '@@'"
    throw "Unexpected certificate credential marshaled string format: 30 characters, starting with '@@'"
    }

    $credType = [WinCred.CRED_MARSHAL_TYPE]::CertCredential
    $unmarshaledCredentialPtrForUnmarshalling = [IntPtr]::Zero
    $unmarshalResult = [WinCred.pinvoke]::CredUnmarshalCredentialW($MarshaledCredential, [ref]$credType, [ref]$unmarshaledCredentialPtrForUnmarshalling)
    $credType = [WinCred.CRED_MARSHAL_TYPE]::CertCredential
    $unmarshaledCredentialPtrForUnmarshalling = [IntPtr]::Zero
    $unmarshalResult = [WinCred.pinvoke]::CredUnmarshalCredentialW($MarshaledCredential, [ref]$credType, [ref]$unmarshaledCredentialPtrForUnmarshalling)

    if ($unmarshalResult -and $credType -eq [WinCred.CRED_MARSHAL_TYPE]::CertCredential) {
    $unmarshaledCredential = [System.Runtime.InteropServices.Marshal]::PtrToStructure($unmarshaledCredentialPtrForUnmarshalling, [type][WinCred.CERT_CREDENTIAL_INFO])
    $hashString = -join ($unmarshaledCredential.rgbHashOfCert | ForEach-Object { $_.ToString("X2") })
    [System.Runtime.InteropServices.Marshal]::FreeCoTaskMem($unmarshaledCredentialPtrForUnmarshalling)
    return $hashString
    }
    if ($unmarshalResult -and $credType -eq [WinCred.CRED_MARSHAL_TYPE]::CertCredential) {
    $unmarshaledCredential = [System.Runtime.InteropServices.Marshal]::PtrToStructure($unmarshaledCredentialPtrForUnmarshalling, [type][WinCred.CERT_CREDENTIAL_INFO])
    $hashString = -join ($unmarshaledCredential.rgbHashOfCert | ForEach-Object { $_.ToString("X2") })
    [System.Runtime.InteropServices.Marshal]::FreeCoTaskMem($unmarshaledCredentialPtrForUnmarshalling)
    return $hashString
    }
    }

    #
    @@ -112,4 +112,11 @@ function ConvertFrom-CertCredentialMarshaledString {
    # username:s:@@BSxT#CyQZRSTgS#0d1kbebRaZeiJ
    #
    # Alternatively, you can literally paste the "@@"-prefixed string in the mstsc username field before connecting.
    #
    # Since the "@@"-prefixed string corresponds to a certificate thumbprint in the current user certificate store,
    # you can use it to find and export the corresponding certificate for closer inspection:
    #
    # $Thumbprint = ConvertFrom-CertCredentialMarshaledString "@@BSxT#CyQZRSTgS#0d1kbebRaZeiJ"
    # $Certificate = Get-Item "cert:\CurrentUser\My\$Thumbprint"
    # Export-Certificate -Cert $Certificate -FilePath "${Thumbprint}.crt"
    #
  2. awakecoding created this gist Aug 8, 2023.
    115 changes: 115 additions & 0 deletions CertCredentialMarshaledString.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    Add-Type -TypeDefinition @"
    using System;
    using System.Runtime.InteropServices;
    namespace WinCred
    {
    public enum CRED_MARSHAL_TYPE
    {
    CertCredential = 1,
    UsernameTargetCredential,
    BinaryBlobCredential,
    UsernameForPackedCredentials,
    BinaryBlobForSystem
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct CERT_CREDENTIAL_INFO
    {
    public uint cbSize;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    public byte[] rgbHashOfCert;
    }
    public static class pinvoke
    {
    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool CredMarshalCredentialW(
    CRED_MARSHAL_TYPE CredType,
    ref CERT_CREDENTIAL_INFO Credential,
    out IntPtr MarshaledCredential);
    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool CredUnmarshalCredentialW(
    string MarshaledCredential,
    out CRED_MARSHAL_TYPE CredType,
    out IntPtr Credential);
    }
    }
    "@ -Language CSharp

    function ConvertTo-CertCredentialMarshaledString {
    param(
    [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
    [string] $CertHash
    )

    if ($CertHash.Length -ne 40) {
    throw "Unexpected certificate hash string length: $($CertHash.Length) (SHA1 hex string is 40 characters or 20 bytes)"
    }

    $hashBytes = [System.Linq.Enumerable]::Range(0, $CertHash.Length / 2).ForEach({
    [Byte]::Parse($CertHash.Substring($_ * 2, 2), [System.Globalization.NumberStyles]::HexNumber)
    })

    $certCredential = [WinCred.CERT_CREDENTIAL_INFO]@{
    cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf([type][WinCred.CERT_CREDENTIAL_INFO])
    rgbHashOfCert = $hashBytes
    }

    $marshaledCredentialPtr = [IntPtr]::Zero
    $result = [WinCred.pinvoke]::CredMarshalCredentialW([WinCred.CRED_MARSHAL_TYPE]::CertCredential, [ref]$certCredential, [ref]$marshaledCredentialPtr)

    if ($result) {
    $marshaledCredential = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($marshaledCredentialPtr)
    [System.Runtime.InteropServices.Marshal]::FreeCoTaskMem($marshaledCredentialPtr)
    return $marshaledCredential
    }
    }

    function ConvertFrom-CertCredentialMarshaledString {
    param(
    [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
    [string] $MarshaledCredential
    )

    if (-Not ($MarshaledCredential.StartsWith('@@') -and $MarshaledCredential.Length -eq 30)) {
    throw "Unexpected certificate credential marshaled string format: 30 characters, starting with '@@'"
    }

    $credType = [WinCred.CRED_MARSHAL_TYPE]::CertCredential
    $unmarshaledCredentialPtrForUnmarshalling = [IntPtr]::Zero
    $unmarshalResult = [WinCred.pinvoke]::CredUnmarshalCredentialW($MarshaledCredential, [ref]$credType, [ref]$unmarshaledCredentialPtrForUnmarshalling)

    if ($unmarshalResult -and $credType -eq [WinCred.CRED_MARSHAL_TYPE]::CertCredential) {
    $unmarshaledCredential = [System.Runtime.InteropServices.Marshal]::PtrToStructure($unmarshaledCredentialPtrForUnmarshalling, [type][WinCred.CERT_CREDENTIAL_INFO])
    $hashString = -join ($unmarshaledCredential.rgbHashOfCert | ForEach-Object { $_.ToString("X2") })
    [System.Runtime.InteropServices.Marshal]::FreeCoTaskMem($unmarshaledCredentialPtrForUnmarshalling)
    return $hashString
    }
    }

    #
    # Find all smartcard client authentication certificates in the current user store and convert the
    # thumbprint (SHA1) to a marshaled "username" credential string used in RDP for smartcard selection:
    #
    # Get-ChildItem -Path cert:\CurrentUser\My | Where-Object {
    # $_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2" -and # Client Authentication OID
    # $_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.4.1.311.20.2.2" # Smart Card Logon OID
    # } | Select-Object -Property Thumbprint,Subject,
    # @{l='CertCredentialMarshaledString';e={ConvertTo-CertCredentialMarshaledString($_.Thumbprint)}}
    #
    # Thumbprint Subject CertCredentialMarshaledString
    # ---------- ------- -----------------------------
    # 523CF9820C65913481924F7735B9795BA4659E98 CN=Administrator, CN=Users, DC=ad, DC=it-help, DC=ninja @@BSxT#CyQZRSTgS#0d1kbebRaZeiJ
    #
    # Sample values with the thumbprint and marshalled credential string equivalents:
    #
    # ConvertTo-CertCredentialMarshaledString "523CF9820C65913481924F7735B9795BA4659E98"
    # ConvertFrom-CertCredentialMarshaledString "@@BSxT#CyQZRSTgS#0d1kbebRaZeiJ"
    #
    # Use the "@@"-prefixed string in the .RDP file as the username like this to pre-select a smartcard certificate:
    # username:s:@@BSxT#CyQZRSTgS#0d1kbebRaZeiJ
    #
    # Alternatively, you can literally paste the "@@"-prefixed string in the mstsc username field before connecting.
    #