WCF の BasicHttpBinding を使ったセルフホストで HTTPS を構成してみた

はじめに

WCFHTTPS を構成できるか試すことになった。 しかも IIS を使わず、セルフホストで。

サービスコントラクトの定義

using System.ServiceModel;

namespace WcfHttpsSample.Shared
{
    [ServiceContract]
    public interface ISampleService
    {
        [OperationContract]
        string Echo(string message);
    }
}

WCF サービスの実装

using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using WcfHttpsSample.Shared;

namespace WcfHttpsSample.Server
{
    class Program
    {
        static void Main(string[] args)
        {
            var baseAddress = new Uri($"https://{Dns.GetHostEntry("").HostName}:8080");
            var host = new ServiceHost(
                typeof(SampleService),
                baseAddress);

            // https で待ち受けられるようにする
            var binding = new BasicHttpBinding();
            binding.Security.Mode = BasicHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

            host.AddServiceEndpoint(
                typeof(ISampleService),
                binding,
                "Sample");

            host.Open();

            Console.WriteLine("Enter で終了");
            Console.ReadLine();

            host.Close();
        }
    }

    public class SampleService : ISampleService
    {
        public string Echo(string message) => message;
    }
}

WCF クライアントの実装

using System;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using WcfHttpsSample.Shared;

namespace WcfHttpsSample.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            // 自己署名証明書を使って試す場合は証明書の検証をしない
            ServicePointManager.ServerCertificateValidationCallback =
                new RemoteCertificateValidationCallback((a, b, c, d) => true);

            var endpointAddress = new EndpointAddress(
                $"https://{Dns.GetHostEntry("").HostName}:8080/Sample");

            // https で通信できるようにする
            var binding = new BasicHttpBinding();
            binding.Security.Mode = BasicHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

            var channel = ChannelFactory<ISampleService>.CreateChannel(
                binding,
                endpointAddress);

            var reply = channel.Echo("Hello World!");
            Console.WriteLine(reply);

            ((IChannel)channel).Close();
            Console.WriteLine("Enter で終了");
            Console.ReadLine();
        }
    }
}

SSL 証明書をインポート

HTTP.sys から見られるよう、ローカルマシンの個人にインポートする。 ローカルマシンにインストールしておかないと、SSL 証明書をポート番号にバインドできない。 そのせいで数時間ハマった。

SSL 証明書をポート番号にバインド

このままだと、クライアントからアクセスした際に下記のような例外が発生する。

System.ServiceModel.CommunicationException: 'https://ホスト名:8080/Sample に対する HTTP 要求の発行中にエラーが発生しました。 この原因としては、HTTPS ケースの HTTP.SYS でサーバー証明書が正しく構成されていないこと、 またはクライアントとサーバーの間でセキュリティ バインドが整合していないことが考えられます。

下記のコマンドを実行して、SSL 証明書をポート番号にバインドする必要がある。

netsh http add sslcert ipport=0.0.0.0:8080 certhash=<証明書のサムプリント> appid={<適当な Guid>}

appid は適当な GUID で良いみたい。

実行結果

f:id:griefworker:20210423103301p:plain

おわりに

早く gRPC に移行したい。