🍐

.NET MAUIでオレオレ証明書を使ったクライアント認証

2023/08/22に公開

いわゆる「オレオレ証明書」はその名前からか、世間の風当たりが強い今日この頃です。「母さん助けて証明書」に改名すればイメージが改善するのでしょうか? OS ベンダーによる包囲網が狭まり、使用にあたっては肩身が狭いというか、どんどん実装が難しくなっているような気がします。
今回は、クライアント証明書を使った HTTPS 通信を MAUI でどう実現するか、Windows・Android・iOS でどう共通実装したかについて解説します。

いきなり解答

クライアント認証を受けダウンロード.cs
private HttpClientHandler _handler;
private HttpClient _request;
private ManualResetEvent _event = new ManualResetEvent(false);

init()
{
	X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
	store.Open(OpenFlags.ReadWrite);
	store.Add(new X509Certificate2("p12 ファイル"));
	store.Close();

	_handler = new HttpClientHandler()
	{
		ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
		ClientCertificateOptions = ClientCertificateOption.Automatic
	};
}

public bool download()
{
	_request = new HttpClient(_handler) { BaseAddress = new Uri("サーバー"), Timeout = TimeSpan.FromSeconds() };
	_event.Reset();
	downloadAsync();
	if (_event.WaitOne(ミリ秒))
	{
		return true;
	}
	_request.CancelPendingRequests();
	_request.Dispose();
	return false;
}

private async void downloadAsync()
{
	try
	{
		using (var stream = await _httpRequest.GetStreamAsync("ファイル名"))
		{
			using (var file = new FileStream("保存先"), FileMode.Create, FileAccess.Write, FileShare.None))
			{
				stream.CopyTo(file);
			}
		}
		_event.Set();
	}
	catch
	{
	}
	return;
}

実は、Windows では動作するのですが、Android では SSL 通信で例外が発生、iOS ではそもそも X509Store に追加できません。X509Store に追加しないで Manual モードでClientCertificates.Add()してもダメです。試行錯誤している中で確認したエラーメッセージから推測するに、おそらくオレオレ証明書だから例外が発生するのであって、きちんとした証明書なら問題なく動くのだと思います(実証はしてませんが…)。

Andoid では別途、鼻薬を嗅がせる必要あり

Android では接続先の信頼性判定に使う CA 証明書がオレオレだと、「この証明書は信頼できる認証機関のものではありません」と通信を拒否します。これの回避方法は公式ドキュメントにあります。
https://developer.android.com/training/articles/security-config?hl=ja

下記のように、接続先と対応する CA 証明書を列挙し、AndroidManifest.xmlに食わせます。オレオレ CA 証明書でも大丈夫です。既出のHttpClientを使った実装でも、これから紹介するHttpWebRequestを使った実装でも、必須の作業となります。

network_security_config.xml
<?xml version="1.0" encoding="utf-8" ?>
<network-security-config>
	<domain-config>
		<domain includeSubdomains="true">接続先</domain>
		<trust-anchors>
			<certificates src="@raw/(拡張子を除くファイル名)" />
			...
		</trust-anchors>
	</domain-config>
</network-security-config>

では、どうする?

HttpClientは諦めます。いつまで通用するかわかりませんがHttpWebRequestを使います。

クライアント認証を受けダウンロード(その2).cs
private HttpWebRequest _request;
private ManualResetEvent _event = new ManualResetEvent(false);

public bool download()
{
	_request = (HttpWebRequest)WebRequest.Create("サーバー");
	_request.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
	_request.ClientCertificates.Add(new X509Certificate2("p12 ファイル"));		
	_event.Reset();
	downloadAsync();
	if (_event.WaitOne(ミリ秒))
	{
		return true;
	}
	_request.Abort();
	return false;
}

private async void downloadAsync()
{
	try
	{
		using (var response = await _request.GetResponseAsync())
		{
			using (var stream = response.GetResponseStream())
			{
				using (var file = new FileStream("保存先"), FileMode.Create, FileAccess.Write, FileShare.None))
				{
				    stream.CopyTo(file);
				}
			}
		}
		_event.Set();
	}
	catch
	{
	}
	return;
}

(了)

Discussion