Last active
January 19, 2024 16:03
-
-
Save awakecoding/d19afb26788e7664fe3428214c2494ae to your computer and use it in GitHub Desktop.
Handle RDP smartcard automatic selection through special '@@'-prefixed usernames containing SHA1 certificate hash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | |
# | |
# 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" | |
# |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment