关于本文
本文是通过利用生成式 AI 的自动化生成流程创建的。我们查阅了 .NET 的 TcpClient、SslStream 和 X509Certificate2 的官方文档,并将其整理为建立 TLS 连接后读取服务器证书有效期的极简示例。验证状态:📘 已确认 .NET 官方规范,尚未执行外部 TLS 连接
无需打开浏览器页面,即可通过 PowerShell 建立 TLS 连接,并确认对方出示证书的主题和有效期。关键在于不禁用证书验证,而是使用SslStream的常规认证流程。
首先尝试
$hostName = 'www.microsoft.com'
$tcp = [Net.Sockets.TcpClient]::new()
$ssl = $null
try {
$tcp.Connect($hostName, 443)
$ssl = [Net.Security.SslStream]::new($tcp.GetStream(), $false)
$ssl.AuthenticateAsClient($hostName)
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new(
$ssl.RemoteCertificate
)
[pscustomobject]@{
Host = $hostName
Subject = $cert.Subject
NotAfter = $cert.NotAfter
DaysLeft = [math]::Floor(($cert.NotAfter - (Get-Date)).TotalDays)
}
}
finally {
if ($null -ne $ssl) { $ssl.Dispose() }
$tcp.Dispose()
}
查看此处
AuthenticateAsClient()在完成后阅读RemoteCertificate。DaysLeft是“从当前到 NotAfter 还有多少天”的观测值。
为什么要传递主机名
在 TLS 中,不仅连接的目标 IP 很重要,“连接到哪个主机名”同样关键。AuthenticateAsClient($hostName)的主机名与使用服务器名的 TLS 认证有关。
sequenceDiagram
participant P as PowerShell
participant T as TcpClient
participant S as TLS Server
P->>T: host:443へ接続
T->>S: TCP接続
P->>S: AuthenticateAsClient(host)
S-->>P: 証明書を提示
P->>P: RemoteCertificateを読む
尝试修改一个地方
www.microsoft.com将github.com更改为
$hostName = 'github.com'
,并比较主题和到期时间有何变化。同样的极简代码,如果对象改变,证书信息也会改变。由此可知,这并非“查看 443 端口的到期时间”,而是查看连接目标所出示的证书。
切勿仅凭到期时间判断“正常”
即使 NotAfter 在未来,也不能断定 TLS 完全健康。还存在名称不匹配、信任链、吊销、TLS 设置等其他因素。
此外,在使用 CDN 或负载均衡器的网站中,有时观察到的是前端服务实际出示的证书,并不一定能看到内部源服务器的证书。
如果在工作中实用
提前确认内部网站证书未及时更新的问题
用于列出多个 FQDN 剩余天数之前的极简验证
将“浏览器中可见的证书”与自动监控的结果进行对照
在咨询供应商之前,记录实际出示的主题/到期时间
如果将其发展为监控,不要因为失败一次就判定为过期,而是将失败位置细分为 DNS、TCP 连接、TLS 认证和证书获取并分别记录,这样在实际业务中会更易于使用。
需要清理资源的原因
TcpClient和SslStream由于持有连接资源,因此需通过finally进行释放(Dispose)。特别是在扩展为遍历多个主机的处理时,省略清理会导致不必要地残留连接资源。

