Last active
May 26, 2025 00:54
-
-
Save jborean93/6f6e99737ada09062e89a1f9c9862bec to your computer and use it in GitHub Desktop.
Gets the Certificate Template Information from an X509Certificate2 object
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
# Copyright: (c) 2025, Jordan Borean (@jborean93) <[email protected]> | |
# MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
using namespace System.DirectoryServices | |
using namespace System.Formats.Asn1 | |
using namespace System.Management.Automation | |
using namespace System.Numerics | |
using namespace System.Security.Cryptography.X509Certificates | |
Function Get-CertificateTemplateInformation { | |
<# | |
.SYNOPSIS | |
Get the certificate template information. | |
.DESCRIPTION | |
Gets the certificate template extension information from the extension data and try and retrieve the name from the current domain environment. | |
.PARAMETER Certificate | |
The certificate(s) to retrieve the template information for. | |
.EXAMPLE | |
Get-ChildItem Cert:\LocalMachine\My | Get-CertificateTemplateInformation | |
.NOTES | |
If the certificate does not have the extension Certificate Template Information (1.3.6.1.4.1.311.21.7) then an error will be emitted. | |
#> | |
[OutputType("CertificateTemplateInformation")] | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[X509Certificate2[]] | |
$Certificate | |
) | |
begin { | |
$CertificateTemplateInformation = '1.3.6.1.4.1.311.21.7' | |
} | |
process { | |
foreach ($cert in $Certificate) { | |
try { | |
Write-Verbose -Message "Processing certificate $($cert.Thumbprint)" | |
$ext = $cert.Extensions | Where-Object { $_.Oid.Value -eq $CertificateTemplateInformation } | |
if (-not $ext) { | |
$err = [ErrorRecord]::new( | |
[ArgumentException]::new( | |
"Certificate $($cert.Thumbprint) does not have the Certificate Template Information extension present"), | |
"NoCertTemplateInfoExtension", | |
[ErrorCategory]::InvalidArgument, | |
$cert) | |
$PSCmdlet.WriteError($err) | |
continue | |
} | |
<# | |
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/9da866e5-9ce9-4a83-9064-0d20af8b2ccf | |
CertificateTemplateOID ::= SEQUENCE { | |
templateID OBJECT IDENTIFIER, | |
templateMajorVersion INTEGER (0..4294967295) OPTIONAL, | |
templateMinorVersion INTEGER (0..4294967295) OPTIONAL | |
} --#public | |
#> | |
Write-Verbose -Message "Reading CertificateTemplateOID extension data for $($cert.Thumbprint)" | |
$majorVersion = $null | |
$minorVersion = $nulls | |
if ('AsnReader' -as [type]) { | |
# System.Formats.Asn1.* are included in pwsh 7.x but won't be | |
# present in WinPS unless added to the GAC. | |
$reader = [AsnReader]::new( | |
$ext.RawData, | |
[AsnEncodingRules]::DER).ReadSequence() | |
$templateId = $reader.ReadObjectIdentifier() | |
if ($reader.HasData) { | |
Write-Verbose -Message "Getting templateMajorVersion data" | |
$majorVersion = [Int64]$reader.ReadInteger() | |
} | |
if ($reader.HasData) { | |
Write-Verbose -Message "Getting templateMinorVersion data" | |
$minorVersion = [Int64]$reader.ReadInteger() | |
} | |
} | |
else { | |
# This has very little validation and only supports a | |
# smaller subset of ASN.1 features. The structures are | |
# simple enough that we should be able to get away with it. | |
# This is not every efficient but good enough. | |
$readerLength = $ext.RawData[1] | |
$sequence = $ext.RawData[2..($readerLength + 2)] | |
$templateIdLength = $sequence[1] | |
$templateIdRaw = $sequence[2..($templateIdLength + 1)] | |
$templateId1 = $templateIdRaw[0] | |
$templateId2 = $templateIdRaw[0] % 40 | |
$templateId = [string[]]@( | |
$(($templateId1 - $templateId2) / 40), | |
$templateId2 | |
[int64]$currentVal = 0 | |
for ($i = 1; $i -lt $templateIdRaw.Length; $i++) { | |
$element = $templateIdRaw[$i] | |
$currentVal = ($currentVal -shl 7) + ($element -band 0x7F) | |
if ($element -lt 0x80) { | |
$currentVal | |
$currentVal = 0 | |
} | |
} | |
) -join "." | |
$sequence = $sequence[($templateIdLength + 2)..($sequence.Length - 1)] | |
if ($sequence.Length) { | |
Write-Verbose -Message "Getting templateMajorVersion data" | |
$majorVersionLength = $sequence[1] | |
$majorVersionRaw = $sequence[2..($majorVersionLength + 1)] | |
[Array]::Reverse($majorVersionRaw) | |
$majorVersion = [int64][BigInteger]::New($majorVersionRaw) | |
$sequence = $sequence[($majorVersionLength + 2)..($sequence.Length - 1)] | |
} | |
if ($sequence.Length) { | |
Write-Verbose -Message "Getting templateMinorVersion data" | |
$minorVersionLength = $sequence[1] | |
$minorVersionRaw = $sequence[2..($minorVersionLength + 1)] | |
[Array]::Reverse($minorVersionRaw) | |
$minorVersion = [int64][BigInteger]::New($minorVersionRaw) | |
$sequence = $sequence[($majorVersionLength + 2)..($sequence.Length - 1)] | |
} | |
} | |
Write-Verbose -Message "Finding the LDAP configuration naming context for current environment" | |
$configRootDN = ([ADSI]"LDAP://RootDSE").configurationNamingContext | |
$searchBase = "LDAP://CN=Certificate Templates,CN=Public Key Services,CN=Services,$configRootDN" | |
$ldapFilter = "(msPKI-Cert-Template-OID=$templateId)" | |
Write-Verbose -Message "Performing LDAP Get under '$searchBase' with filter '$ldapFilter'" | |
$searcher = [DirectorySearcher]::new( | |
$searchBase, | |
$ldapFilter, | |
@("cn"), | |
[SearchScope]::OneLevel) | |
$templateName = $null | |
foreach ($result in $searcher.FindAll()) { | |
Write-Verbose -Message "Processing LDAP result $($result.Path)" | |
if ($templateName) { | |
$err = [ErrorRecord]::new( | |
[Exception]::new( | |
"Found multiple records for $searchBase with filter $ldapFilter when only expecting 1"), | |
"FoundMultipleLDAPResultsForTemplate", | |
[ErrorCategory]::InvalidResult, | |
$null) | |
$PSCmdlet.WriteError($err) | |
continue | |
} | |
$templateName = $result.Properties.cn[0] | |
} | |
[PSCustomObject]@{ | |
PSTypeName = 'CertificateTemplateInformation' | |
Name = $templateName | |
Oid = $templateId | |
MajorVersion = $majorVersion | |
MinorVersion = $minorVersion | |
} | |
} | |
catch { | |
$PSCmdlet.WriteError($_) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment