受 SSL 保护的 RESTFul WCF 自托管服务

Posted

技术标签:

【中文标题】受 SSL 保护的 RESTFul WCF 自托管服务【英文标题】:SSL-protected RESTFul WCF Self-hosting service 【发布时间】:2020-03-05 16:38:27 【问题描述】:

总结

我正在尝试实现受 SSL 保护的 RESTFul WCF 服务,但出现以下错误并且通信失败。

making the HTTP request to ‘https://123.123.123.123:5000/TestService/PostMsg’. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server. on some customers machines.

谁能帮帮我? 最好的问候

尝试过的东西:

我已经成功地在没有 SSL 保护的情况下进行通信。

代码

服务实现

using System;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.ServiceModel.Web;

namespace TestService

    class Program
    
        static WebServiceHost host;

        static void Main()
        
            WebHttpBinding binding = new WebHttpBinding();
            binding.Security.Mode = WebHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
            binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;

            Uri uri = new Uri("https://localhost:5000/TestService");

            host = new WebServiceHost(typeof(TestService));
            ServiceEndpoint se = host.AddServiceEndpoint(typeof(ITestService), binding, uri);

            var behavior = new WebHttpBehavior();
            behavior.FaultExceptionEnabled = false;
            behavior.HelpEnabled = true;
            behavior.DefaultOutgoingRequestFormat = WebMessageFormat.Json;
            behavior.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
            se.EndpointBehaviors.Add(behavior);

            ServiceDebugBehavior debug = host.Description.Behaviors.Find<ServiceDebugBehavior>();
            debug.IncludeExceptionDetailInFaults = true;

            ServiceMetadataBehavior metad = new ServiceMetadataBehavior();
            metad.HttpGetEnabled = true;
            metad.HttpsGetEnabled = true;
            host.Description.Behaviors.Add(metad);

            var certificate = new X509Certificate2(@"D:\Work\TestService\ServerCert1.pfx", "paswd", X509KeyStorageFlags.UserKeySet);
            host.Credentials.ServiceCertificate.Certificate = certificate;
            host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;

            host.Open();
            Console.WriteLine(string.Format(null, "URL : 0", uri.ToString()));
            Console.WriteLine("Press <ENTER> to terminate");
            Console.ReadLine();
            host.Close();

        
    

界面

using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace TestService

    [ServiceContract]
    interface ITestService
    
        [OperationContract]
        [WebInvoke(Method = "POST"
                            , RequestFormat = WebMessageFormat.Json
                            , UriTemplate = "/PostMsg"
            )]
        MessageData PostMsg(MessageData msg);
    

    [DataContract]
    public class MessageData
    
        [DataMember]
        public string Name  get; set; 

        [DataMember]
        public int Gender  get; set; 

        [DataMember]
        public int Age  get; set; 

    


using System;

namespace TestService

    class TestService : ITestService
    
        public MessageData PostMsg(MessageData msg)
        
            Console.WriteLine(string.Format(null, "Recieved Name: 0, Gender:1, Age:2", msg.Name , msg.Gender , msg.Age));

            return new MessageData()
            
                Name = msg.Name,
                Gender = msg.Gender,
                Age = msg.Age + 1
            ;
        

    

客户端实现

using System;
using System.Windows.Forms;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.ServiceModel.Web;
using TestService;

namespace TestClient

    public partial class Form1 : Form
    
        WebChannelFactory<ITestService> cf = null;
        ITestService channel = null;

        public Form1()
        
            InitializeComponent();
        

        private void Form1_Load(object sender, EventArgs e)
        
            listBox1.HorizontalScrollbar = true;

            Uri uri = new Uri("https://123.123.123.123:5000/TestService");
            EndpointAddress endpointAddress = new EndpointAddress(uri);

            cf = new WebChannelFactory<ITestService>(uri);
            WebHttpBinding binding = cf.Endpoint.Binding as WebHttpBinding;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
            binding.Security.Mode = WebHttpSecurityMode.Transport;

            var behavior = new WebHttpBehavior();
            behavior.FaultExceptionEnabled = false;
            behavior.HelpEnabled = true;
            behavior.DefaultOutgoingRequestFormat = WebMessageFormat.Json;
            behavior.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
            cf.Endpoint.Behaviors.Add(behavior);

            var clientCertificate = new X509Certificate2(@"D:\Work\TestService\ServerCert1.pfx", "pswd", X509KeyStorageFlags.UserKeySet);
            cf.Credentials.ClientCertificate.Certificate = clientCertificate;
            cf.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;

            channel = cf.CreateChannel();

        

        private void button1_Click(object sender, EventArgs e)
        
            MessageData msg = new MessageData()
            
                Name = "Taro",
                Gender = 1,
                Age = 3
            ;
            MessageData rtn = channel.PostMsg(msg);
            listBox1.Items.Insert(0, string.Format("Name:0, Gender:1, Age2 ", rtn.Name, rtn.Gender, rtn.Age));

        
    

用于创建 pfx 文件的脚本。

@echo ----------------------------------------------
@echo     Script for creating self certificate
@echo ----------------------------------------------
@set "TOOL_DIR=E:\Windows Kits\10\bin\10.0.18362.0\x86"
@if not exist "%TOOL_DIR%" (
    @echo Tools do not exists. %TOOL_DIR%
    @goto ERROR_EXIT
)
@set "PATH=%TOOL_DIR%;%PATH%"

@set "WORK_DIR=D:\Work\TestService"
@if not exist %WORK_DIR% ( 
    @echo Work folder does not exist. %WORK_DIR%
    @goto ERROR_EXIT
)
cd  /d %WORK_DIR%

@openfiles > NUL 2>&1 
@if NOT %ERRORLEVEL% EQU 0 (
    @echo It is not being executed as an administor.
    goto ERROR_EXIT
)

@SET /P ANS="Create a certificate file. Are you sure (Y / N)?"
@if /i %ANS% NEQ y if /i %ANS% NEQ Y goto ERROR_EXIT

del %WORK_DIR%\*.*
@echo;

@echo Create Self-Signed Certificate
makecert -n "CN=ServerCN1" -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv ServerCert1.pvk ServerCert1.cer -cy authority -b 11/06/2019 -e 12/31/2019
@echo;

@echo Create Software Publisher Certificate File
cert2spc ServerCert1.cer ServerCert1.spc
@echo;

@echo Create Personal Information Exchange File
pvk2pfx -pvk ServerCert1.pvk -spc ServerCert1.spc -po pswd -pfx ServerCert1.pfx -f
@echo;

:ERROR_EXIT
@SET /P ANS="Finished."

必需

我不想使用 IIS,因为我认为它有很多设置。相反,我在“System.Security.Cryptography.X509Certificates”命名空间中使用 X509Certificate2 类的 X509Certificate2 方法。 (.NETFramework\v4.7.2\System.dll)

此时服务器和客户端在同一台计算机上。

Windows Defender 防火墙的端口已打开。

一些图片用于了解我在做什么。

https://i.stack.imgur.com/MCFl9.png

https://i.stack.imgur.com/DgNid.png

【问题讨论】:

【参考方案1】:

使用证书来保护通信需要我们使用以下命令将证书绑定到特定的计算机端口。

Netsh http 添加 sslcert ipport=0.0.0.0:5000 certash= 0102030405060708090A0B0C0D0E0F1011121314 appid= appid=00112233-4455-6677-8899-AABBCCDDEEFF

https://docs.microsoft.com/en-us/windows/win32/http/add-sslcerthttps://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate

当我们设置站点绑定时,此功能会在 IIS 中自动完成。

  WebHttpBinding binding = new WebHttpBinding();
            binding.Security.Mode = WebHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Cert

关于使用证书对客户端进行身份验证,请参考以下官方文档。 https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-with-certificate-authentication 通常有两点我们需要注意。

    1。我们应该确保服务证书和客户端证书都应该具有客户端认证和服务器认证的目的。

服务器身份验证 (1.3.6.1.5.5.7.3.1) 客户端身份验证 (1.3.6.1.5.5.7.3.2)

    2。我们最好在 Dotnet 框架 4.6.2 上构建项目,因为读取使用 PowerShell 证书创建的证书的私钥有问题。

如果有什么我可以帮忙的,请随时告诉我。

【讨论】:

1.我了解到证书有几种类型,具体取决于用途。现在,我将研究如何实现它。 2.我已经安装了 Dotnet 框架 4.6.2。谢谢你。 :) 服务端源码中X509Certificate2方法的第一个参数中,是否要指定客户端证书文件(用1.3.6.1.5.5.7.3.2创建)?而在客户端源码中,是否指定了服务器证书(使用1.3.6.1.5.5.7.3.1创建)? 是的,这些证书应该添加两个预期目的。我建议您使用 Powershell 创建证书,因为以下命令创建的证书默认包含两个预期用途。 New-SelfSignedCertificate -DnsName "mycomputername" -CertStoreLocation "cert:\LocalMachine\My" docs.microsoft.com/en-us/powershell/module/pkiclient/…

以上是关于受 SSL 保护的 RESTFul WCF 自托管服务的主要内容,如果未能解决你的问题,请参考以下文章

如何使用用户名/密码 + SSL 使用 WCF 配置安全 RESTful 服务

如何在 WCF 客户端中接受自签名 SSL 证书?

WCF 自签名证书在客户端上不受信任

WCF 安全绑定问题

Azure Active Directory 和 WCF 服务库示例

有时调用 Microsoft Azure 托管的 WCF 服务会导致“SSL/TLS 的信任关系”问题