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.
Handle RDP smartcard automatic selection through special '@@'-prefixed usernames containing SHA1 certificate hash
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