背景
定期的にSSL証明書の期限を確認して、切れそうだったら教えてくれるプログラムを作りたいと思って作った(3年前)
↑これより、もっといい方法があるんじゃないか?と思ってILSpyを見てた。
だいぶいろいろわかってきたので、最新版を書こうと思った。
コード
以前書いたコードでは、HttpClient
の処理の途中で証明書だけ奪ってた。
HttpClient
はかなり複雑なことをやっているんだけど、最終的にはSslStream
を使っているようなので、それを直接使う。
public static async Task<X509Certificate2?> GetCertificateAsync(string host, int port = 443)
{
using var client = new TcpClient(host, port); //※1.1
using var stream = client.GetStream();
using var sslStream = new SslStream(stream, false, (_, _, _, _) => true); //※2
await sslStream.AuthenticateAsClientAsync(host); //※1.2
return sslStream.RemoteCertificate as X509Certificate2; //※3
}
※1: 接続先と認証で、ホスト名を2回使う。※1.2でホスト名が必要なのはなんでだろう?取得した証明書の確認の為か?(SSL SNIの解決のため、ということでした。サーバは複数の証明書を持つことが可能で、どのホスト名の証明書が必要か送らないとダメです。@jzkeyさんありがとうございます。)
※2: 証明書を検査する(ふりをする)コールバック(_, _, _, _) => true
を設定しないといけない。これをやらないと、証明書にエラーがあったときに例外が発生する。今回はたとえエラーがあっても証明書自体を取得したいので、エラーは無視。
※3: 取得できる証明書の型はX509Certificate
なんだけど、実体はX509Certificate2
っぽいので変換。変換しなくてもいいかもしれないけどよくわかっていない。
取得した証明書は、使い終わったらDisposeする必要がある。
sslStream.RemoteCertificate
で証明書を取得すると、SslStream
の破棄時に証明書が一緒に破棄されなくなるっぽい。
↓CancellationToken
でキャンセルできるようにしたバージョン