iOSのアプリでクライアント認証が必要だったので四苦八苦。
なんとか共通部で実装を試みるも、WebRequsetHandlerは存在せず、Certificate周りのメソッドを無理矢理上書きしたハンドラを作るも動いてくれず。
結局、iOS側の実装でNSUrlSessionの実装をゴリゴリ書くことに。
以下の処理をDependencyServiceとして実装。
using System;
using Xamarin.Forms;
using Hoge;
using Hoge.iOS;
using Foundation;
using Security;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
[assembly: Dependency(typeof(DeviceService))]
namespace Hoge.iOS
{
public class DeviceService : Hoge.IDeviceService
{
// HttpResultは適当な自作クラスです
public async Task<HttpResult> Get(string urlString)
{
var url = new NSUrl(urlString);
var request = new NSUrlRequest(url);
var myConfig = NSUrlSessionConfiguration.DefaultSessionConfiguration;
var session = NSUrlSession.FromConfiguration(myConfig, new MySessionDelegate(), new NSOperationQueue());
var task = await session.CreateDataTaskAsync(request);
if (task.Response == null)
{
return null;
}
else
{
return new HttpResult()
{
StatusCode = (int)((NSHttpUrlResponse)task.Response).StatusCode,
Body = task.Data.ToString(NSStringEncoding.UTF8).ToString()
};
}
}
public class MySessionDelegate : NSUrlSessionDelegate
{
public override void DidReceiveChallenge(
NSUrlSession session,
NSUrlAuthenticationChallenge challenge,
Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
if (challenge.PreviousFailureCount > 0)
{
completionHandler(NSUrlSessionAuthChallengeDisposition.CancelAuthenticationChallenge, null);
}
else if (challenge.ProtectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodServerTrust")
{
// 決められたホストにしか繋がない前提
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, CreateCredential());
}
else if (challenge.ProtectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodClientCertificate")
{
// 決められたホストにしか繋がない前提
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, CreateCredential());
}
}
private NSUrlCredential CreateCredential()
{
var identityRef = SecIdentity.Import(App.GetApp().GetCert());
//SecCertificate cert = new SecCertificate(App.GetApp().GetCert());
var credential = new NSUrlCredential(identityRef, null, NSUrlCredentialPersistence.None);
return credential;
}
}
}
}
アプリ埋め込みの証明書をロードする処理をAppに実装
証明書は秘密鍵付きで、ビルドタイプをEmbeddedResouceとして設定。
public X509Certificate2 GetCert()
{
X509Certificate2 cert = new X509Certificate2();
using (Stream CertStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "証明書ファイル.p12"))
{
byte[] RawBytes = new byte[CertStream.Length];
for (int Index = 0; Index < CertStream.Length; Index++)
{
RawBytes[Index] = (byte)CertStream.ReadByte();
}
cert.Import(RawBytes, "パスワード", X509KeyStorageFlags.DefaultKeySet);
}
return cert;
}