|
| 1 | +--- |
| 2 | +title: Scripts to View the Certificate Information in the msDS-KeyCredentialLink Attribute |
| 3 | +description: This article introduces a script to view the certificate information in the msDS-KeyCredentialLink attribute from AD user objects. |
| 4 | +ms.date: 02/13/2025 |
| 5 | +manager: dcscontentpm |
| 6 | +audience: itpro |
| 7 | +ms.topic: troubleshooting |
| 8 | +ms.reviewer: takondo |
| 9 | +ms.custom: sap:User Logon and Profiles\User profiles, csstroubleshoot |
| 10 | +--- |
| 11 | +# Scripts: View the certificate information in the msDS-KeyCredentialLink attribute from AD user objects |
| 12 | + |
| 13 | +The **msDS-KeyCredentialLink** attribute can be viewed in PowerShell. However, the value is in binary format and can't be read. This script helps you do the following things: |
| 14 | + |
| 15 | +- Enumerate all users in Active Directory (AD) that have a non-null value in **msDS-KeyCredentialLink**. |
| 16 | +- Extract the bcrypt-sha256 key ID hash of each certificate saved in **msDS-KeyCredentialLink** and save it to a file. |
| 17 | + |
| 18 | +You can then use the saved information to check whether the expected values are in the user's **msDS-KeyCredentialLink** attribute. |
| 19 | + |
| 20 | +## Script |
| 21 | + |
| 22 | +[!INCLUDE [Script disclaimer](../../includes/script-disclaimer.md)] |
| 23 | + |
| 24 | +After you run the script, the results are saved in the file **C:\temp\KeyCredentialLink-report.txt**. |
| 25 | + |
| 26 | +```powershell |
| 27 | +$outputfile = "C:\temp\KeyCredentialLink-report.txt" |
| 28 | +New-Item -ItemType file -Path $outputfile -Force | Out-Null |
| 29 | +
|
| 30 | +"Report generated on " + (Get-Date) | Out-File $outputfile |
| 31 | +
|
| 32 | +# Enumerate all AD users that has a msds-KeyCredentialLink value |
| 33 | +foreach ($user in (Get-ADUser -LDAPFilter '(msDS-KeyCredentialLink=*)' -Properties "msDS-KeyCredentialLink")) { |
| 34 | +
|
| 35 | + # For each user, output the UPN, DN, and all key IDs in msDS-KeyCredentialLink |
| 36 | + "===========`nUser: $($user.UserPrincipalName)`nDN: $($user.DistinguishedName)" | Out-File $outputfile -Append |
| 37 | + "KeyCredialLink Entries:" | Out-File $outputfile -Append |
| 38 | + " Source|Usage|DeviceID |KeyID" | Out-File $outputfile -Append |
| 39 | + " -------------------------------------------------------------------" | Out-File $outputfile -Append |
| 40 | +
|
| 41 | + foreach ($blob in ($user."msDS-KeyCredentialLink")) { |
| 42 | + $KCLstring = ($blob -split ':')[2] |
| 43 | + |
| 44 | + # Check that the entries are version 2 |
| 45 | + if ($KCLstring.Substring(0, 8) -eq "00020000") { |
| 46 | + $curIndex = 8 |
| 47 | + |
| 48 | + # Parse all KeyCredentialLink entries from the hex string |
| 49 | + while ($curIndex -lt $KCLstring.Length) { |
| 50 | +
|
| 51 | + # Read the length, reverse the byte order to account for endianess, then convert to an int |
| 52 | + # The length is in bytes, so multiply by 2 to get the length in characters |
| 53 | + $strLength = ($KCLstring.Substring($curIndex, 4)) -split '(?<=\G..)(?!$)' |
| 54 | + [array]::Reverse($strLength) |
| 55 | + $kcle_Length = ([convert]::ToInt16(-join $strLength, 16)) * 2 |
| 56 | + |
| 57 | + # Read the identifier and value |
| 58 | + $kcle_Identifier = $KCLstring.Substring($curIndex + 4, 2) |
| 59 | + $kcle_Value = $KCLstring.Substring($curIndex + 6, $kcle_Length) |
| 60 | + |
| 61 | + switch ($kcle_Identifier) { |
| 62 | + # KeyID |
| 63 | + '01' { |
| 64 | + $KeyID = $kcle_Value |
| 65 | + } |
| 66 | +
|
| 67 | + # KeyUsage |
| 68 | + '04' { |
| 69 | + switch ($kcle_Value) { |
| 70 | + '01' { $Usage = "NGC " } |
| 71 | + '07' { $Usage = "FIDO " } |
| 72 | + '08' { $Usage = "FEK " } |
| 73 | + Default { $Usage = $kcle_Value } |
| 74 | + } |
| 75 | + } |
| 76 | +
|
| 77 | + # Source |
| 78 | + '05' { |
| 79 | + switch ($kcle_Value) { |
| 80 | + '00' { $Source = "AD " } |
| 81 | + '01' { $Source = "Entra " } |
| 82 | + Default { $Source = $kcle_Value } |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + # DeviceID |
| 87 | + '06' { |
| 88 | + $tempByteArray = $kcle_Value -split '(?<=\G..)(?!$)' |
| 89 | + $DeviceID = [System.Guid]::new($tempByteArray[3..0] + $tempByteArray[5..4] + $tempByteArray[7..6] + $tempByteArray[8..16] -join "") |
| 90 | + } |
| 91 | + } |
| 92 | +
|
| 93 | + $curIndex += 6 + $kcle_Length |
| 94 | + } |
| 95 | +
|
| 96 | + # Save the data to file |
| 97 | + " $Source|$Usage|$DeviceID|$KeyID" | Out-File $outputfile -Append |
| 98 | + } |
| 99 | + } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +## Script sample output and analysis |
| 104 | + |
| 105 | +Here's a sample output of the script: |
| 106 | + |
| 107 | +```output |
| 108 | + |
| 109 | +DN: CN=user1,OU=MyOU,DC=contoso,DC=com |
| 110 | +KeyCredialLink Entries: |
| 111 | + Source|Usage|DeviceID |KeyID |
| 112 | + ------------------------------------------------------------------- |
| 113 | + Entra |NGC |8a763ab0-0f6f-44f3-99ae-599a6aaca45b|FD68391824C44158B23C5605F567A588D02C4B2962AC96B789EDBCE091CF5067 |
| 114 | + Entra |NGC |9cf88f41-1e1e-462b-87d5-6938410ea82c|E91EF4E058513155A3F7E7E5B3E34951ADE923FFD0A7C24BB9957510F007E2F3 |
| 115 | + Entra |NGC |d04581fc-d1c8-45fc-a5ad-192bc649574f|E60B476088CC5CDF6FB75454CFA6E17C3059D51F2CA1E414E1554715BE6C0527 |
| 116 | +=========== |
| 117 | + |
| 118 | +DN: CN=user2,OU=MyOU,DC=contoso,DC=com |
| 119 | +KeyCredialLink Entries: |
| 120 | + Source|Usage|DeviceID |KeyID |
| 121 | + --------------------------- |
| 122 | + Entra |NGC |c8fcc7a6-8f3f-4ec7-a90b-49c6988ba3a4|32EF67B902CB498710F0091F5B10B6A4A2F05D621B748B8150E08FA3048F227F |
| 123 | +``` |
| 124 | + |
| 125 | +Each `KeyCredentialLink` entry represents a certificate. The output contains the following information: |
| 126 | + |
| 127 | +- `Source`: The source of the certificate. This information can either be from Microsoft Entra ID or on-premises AD. |
| 128 | +- `Usage`: The defined usage of the certificate. This information can be NGC (WHfB), FIDO, or FEK (File Encryption Key). |
| 129 | +- `DeviceID`: The ID of the computer where the certificate was created. This information is the Device ID in Microsoft Entra ID and the objectGUID in AD. |
| 130 | +- `KeyID`: The bcrypt-sha256 key ID hash of the certificate. |
| 131 | + |
| 132 | +The matching certificate should be found in the user's personal certificate store on the computer with the matching `DeviceID`. To find the certificate being used on the client, you can run `certutil -v -user -store my` from a PowerShell or command prompt to dump detailed certificate information from the user's personal store. In this example, you should find a self-signed certificate where the subject and issuer are the same and in the form of `CN=<User SID>/login.windows.net/<Tenant ID>/<user UPN>`. Once you find this certificate, check the bcrypt-sha256 key ID hash. This hash value should match one of the entries in the **msDS-KeyCredentialLink** attribute. |
| 133 | + |
| 134 | +Here's an excerpt from [email protected]'s certificate store: |
| 135 | + |
| 136 | +```output |
| 137 | +> certutil -v -user -store my |
| 138 | +my "Personal" |
| 139 | +================ Certificate 0 ================ |
| 140 | +X509 Certificate: |
| 141 | +Version: 3 |
| 142 | +Serial Number: 184621… |
| 143 | +Signature Algorithm: |
| 144 | + Algorithm ObjectId: 1.2.840.113549.1.1.11 sha256RSA |
| 145 | + Algorithm Parameters: |
| 146 | + 05 00 |
| 147 | +Issuer: |
| 148 | + CN=S-1-5-21-394…7436/login.windows.net/ccf…83a/[email protected] |
| 149 | +
|
| 150 | + : |
| 151 | +
|
| 152 | +Signature Algorithm: |
| 153 | + Algorithm ObjectId: 1.2.840.113549.1.1.11 sha256RSA |
| 154 | + Algorithm Parameters: |
| 155 | + 05 00 |
| 156 | +Signature: UnusedBits=0 |
| 157 | + 0000 19 17 8f 65 c1 e3 f1 0a 3b 62 90 7f fa 94 13 ad |
| 158 | + // snip |
| 159 | + 00f0 a2 e3 72 54 a5 0a 84 30 9e 8b 81 19 d3 61 46 58 |
| 160 | +Signature matches Public Key |
| 161 | +Root Certificate: Subject matches Issuer |
| 162 | +Key Id Hash(rfc-sha1): 9a546… |
| 163 | +Key Id Hash(sha1): d66eb… |
| 164 | +Key Id Hash(bcrypt-sha1): 7f4a0… |
| 165 | +Key Id Hash(bcrypt-sha256): 32ef67b902cb… |
| 166 | +``` |
| 167 | + |
| 168 | +## Reference |
| 169 | + |
| 170 | +[Key Credential Link Structures](/openspecs/windows_protocols/ms-adts/de61eb56-b75f-4743-b8af-e9be154b47af) |
0 commit comments