Get-TlsCertInfo.psm1
|
<# .SYNOPSIS Retrieves TLS certificate details from a remote endpoint (via SslStream) and performs expiration checks. #> function Get-TlsCertInfo { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Server, [int]$Port = 443, [int]$TimeoutMs = 10000, [bool]$IgnoreCertErrors = $true, [int]$WarnDays = 30 ) # TCP connect with timeout $tcp = [System.Net.Sockets.TcpClient]::new() $task = $tcp.ConnectAsync($Server, $Port) if (-not $task.Wait($TimeoutMs)) { $tcp.Close() throw "Connection to ${Server}:${Port} timed out after ${TimeoutMs} ms." } try { # Validation callback $callback = if ($IgnoreCertErrors) { [System.Net.Security.RemoteCertificateValidationCallback]{ param($sender,$cert,$chain,$errors) $true } } else { [System.Net.Security.RemoteCertificateValidationCallback]{ param($sender,$cert,$chain,$errors) ($errors -eq [System.Net.Security.SslPolicyErrors]::None) } } $sslStream = [System.Net.Security.SslStream]::new($tcp.GetStream(), $false, $callback) # Prefer SslClientAuthenticationOptions on PS7+ $useAdvancedAuth = $false if ($PSVersionTable.PSVersion.Major -ge 7) { try { $sslOptions = [System.Net.Security.SslClientAuthenticationOptions]::new() $sslOptions.TargetHost = $Server $sslOptions.EnabledSslProtocols = [System.Security.Authentication.SslProtocols]::Tls12 -bor [System.Security.Authentication.SslProtocols]::Tls13 $sslOptions.CertificateRevocationCheckMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::Online $sslStream.AuthenticateAsClient($sslOptions) $useAdvancedAuth = $true } catch { $useAdvancedAuth = $false } } if (-not $useAdvancedAuth) { # PS5.1 fallback $sslStream.AuthenticateAsClient($Server) } # Certificate $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sslStream.RemoteCertificate) # Expiration $now = [DateTime]::UtcNow $notAfterUtc = $cert.NotAfter.ToUniversalTime() $daysLeft = [Math]::Floor(($notAfterUtc - $now).TotalDays) # SAN (OID 2.5.29.17) $sanList = @() foreach ($ext in $cert.Extensions) { if ($ext.Oid.Value -eq '2.5.29.17') { $text = $ext.Format($true) $sanList += ($text -split "`r?`n") | Where-Object { $_ -match '=' } | ForEach-Object { ($_ -split '=', 2)[1].Trim() } } } # Chain $chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new() $null = $chain.Build($cert) $chainSubjects = $chain.ChainElements | ForEach-Object { $_.Certificate.Subject } # Status $status = if ($daysLeft -lt 0) { "EXPIRED ($(-1 * $daysLeft) days ago)" } elseif ($daysLeft -le $WarnDays) { "EXPIRING SOON ($daysLeft days left ≤ $WarnDays)" } else { "OK ($daysLeft days left)" } # Cipher (PS7+) $negCipher = $null if ($sslStream.PSObject.Properties.Name -contains 'NegotiatedCipherSuite') { try { $negCipher = $sslStream.NegotiatedCipherSuite.ToString() } catch { $negCipher = $null } } [PSCustomObject]@{ Server = $Server Port = $Port Subject = $cert.Subject Issuer = $cert.Issuer Thumbprint = $cert.Thumbprint SerialNumber = $cert.SerialNumber NotBeforeUtc = $cert.NotBefore.ToUniversalTime() NotAfterUtc = $notAfterUtc DaysRemaining = $daysLeft ExpirationStatus = $status SAN = $sanList SignatureAlgorithm = $cert.SignatureAlgorithm.FriendlyName PublicKeyAlgorithm = $cert.PublicKey.Oid.FriendlyName KeyLengthBits = $cert.PublicKey.Key.KeySize ChainSubjects = $chainSubjects IgnoreCertErrors = $IgnoreCertErrors ProtocolsRequested = if ($useAdvancedAuth) { $sslOptions.EnabledSslProtocols.ToString() } else { 'Default (AuthenticateAsClient(server))' } SslNegotiatedCipher = $negCipher } } finally { if ($sslStream) { $sslStream.Dispose() } if ($tcp) { $tcp.Close() } } } Export-ModuleMember -Function Get-TlsCertInfo |