Problem Fetching Subject Alternative Names using certificate Extensions

112 Views Asked by At

I currently have this powershell code that basically fetches basic information about certificates from a URI, and I need to do it in a Powershell Native fashion, therefore I cannot use tools like OpenSSL, hence why I commented it out. Everything works, but when trying to fetch the SANs, it comes up empty in powershell.

this is output of the code below when fetching cert info about a URI, for safety reasons I cannot show the whole output. But everything above this works well.

Here is the code

[CmdletBinding()]
Param(
    [Parameter(Mandatory = $True, Position = 0)]
    [string]$uri
    #[Parameter(Mandatory = $false, Position = 1)]
    #[string]$path = 'C:\Apps\OpenSSL-Win64\bin\'
)

#if (-Not (Test-Path -Path $path)) {
#    Write-Error "OpenSSL client not found. Supply a valid OpenSSL \bin path using -path parameter"
#    return        
#}
#else {
#    $openSSL = (Join-Path -Path $path -ChildPath openssl.exe)
#}

# Disable certificate validation
[Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

# Force TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Set timeout for the web client
$timeoutMs = 10000

# Show URI being tested (passed as input parameter URI)
$endPoint = "https://{0}" -f $uri
$portString = ":443"
$server = "{0}{1}" -f $uri,$portString
Write-Host "Check $endPoint" -ForegroundColor Green

$req = [Net.HttpWebRequest]::Create($endPoint)
$req.Timeout = $timeoutMs

try {
    $req.GetResponse() | Out-Null
} 
catch {
    Write-Host "URL check error $uri`: $_" -ForegroundColor Red
}

# Retrieve certificate details
$cert = $req.ServicePoint.Certificate

$certExpDate = $cert.GetExpirationDateString()
$certName = $cert.GetName()
$certThumbprint = $cert.GetCertHashString()
$certEffectiveDate = $cert.GetEffectiveDateString()

# Retrieve SANs from the certificate's raw data
$certSANs = @()
$rawSANs = $cert.Extensions | Where-Object { $_.Oid.Value -eq "2.5.29.17" } | ForEach-Object { $_.Format($false) }
foreach ($rawSAN in $rawSANs) {
    $certSANs += ($rawSAN -split ", " | Where-Object { $_ -like "DNS:*" }).Substring(4)
}

# Construct an output string using PowerShell string formatting (-f)
$certDetails = @"
Subject: $certName
Thumbprint: $certThumbprint
Effective Date: $certEffectiveDate
Expiry Date: $certExpDate
SANs: $($certSANs -join ", ")
"@

# Output certificate details to terminal    
Write-Output $certDetails

I tried to use the extensions of certificate to pull out the Sans, with the Oid value of 2.5.29.17 but had no success. I was expecting it to pull out the SANS of the URI mentioned in powershell, then format it.

2

There are 2 best solutions below

0
TheMadTechnician On

In my experience the certificate that you get from that doesn't expose the extensions. The data's there, but in that context it isn't available. What you can do is convert it to raw data, initialize a new certificate object, then import the raw data, and that should get you the extension data, and allow you to extract the SANs.

$rawcert = $req.ServicePoint.Certificate.Export('cert')
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($rawcert)
$SANs = $cert.Extensions.where({?{$_.oid.value -eq '2.5.29.17'}).format(0) -split ', ' |?{$_ -like 'DNS*'}|%{$_ -replace '^.*?[:=]'}
$certDetails = @"
Subject: $($cert.Subject)
Thumbprint: $($cert.Thumbprint)
Effective Date: $($cert.NotBefore)
Expiry Date: $($cert.NotAfter)
SANs: $($SANs -join ", ")
"@
0
Dallas On

I was also a bit frustrated by the inability to pull SAN values along with all the other common certificate property values you might expect could be pulled altogether when retrieving via a PowerShell function call.

To display in my function, I was able to retrieve the SAN values and populate Custom Object Property with the following... $ProtocolStatus["SAN"] = @($RemoteCertificate.Extensions | Where-Object {$_.Oid.FriendlyName -eq "Subject Alternative Name"} | ForEach-Object {$_.Format(0)}) . You may need to modify a bit, but the part you're after would be something similar to

$SANValues = $RemoteCertificate.Extensions | Where-Object {$_.Oid.FriendlyName -eq "Subject Alternative Name"} | ForEach-Object {$_.Format(0)} ...assuming $RemoteCertificate is an object of type System.Security.Cryptography.X509Certificates.X509Certificate2

I actually like what I see from @TheMadTechnician better than mine, for most purposes, because that code removes the "DNSName=" from each SAN value and would just list the values themselves... whereas the results from my function is stored/displayed in object or array as

SAN                : {DNS Name=sub.domain.com, DNS Name=sub2.domain.com, DNS Name=aliasdomain.com}