Created
October 3, 2021 12:51
-
-
Save olicooper/888e89a39b54ccfb45233559707d0b1d to your computer and use it in GitHub Desktop.
Gets the certificate information for a given host using `System.Net.Sockets.TcpClient`
This file contains 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
<# | |
.SYNOPSIS | |
Gets the certificate information for a given host using System.Net.Sockets.TcpClient | |
.DESCRIPTION | |
Gets the certificate information for a given host, optionally outputting x509 certificate and extended certificate information. | |
.PARAMETER HostName | |
The host to connect to. | |
.PARAMETER SniHostName | |
When the host uses SNI, this authenticates based on the specified SniHostName. | |
.PARAMETER Port | |
The port to connect to. | |
.PARAMETER SslProtocol | |
The protocol to use [Default,Tls,Tls11,Tls12,Tls13,Ssl2,Ssl3]. | |
.PARAMETER FullExceptionOutput | |
When an exception occurs, print out the full exception message rather than an overview. | |
.PARAMETER OutputCertInfo | |
Print the Extended information about the certificate (uses openssl - please ensure it is installed beforehand). | |
.PARAMETER OutputCert | |
Print the X509 certificate string. | |
.EXAMPLE | |
Get-HostCertificateInfo -HostName "example.com" -SniHostName "my.example.com" -Port 443 -OutputCertInfo | |
.Notes | |
NAME: Get-HostCertificateInfo | |
AUTHOR: Oliver Cooper | |
LASTEDIT: 02 October 2021 | |
#> | |
#function Get-HostCertificateInfo { | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory=$true)] | |
[string] | |
$HostName, | |
[string] | |
$SniHostName = "", | |
[int] | |
$Port = 443, | |
[Security.Authentication.SslProtocols] | |
$SslProtocol = [Security.Authentication.SslProtocols]::None, | |
[switch] | |
$FullExceptionOutput, | |
[switch] | |
$OutputCertInfo, | |
[switch] | |
$OutputCert | |
) | |
function FriendlyErrorString { | |
param( | |
[string]$Title, | |
[System.Management.Automation.ErrorRecord]$ThisError | |
) | |
$Return = new-object System.Text.StringBuilder(256) | |
[void]$Return.AppendLine($Title) | |
[void]$Return.Append("`t-> Message: ") | |
if (-Not $ThisError) { | |
[void]$Return.AppendLine("N/A") | |
Return $Return.ToString() | |
} | |
if ($FullExceptionOutput.IsPresent) { | |
[void]$Return.AppendLine($ThisError.Exception.ToString().Trim()) | |
} else { | |
[void]$Return.AppendLine($ThisError.Exception.Message.Trim()) | |
if ($ThisError.Exception.InnerException -and $ThisError.Exception.InnerException.InnerException) { | |
[void]$Return.Append("`t`t-> ") | |
[void]$Return.AppendLine($ThisError.Exception.InnerException.InnerException.Message.Trim()) | |
} | |
} | |
[void]$Return.Append("`t-> Method: ") | |
[void]$Return.Append($ThisError.InvocationInfo.Line.Trim()) | |
[void]$Return.Append(" [L") | |
[void]$Return.Append($ThisError.InvocationInfo.ScriptLineNumber) | |
[void]$Return.Append(":") | |
[void]$Return.Append($ThisError.InvocationInfo.OffsetInLine) | |
[void]$Return.AppendLine("]") | |
Return $Return.ToString() | |
} | |
[bool]$IsError = $false | |
$ErrorString = new-object System.Text.StringBuilder(2000) | |
$Certificate = $null | |
$TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient | |
try { | |
# see: https://docs.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=net-5.0#code-try-2 | |
# Create a TCP/IP client socket | |
$TcpClient.Connect($HostName, $Port) | |
$TcpStream = $TcpClient.GetStream() | |
$Callback = { | |
# see: https://docs.microsoft.com/en-us/dotnet/api/system.net.security.remotecertificatevalidationcallback?view=net-5.0 | |
param($sender, $cert, $chain, [System.Net.Security.SslPolicyErrors]$errors) { | |
if ($errors -ne [System.Net.Security.SslPolicyErrors]::None) { | |
[void]$ErrorString.AppendLine($(Convert-String -InputObject $errors)) | |
} | |
return $true | |
} | |
} | |
$SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList @($TcpStream, $true, $Callback) | |
try { | |
$EmptyCertCollection = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection | |
$SslStream.AuthenticateAsClient($SniHostName, $EmptyCertCollection, $SslProtocol, $false) | |
$Certificate = $SslStream.RemoteCertificate | |
} catch { | |
$IsError = $true | |
write-host $Error[0] | |
[void]$ErrorString.AppendLine($(FriendlyErrorString 'SSL authentication error' $Error[0])) | |
$Error.Clear() | |
} | |
finally { | |
$SslStream.Dispose() | |
} | |
} catch { | |
$IsError = $true | |
[void]$ErrorString.AppendLine($(FriendlyErrorString 'TCP connection error' $Error[0])) | |
} finally { | |
$TcpClient.Dispose() | |
} | |
if ($Certificate) { | |
if ($Certificate -IsNot [System.Security.Cryptography.X509Certificates.X509Certificate2]) { | |
$Certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $Certificate | |
} | |
# Format the certificate for openssl to parse | |
$X509Cert = [System.Convert]::ToBase64String( | |
$Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) | |
) | |
$CertStr = new-object System.Text.StringBuilder(2000) | |
[void]$CertStr.AppendLine("-----BEGIN CERTIFICATE-----") | |
[void]$CertStr.AppendLine($X509Cert) | |
[void]$CertStr.AppendLine("-----END CERTIFICATE-----") | |
#if ($OutputFingerprint.IsPresent) { | |
Write-Output $Certificate | |
Write-Output "" | |
#} | |
if ($OutputCert.IsPresent) { | |
Write-Output $CertStr.ToString() | |
Write-Output "" | |
} | |
if ($OutputCertInfo.IsPresent) { | |
# Pass x509 to openssl for decoding | |
Write-Output $CertStr.ToString() | openssl x509 -text -noout | |
} | |
} elseif ($IsError) { | |
Write-Warning -Message $ErrorString.ToString() | |
} | |
else { | |
Write-Information "No certificate information found" | |
} | |
#} # END Get-HostCertificateInfo | |
#Export-ModuleMember -Function Get-HostCertificateInfo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment