0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TLS通信したい (3) Hello, world!

Last updated at Posted at 2025-08-31

1. はじめに

なんでも「とりあえず」ですが、ポート番号5000でやりとりすることを考えましょう。
サーバー側とクライアント側と二つのプログラムを作ることになります。

2-1.サーバー側のコード

  1. サーバーは 自分で作ったサーバー証明書をロードし、クライアントを待ちます
  2. クライアントが接続し、クライアントが何か送信してきたら、それを返します
TlsServer.cs
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Text;

class TlsServer
{
    static void Main()
    {
        int port = 5000;
        string certPath = "server.pfx";
        string certPassword = "p@ssw0rd"; // PFX パスワード

        X509Certificate2 serverCertificate = new X509Certificate2(certPath, certPassword);

        TcpListener listener = new TcpListener(IPAddress.Any, port);
        listener.Start();
        Console.WriteLine($"Server listening on port {port}...");

        while (true)
        {
            TcpClient client = listener.AcceptTcpClient();
            Console.WriteLine("Client connected.");

            using (NetworkStream networkStream = client.GetStream())
            using (SslStream sslStream = new SslStream(networkStream, false))
            {
                sslStream.AuthenticateAsServer(serverCertificate, clientCertificateRequired: false, enabledSslProtocols: System.Security.Authentication.SslProtocols.Tls13, checkCertificateRevocation: false);
                Console.WriteLine("TLS handshake completed.");

                // クライアントから受信
                byte[] buffer = new byte[1024];
                int bytesRead = sslStream.Read(buffer, 0, buffer.Length);
                string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                Console.WriteLine("Received from client: " + message);

                // そのまま返す
                byte[] response = Encoding.UTF8.GetBytes(message);
                sslStream.Write(response);
                sslStream.Flush();
            }
            client.Close();
        }
    }
}

2-2.とりあえずクライアント側のコード

  1. クライアントはサーバーに接続します
  2. サーバー認証することは考えずに受け入れます
  3. サーバーに "Hello, world!" を送信します
  4. サーバーから送信された文字列を表示します
    この場合、送った "Hello, world!" が サーバーから送り返されてくるのでそれを表示します。
TlsClient.cs
using System;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;

class TlsClientTest
{
    static void Main()
    {
        string server = "localhost";
        int port = 5000;

        TcpClient client = new TcpClient(server, port);
        // サーバ証明書を検証せずに、とりあえず承認する
        using (SslStream sslStream = new SslStream(client.GetStream(), false, (sender, cert, chain, errors) => true))
        {
            sslStream.AuthenticateAsClient(server, null, SslProtocols.Tls13, checkCertificateRevocation: false);
            Console.WriteLine("TLS handshake completed.");

            // サーバに送信
            string message = "Hello, world!";
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);
            sslStream.Write(messageBytes);
            sslStream.Flush();

            // サーバから受信
            byte[] buffer = new byte[1024];
            int bytesRead = sslStream.Read(buffer, 0, buffer.Length);
            string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine("Received from server: " + response);
        }

        client.Close();
    }
}

2-2.サーバー認証付のクライアントコード

真面目にサーバー認証をしてみましょう。
具体的には「サーバー証明書にはこう書かれているはず」を検証します。
サーバー証明書には 「CN=なんちゃらかんちゃら」とか、SANの部分にドメイン名が書いてあるはずです。

そこをチェックします。

TlsClient.cs
using System;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;

class TlsClient
{
    static void Main()
    {
        string server = "localhost"; // 接続先サーバ名
        int port = 5000;

        TcpClient client = new TcpClient(server, port);

        using (SslStream sslStream = new SslStream(
            client.GetStream(),
            false,
            // サーバー証明書を検証する(delegateで渡す)
            new RemoteCertificateValidationCallback(ValidateServerCertificate)
        ))
        {
            sslStream.AuthenticateAsClient(
                server,
                null,
                SslProtocols.Tls13,
                checkCertificateRevocation: true
            );

            Console.WriteLine("TLS handshake completed.");
            Console.WriteLine("Negotiated protocol: " + sslStream.SslProtocol);

            string message = "Hello world";
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);
            sslStream.Write(messageBytes);
            sslStream.Flush();

            byte[] buffer = new byte[1024];
            int bytesRead = sslStream.Read(buffer, 0, buffer.Length);
            string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine("Received from server: " + response);
        }

        client.Close();
    }

    // サーバー証明書を検証する
    public static bool ValidateServerCertificate(
        object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        X509Certificate2 cert2 = certificate as X509Certificate2;

        if (cert2 == null)
        {
            Console.WriteLine("Certificate is null.");
            return false;
        }

        // 1. OS による標準的な検証(CA署名)
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            // CA署名ならばOSのチェックでここに落ちるはず
            return true; // 問題なし
        }

        // 以下、自己署名の場合
        // 2. CN と SAN の確認
        string expectedServerName = "localhost";

        // CN チェックかSANのどちらかが一致していればOK
        // ブラウザや.NET 標準 TLS 実装も OR で評価
        string cn = cert2.GetNameInfo(X509NameType.DnsName, false);
        if (cn.Equals(expectedServerName, StringComparison.OrdinalIgnoreCase))
        {
            // 予想されるサーバー名と一致しました。
            Console.WriteLine("CN matches expected server name.");
            return true;
        }

        // SAN チェック
        foreach (var extension in cert2.Extensions)
        {
            if (extension.Oid.Value == "2.5.29.17") // Subject Alternative Name
            {
                var san = new System.Security.Cryptography.AsnEncodedData(extension.Oid, extension.RawData);
                string sanString = san.Format(true);
                if (sanString.Contains(expectedServerName))
                {
                    Console.WriteLine("SAN contains expected server name.");
                    return true;
                }
            }
        }

        Console.WriteLine("CN/SAN check failed.");
        return false;
    }
}

3. WireSharkでキャプチャ(終わりに)

んー。クライアントには帰ってきているのにTLS 1.3になっていない。
これであっているのかあっていないのか。たぶんあっていないと思う。
でもなんとなく、SslStreamに流せばあとはライブラリやらOSに任せていいんだ、という気がしてきました。

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?