目的
OpenSSLを用いて秘密鍵付きの自己署名証明書を作り、C# の SslStream
を用いて簡易HTTPSサーバを立てます。
この記事の目的はとりあえず適当なHTTPSサーバを立てることであり、セキュリティは考慮していません。
例えば、クラウドサービス上で秘密鍵を作る手順がありますが、実際に運用する場合は秘密鍵はローカル (サーバを動かすコンピュータ上) で生成するべきです。
証明書の作成
OpenSSLを用い、以下の手順で秘密鍵付きの自己署名証明書を作ります。
- 秘密鍵を作る
- 署名要求ファイルを作る
- 証明書を作る
- 秘密鍵と証明書を合体する
ブラウザでプログラミング・実行ができる「オンライン実行環境」| paiza.IO
の Bash で以下のコードを実行することで、秘密鍵付きの証明書を作成できます。
openssl genrsa -out server.key 2048
openssl req -utf8 -new -key server.key -out server.csr -subj "/C=JP/ST=Test-State/L=Test-City/O=Test/OU=/CN=localhost:8080"
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
openssl pkcs12 -export -password pass:password -inkey server.key -in server.crt -out certificate.pfx
zip -q certificate.zip server.* certificate.pfx
base64 certificate.zip
最初の4行で上に挙げた手順をそれぞれ実行し、最後の2行で作成したデータをBase64エンコードしたZIP形式で取り出します。
取り出したBase64データは、例えばCyberChefでデコードし、ファイル化することができます。
デコードしたZIPファイルを展開すると、以下のファイルが得られます。
-
server.key
: 秘密鍵 -
server.csr
: 署名要求データ -
server.crt
: 証明書 -
certificate.pfx
: 秘密鍵付きの証明書
簡易HTTPSサーバを立てる
C# で SslStream
を用うと、TLS通信を行うことができます。
まず、以下のように X509Certificate2
クラスを用いて作成した秘密鍵付きの証明書を読み込みます。
第1引数に秘密鍵付きの証明書のファイル名、第2引数に秘密鍵付きの証明書を作る時に設定したパスワードを指定します。
X509Certificate2 cert = new X509Certificate2("certificate.pfx", "password");
2
が付かない X509Certificate
を用いた場合、
後述の AuthenticateAsServer
で以下のエラーが出てしまいました。
System.NotSupportedException: サーバー モード SSL は関連付けられた秘密キーを使用した証明書を使用しなければなりません。
通常のTCPサーバを立て、接続されたら、以下のように SslStream
を作成し、サーバとして初期化します。
SslStream stream = new SslStream(client.GetStream(), false);
stream.AuthenticateAsServer(cert, false, SslProtocols.Tls12 | SslProtocols.Tls13, false);
TLSのバージョンを明示的に指定しないと、TLSv1になってしまい、Google Chromeで接続できませんでした。
以降、この stream
を通じてクライアントとの通信ができます。
以下がコード全体です。
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace Test
{
public class SslServerTest
{
public static void Main(string[] args)
{
X509Certificate2 cert = new X509Certificate2("certificate.pfx", "password");
TcpListener listener = new TcpListener(IPAddress.Any, 8080);
listener.Start();
for(;;)
{
Console.WriteLine("listening");
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("accepted");
SslStream stream = new SslStream(client.GetStream(), false);
try
{
stream.AuthenticateAsServer(cert, false, SslProtocols.Tls12 | SslProtocols.Tls13, false);
StreamReader sr = new StreamReader(stream);
string line;
while ((line = sr.ReadLine()) != null && !line.Equals(""))
{
Console.WriteLine("received: " + line);
}
StreamWriter sw = new StreamWriter(stream);
sw.Write("HTTP/1.0 200 OK\r\n");
sw.Write("Conenction: close\r\n");
sw.Write("Content-Type: text/plain\r\n");
sw.Write("Content-Length: 5\r\n");
sw.Write("\r\n");
sw.Write("hello");
sw.Flush();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
stream.Close();
}
}
}
}
}
このコードを、最近のWindowsに標準付属しているC#コンパイラでコンパイルします。
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc" SslServerTest.cs
コンパイルした結果の実行ファイルを実行すると、Google Chrome で接続することができました。
参考サイト
証明書の作成
- 【初心者向け】最速で自己証明書を発行するためのコマンド集 - サーバーワークスエンジニアブログ
- opensslコマンドでcsrファイルを作成する際のサーバ名等をコマンドラインオプションで指定する | パソコン鳥のブログ
- 秘密鍵、公開鍵形式からPKCS#12(pfx)形式への変換方法
- opensslのパスフレーズ引数について - Qiita
簡易HTTPSサーバを立てる
- X509Certificate2 Class (System.Security.Cryptography.X509Certificates) | Microsoft Learn
- SslStream Class (System.Net.Security) | Microsoft Learn
- SslStream.AuthenticateAsServer Method (System.Net.Security) | Microsoft Learn
- SslProtocols Enum (System.Security.Authentication) | Microsoft Learn
- OSに標準付属のC#/VBコンパイラーでソースコードをコンパイルするには?:.NET TIPS - @IT
- c# - How to implement a Https web server using SslStream and a self signed certificate? - Stack Overflow
- c# - SslStream AuthenticateAsServer failed with "Server certificate Private Key unavailable." exception on Mono - Stack Overflow
- c# - Associate a private key with the X509Certificate2 class in .net - Stack Overflow