C#でSSL(サーバー証明書の検証あり版)
サーバー証明書の検証も行う。
例として、インターネット上のWebサーバーとSSL通信を行う。
手順は、以下のようになる。
1 | Webサーバー名からWebサーバーのIPアドレスをDNSでルックアップ |
2 | TcpClientを用いて、Webサーバーの443番ポートに接続する |
3 | SslStreamを生成し、でSSL通信を開始 |
4 | SslStream#AuthenticateAsClientメソッドで、サーバー証明書の検証を行う |
5 | HTTPリクエストを送信する |
6 | HTTPレスポンスを受信する |
PrintCertificateメソッドは、サーバー証明書の内容をコンソールに表示するためのメソッド。
RemoteCertificateValidationCallbackメソッドで、証明書の検証を行なっている。
using System; using System.Net; using System.Net.Sockets; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.IO; using System.Text; public class Program { //証明書の内容を表示するメソッド private static void PrintCertificate(X509Certificate certificate) { Console.WriteLine("==========================================="); Console.WriteLine("Subject={0}", certificate.Subject); Console.WriteLine("Issuer={0}", certificate.Issuer); Console.WriteLine("Format={0}", certificate.GetFormat()); Console.WriteLine("ExpirationDate={0}", certificate.GetExpirationDateString()); Console.WriteLine("EffectiveDate={0}", certificate.GetEffectiveDateString()); Console.WriteLine("KeyAlgorithm={0}", certificate.GetKeyAlgorithm()); Console.WriteLine("PublicKey={0}", certificate.GetPublicKeyString()); Console.WriteLine("SerialNumber={0}", certificate.GetSerialNumberString()); Console.WriteLine("==========================================="); } //サーバー証明書を検証するためのコールバックメソッド private static Boolean RemoteCertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { PrintCertificate(certificate); if (sslPolicyErrors == SslPolicyErrors.None) { Console.WriteLine("サーバー証明書の検証に成功しました\n"); return true; } else { //何かサーバー証明書検証エラーが発生している //SslPolicyErrors列挙体には、Flags属性があるので、 //エラーの原因が複数含まれているかもしれない。 //そのため、&演算子で1つ1つエラーの原因を検出する。 if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.RemoteCertificateChainErrors) { Console.WriteLine("ChainStatusが、空でない配列を返しました"); } if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) == SslPolicyErrors.RemoteCertificateNameMismatch) { Console.WriteLine("証明書名が不一致です"); } if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNotAvailable) == SslPolicyErrors.RemoteCertificateNotAvailable) { Console.WriteLine("証明書が利用できません"); } //検証失敗とする return false; } } public static void Main() { //ホスト名とポート番号を指定 String hostName = "google.com"; Int32 port = 443; using (TcpClient client = new TcpClient()) { //接続先Webサーバー名からIPアドレスをルックアップ IPAddress[] ipAddresses = Dns.GetHostAddresses(hostName); //Webサーバーに接続する client.Connect(new IPEndPoint(ipAddresses[0], port)); //SSL通信の開始 using (SslStream sslStream = new SslStream(client.GetStream(), false, RemoteCertificateValidationCallback)) { //サーバーの認証を行う //これにより、RemoteCertificateValidationCallbackメソッドが呼ばれる sslStream.AuthenticateAsClient(hostName); //HTTPリクエストをサーバーに送信する Byte[] req = Encoding.ASCII.GetBytes(String.Format("GET / HTTP/1.0\r\nHost: {0}\r\n\r\n", hostName)); sslStream.Write(req); sslStream.Flush(); //サーバーから受信したHTTPレスポンスを読み込んで //コンソールに表示数 Byte[] res = new Byte[1024]; Int32 n; while ( (n = sslStream.Read(res, 0, res.Length) ) > 0) { String s = Encoding.ASCII.GetString(res, 0, n); Console.WriteLine(s); } } } } }
ソースコードの補足説明
SslStreamクラスコンストラクタの第3引数で指定した、RemoteCertificateValidationCallbackというコールバックメソッドでサーバー証明書の検証を行っている。
RemoteCertificateValidationCallbackは、デリゲートとして以下のように定義されている。
public delegate bool RemoteCertificateValidationCallback( Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors )
サーバー証明書の検証を行うためには、このメソッドの第3引数である、sslPolicyErrorsを用いる。
sslPolicyErrorsは、SslPolicyErrors列挙体のオブジェクトであり、SslPolicyErrors列挙体は、以下のように定義されている。
namespace System.Net.Security { // 概要: // SSL (Secure Socket Layer) のポリシー エラーを列挙します。 [Flags] public enum SslPolicyErrors { // 概要: // SSL のポリシー エラーはありません。 None = 0, // // 概要: // 証明書が利用できません。 RemoteCertificateNotAvailable = 1, // // 概要: // 証明書名が不一致です。 RemoteCertificateNameMismatch = 2, // // 概要: // System.Security.Cryptography.X509Certificates.X509Chain.ChainStatus が、空でない配列を返しました。 RemoteCertificateChainErrors = 4, } }
SslPolicyErrors列挙体には Flags属性 が付いているため、サーバー証明書の検証エラーの複合的な原因を、&演算子を用いて詳細に調べることができる
実行結果
=========================================== Subject=CN=*.google.com, O=Google Inc, L=Mountain View, S=California, C=US Issuer=CN=Google Internet Authority, O=Google Inc, C=US Format=X509 ExpirationDate=2014/01/01 0:58:50 EffectiveDate=2013/04/11 21:52:18 KeyAlgorithm=1.2.840.10045.2.1 PublicKey=0481072B5CEC82040D3E50F8B7D07A47423C0CB215E0B716AD744E46166782A4729BCA A1155C1332105EB0A331B721BB1917106E56464312861ED7ADD54464F4B1 SerialNumber=40950B0A00010000839D =========================================== サーバー証明書の検証に成功しました HTTP/1.0 301 Moved Permanently Location: https://www.google.com/ Content-Type: text/html; charset=UTF-8 Date: Tue, 30 Apr 2013 11:28:01 GMT Expires: Thu, 30 May 2013 11:28:01 GMT Cache-Control: public, max-age=2592000 Server: gws Content-Length: 220 X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="https://www.google.com/">here</A>. </BODY></HTML>
実際には、Main関数のhostNameに不正なサーバー証明書を用いているWebサーバー名を指定しないと、検証エラーを発生させることができません。ですので自前でオレオレ証明書を作成して、自前のWebサーバーにセットして試すのが良いかと思います。