Skip to content

Instantly share code, notes, and snippets.

@joshooaj
Last active November 26, 2024 23:01
Show Gist options
  • Save joshooaj/9ae22fa6af0c257a13472f57d9b8a257 to your computer and use it in GitHub Desktop.
Save joshooaj/9ae22fa6af0c257a13472f57d9b8a257 to your computer and use it in GitHub Desktop.
Tests a web URI for connectivity and certificate validation
function Test-TlsConnection {
<#
.SYNOPSIS
Tests a URI for connectivity, and checks whether the TLS certificate is valid.
.DESCRIPTION
This cmdlet tests a URI for connectivity, and checks whether the TLS
certificate is valid, expired, expiring soon, and returns information about
the certificate when used with InformationLevel 'Detailed'.
Since this function does not make an HTTP request, it will work with any service
or application implementing SSL/TLS. It is not exclusively for testing TLS
connectivity to traditional web servers.
.PARAMETER Uri
A web uri on which to return TLS connection test results. For example, https://www.powershellgallery.com
.PARAMETER SslProtocol
Specifies which SSL/TLS protocols to consider "trusted". If the web server
uses any other protocol, the connection is not considered trusted. The default
is TLS 1.2 or greater. If the web server uses a lower version, the command
should still succeed, but `IsTrusted` and `UriTestSucceeded` will be
`$false`.
.PARAMETER InformationLevel
Specifies whether to return detailed information, or a simple $true or $false.
.EXAMPLE
Test-TlsConnection https://badssl.com/
Returns a detailed TestUriResult with an IsTrusted property value of $true
under normal circumstances.
.EXAMPLE
Test-TlsConnection https://badssl.com/ -InformationLevel Quiet
Returns a value of $true under normal circumstances.
.EXAMPLE
Test-TlsConnection https://expired.badssl.com/
Returns a detailed TestUriResult with an IsExpired property value of $true
.EXAMPLE
Test-TlsConnection https://expired.badssl.com/
Returns a detailed TestUriResult with an IsExpired property value of $true
.EXAMPLE
Test-TlsConnection https://tls-v1-1.badssl.com:1011/ -SslProtocol Tls11
Returns a detailed TestUriResult where IsTrusted and UriTestSucceeded are
$true, because we've specified to use SslProtocol Tls11.
.EXAMPLE
Test-TlsConnection https://tls-v1-1.badssl.com:1011/
Returns a detailed TestUriResult where IsTrusted and UriTestSucceeded are
$false, because only Tls12 and Tls13 are trusted by default.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)]
[uri[]]
$Uri,
[Parameter()]
[System.Security.Authentication.SslProtocols[]]
$SslProtocol = ([enum]::GetValues([System.Security.Authentication.SslProtocols]) | Where-Object { $_ -match 'TLS[1-9][2-9]' }),
[Parameter()]
[ValidateSet('Detailed', 'Quiet')]
[string]
$InformationLevel = 'Detailed'
)
process {
[System.Security.Authentication.SslProtocols]$trustedProtocols = 0
$SslProtocol | ForEach-Object { $trustedProtocols = $trustedProtocols -bor $_ }
foreach ($address in $Uri) {
$result = [pscustomobject]@{
PSTypeName = 'TestUriResult'
Uri = $address
RemoteAddress = $null
RemotePort = $null
SourceAddress = $null
RemoteCertificate = $null
CipherAlgorithm = $null
HashAlgorithm = $null
SslProtocol = $null
TcpTestSucceeded = $false
IsExpired = $false
IsExpiring = $false
IsTrusted = $false
UriTestSucceeded = $false
}
try {
$tcpClient = [net.sockets.tcpclient]::new($address.Host, $address.Port)
$result.TcpTestSucceeded = $true
$result.RemoteAddress = $tcpClient.Client.RemoteEndPoint.Address
$result.RemotePort = $tcpClient.Client.RemoteEndPoint.Port
$result.SourceAddress = $tcpclient.Client.LocalEndPoint.Address
$stream = $tcpClient.GetStream()
$sslStream = [net.security.sslstream]::new($stream, $false, { $true })
$protocols = 0; [enum]::GetValues([System.Security.Authentication.SslProtocols]) | Where-Object { $_ -match '(Ssl|Tls)' } | ForEach-Object { $protocols = $protocols -bor $_ }
$sslStream.AuthenticateAsClient($address.Host, $null, $protocols, $true)
$certInfo = [security.cryptography.x509certificates.x509certificate2]::new($sslStream.RemoteCertificate)
$result.SslProtocol = $sslStream.SslProtocol
$result.RemoteCertificate = $certInfo
$result.CipherAlgorithm = $sslStream.CipherAlgorithm
$result.HashAlgorithm = $sslStream.HashAlgorithm
$result.IsExpired = $certInfo.NotAfter -le (Get-Date)
$result.IsExpiring = $certInfo.NotAfter -le (Get-Date).AddDays(30)
$result.IsTrusted = $certInfo.Verify() -and ($sslStream.SslProtocol -band $trustedProtocols)
$result.UriTestSucceeded = $result.IsTrusted -and !$result.IsExpired -and ($sslStream.SslProtocol -band $trustedProtocols)
if (-not ($sslStream.SslProtocol -band $trustedProtocols)) {
Write-Warning "The transport layer security protocol $($sslStream.SslProtocol) is not in the list of trusted protocols: $trustedProtocols."
}
if ($result.IsExpired) {
Write-Warning "Certificate for '$address' is expired. Subject='$($result.RemoteCertificate.Subject)'; NotAfter='$($result.RemoteCertificate.NotAfter.ToString('o'))'"
} elseif ($result.IsExpiring) {
Write-Warning "Certificate for '$address' expires in 30 days or less. Subject='$($result.RemoteCertificate.Subject)'; NotAfter='$($result.RemoteCertificate.NotAfter.ToString('o'))'"
}
} catch {
Write-Error -ErrorRecord $_
} finally {
if ($sslStream) {
$sslStream.Dispose()
}
if ($stream) {
$stream.Dispose()
}
if ($tcpClient) {
$tcpClient.Dispose()
}
}
if ($InformationLevel -eq 'Quiet') {
$result.UriTestSucceeded
} else {
$result
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment