博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[WCF安全系列]实例演示:TLS/SSL在WCF中的应用[SSL over TCP]
阅读量:7049 次
发布时间:2019-06-28

本文共 10702 字,大约阅读时间需要 35 分钟。

在接下来的系列文章中我们正是讨论关于身份认证的主题。在前面我们已经谈到了,WCF中的认证属于“双向认证”,既包括服务对客户端的认证(以下简称客户端认证),也包括客户端对服务的认证(以下简称服务认证)。客户端认证和服务认证从本质上并没有什么不同,无非都是被认证一方提供相应的用户凭证供对方对自己的身份进行验证。我们先来讨论服务认证,客户端认证放在后续的文章中。

在《》中,我们对TLS/SSL进行了简单的介绍。我们知道,客户端和服务在为建立安全上下文而进行的协商过程中会验证服务端的X.509证书如否值得信任。对于服务证书的验证实际上可以看成是一种服务认证,或者说TLS/SSL对证书的验证可以看成是WCF服务认证的一个环节。

目录

TLS/SSL与X.509证书
创建基于TLS/SSL的WCF服务
    创建X.509证书
    服务寄宿 
    服务调用
    改变证书认证模式

一、TLS/SSL与X.509证书

TLS/SSL是实现Transport安全模式的一种主要的方式,但不是唯一方式。对于所有基于HTTP的绑定(主要指BasicHttpBinding、WSHttpBinding和WS2007HttpBinding,而WSDualHttpBinding不支持Transport安全模式),如果选择了Transport或者Mixed安全模式,不论采用怎样的认证方式,底层的实现总是基于TLS/SSL(HTTPS)。

而对于NetTcpBinding来说,如果采用Transport安全模式,并且采用非Windows认证(客户端凭证类型选择None或者Certificate),最终的传输安全的实现也是基于TLS/SSL(SSL Over TCP)。如果选择Mixed安全模式,不论选择怎样的客户端凭证类型,WCF最终都会采用TLS/SSL来提供对传输安全的实现。也正是因为如此,在这两种情况下,你总是需要选择一个X.509证书作为服务的凭证。举个例子,对于如下的配置,终结点采用NetTcpBinding绑定,并且选择Transport安全模式,但是却采用匿名的认证方式(客户端凭证类型为None)。

1: 
2:   
3:     
4:       
5:         
6:           
7:         
8:       
9:     
10:   
11:   
12:     
13:       
14: binding="netTcpBinding" bindingConfiguration="transportTcpBinding"
15: contract="Artech.WcfServices.Contracts.ICalculator" />
16:     
17:   
18: 

在对服务进行寄宿时,会抛出如下图所示的InvalidOperationException异常,提示“未提供服务证书。请在 ServiceCredentials 中指定服务证书”。

作为服务凭证的证书通过服务行为来指定,对于WCF的安全体系来说,ServiceCredentials是个非常重要的对象,在本章后续文章中我们将反复地使用到它。对于服务凭证的指定,需要使用到ServiceCredentials的只读属性ServiceCertificate,该属性对应的类型为。X509CertificateRecipientServiceCredential对象实际上是对一个对象的封装,它定义了若干SetCertificate方法重载用以指定一个X.509证书作为服务的凭证。ServiceCredentials和X509CertificateRecipientServiceCredential的相关定义反映在如下所示的代码片断中。

1: public class ServiceCredentials : SecurityCredentialsManager, IServiceBehavior
2: {
3:     //其他成员
4:     public X509CertificateRecipientServiceCredential ServiceCertificate   { get; }
5: }
6: public sealed class X509CertificateRecipientServiceCredential
7: {
8:     //其他成员
9:     public void SetCertificate(string subjectName);
10:     public void SetCertificate(string subjectName, StoreLocation storeLocation, StoreName storeName);
11:     public void SetCertificate(StoreLocation storeLocation, StoreName storeName, X509FindType findType, object findValue);
12: 
13:     public X509Certificate2 Certificate { get; set; }
14: }

如果采用自我寄宿的方式,我们可以通过编程的方式来为寄宿的服务设置一个代表服务凭证的X.509证书。在下面给出的代码片断中,我们为服务设置一个主体名称为Jinnan-PC(我的机器名)的X.509证书,该证书是一个基于个人存储(Personal Store,通过StoreName.My表示)的本机(StoreLocation.LocalMachine)证书。

1: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3:     ServiceCredentials serviceCredentials = host.Description.Behaviors.Find
();
4:     if (null == serviceCredentials)
5:     {
6:         serviceCredentials = new ServiceCredentials();
7:         host.Description.Behaviors.Add(serviceCredentials);
8:     }
9:     serviceCredentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "Jinnan-PC");
10:     host.Open();
11:     ...
12: }

当然,我们依旧推荐采用配置的方式进行服务凭证的设置。对于上面一段设置服务证书的代码,我们可以通过下面的一段配置来代替。

1: 
2:   ...
3:   
4:     
5:       
6: binding="netTcpBinding" bindingConfiguration="transportTcpBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
7:     
8:   
9:   
10:     
11:       
12:         
13:           
14: x509FindType="FindBySubjectName" findValue="Jinnan-PC" />
15:         
16:       
17:     
18:   
19: 

对于采用基于TLS/SSL的Transport安全模式,对服务证书的验证方式会因为绑定类型的不同而具有小小的差异。

二、创建基于TLS/SSL的WCF服务

接下来我们会通过一个简单的例子来演示如何在WCF服务中使用基于TLS/SSL的Transport安全。该实例会涉及两种不同的绑定类型(WS2007HttpBinding和NetTcpBinding)和寄宿方式(自我寄宿和IIS寄宿)。

我们还是采用惯用的计算服务的例子,演示实例的解决方式具有右图所示的结构。Contract和Services为两个类库项目,分别用于定义服务契约和实现契约的服务类型。而Hosting和Client为两个控制台应用,前者用于进行服务寄宿(自我寄宿),后者用于模拟客户端程序。下面的代码片断代码了分别定义在Contracts和Services项目中的服务契约接口ICalculator和具体的服务类型CalculatorService。

1: using System.ServiceModel;
2: namespace Artech.WcfServices.Contracts
3: {
4:     [ServiceContract(Namespace = "http://www.artech.com/")]
5:     public interface ICalculator
6:     {
7:         [OperationContract]
8:         double Add(double x, double y);
9:     }
10: }
11: 
12: using Artech.WcfServices.Contracts;
13: namespace Artech.WcfServices.Services
14: {
15:     public class CalculatorService : ICalculator
16:     {
17:         public double Add(double x, double y)
18:         {
19:             return x + y;
20:         }
21:     }
22: }

创建X.509证书

由于TLS/SSL需要通过协商的方式生成一个用于消息签名和加密的会话密钥,而会话密钥的交换依赖一个X.509证书以确保安全。所以我们首要的任务是需要得到一个X.509证书,这样一个证书可以直接借助于MakeCert工具,通过命令行的方式创建一个主体名称为Jinnan-PC(我个人的机器名,你需要替换成你本机的名称)的证书。为了方便,我们在测试的时候倾向于创建自签名证书,即证书授予者和颁发者身份合二为一。不过为了演示证书正常的信任链,我们不采用这种方式。所以我们需要通过运行如下的命令行先创建一个CA证书。该CA证书本身是自签名的(对应于-r命令行开关)

1: Makecert -n "CN=RootCA" -r -sv C:\RootCA.pvk C:\RootCA.cer

上面的命令行在执行的过程中,会弹出两个用于输入密码的对话框。你需要输入相应的密码用以包括生成的两个文件,一个是包含私钥的文件RootCA.pvk,另一个是证书文件RootCA.cer,它们都保存在C盘根目录下。

然后通过如下的命令行创建一个主题名称为Jinnan-PC(我的机器名,你需要换成你的机器名或者本机影射的Host Name)的证书,并以上面创建证书对应的CA(RootCA)作为该证书的颁发者(-ic C:\RootCA.cer -iv C:\RootCA.pvk)。该证书最终自动保存到本机(-sr LocalMachine)的个人存储区(-ss My)。而-pe表示证书的私钥可以被导出。-sky表示密钥的类型或者作用,具有两个选项signature和exchange,前者用于数字签名,后者用于加密和密钥交换,这里选用exchange。

1: Makecert -n "CN=Jinnan-PC" -ic C:\RootCA.cer -iv C:\RootCA.pvk -sr LocalMachine -ss My -pe -sky exchange

服务寄宿

我们先使用NetTcpBinding作为绑定,在Hosting项目中定义如下的配置。从配置中我们可以看到:寄宿的CalculatorService服务唯一的终结点使用了Transport模式的NetTcpBinding绑定。该绑定的客户端凭证类型为None,意味着接受匿名客户端。通过命令行生成和存储的X.509证书通过服务行为的方式被设置成寄宿服务的凭证。

1: 
2: 
3: 
4:   
5:     
6:       
7:         
8:           
9:         
10:       
11:     
12:   
13:   
14:     
15:       
16: binding="netTcpBinding" bindingConfiguration="transportTcpBinding"
17: contract="Artech.WcfServices.Contracts.ICalculator" />
18:     
19:   
20:   
21:     
22:       
23:         
24:           
25: x509FindType="FindBySubjectName" findValue="Jinnan-PC" />
26:         
27:       
28:     
29:   
30: 
31: 

通过上面的配置,我们创建的X.509证书通过ServiceCredentials服务行为被指定为服务的凭证。此外还有一点值得注意的是:终结点地址采用了没有使用localhost和127.0.0.1,而是直接使用了机器名(Jinnan-PC),至于为什么需要这么做,在后续的内容中你会找到答案。而对于寄宿服务的程序,我们力求简洁,在Main方法中仅仅包括如下的代码。

1: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3:     host.Open();
4:     Console.Read();
5: }

服务调用

然后我们在Client项目中定义如下的客户端配置,用于进行服务调用的终结点的NetTcpBinding具有与服务端相同的配置。客户端通过如下一段简单的代码进行服务的调用。

1: 
2: 
3:   
4:     
5:       
6:         
7:           
8:             
9:           
10:         
11:       
12:     
13:     
14:       
15: binding="netTcpBinding" bindingConfiguration="transportTcpBinding"
16: contract="Artech.WcfServices.Contracts.ICalculator" />
17:     
18:   
19: 

服务调用程序:

1: using (ChannelFactory
channelFactory = new ChannelFactory
("calculatorService"))
2: {
3:     ICalculator calculator = channelFactory.CreateChannel();
4:     Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2)); ;
5: }
6: Console.Read();

完成所有编程和配置工作之后,我们先后启动Hosting和Client这两个控制台程序,你会发现服务并不能正常地调用,而是抛出如下图所示的SecurityNegotiationException异常,提示服务证书不受信任。

改变证书认证模式

之所以会抛出这样的异常,原因在于:WCF默认采用ChainTrust的模式对服务证书进行验证。该认证模式要求服务证书的颁发机构链必须在客户端的“受信任根证书颁发机构(Trusted Root Certification Authorities)”。为了解决这个问题,我们具有如下两种“解决方案”:

  • 将服务证书的颁发机构纳入到受信任根证书颁发机构中。你可以通过MMC的证书管理单元的导出/入功能将颁发机构的证书(C:\RootCA.cer)导入到受信任根证书颁发机构存储区中。但是不幸的是,由于CA证书是通过MakeCert.exe创建的,即使导入到受信任根证书颁发机构存储区,它也不能作为受信任的CA;
  • 通过System.ServiceModel.Description.ClientCredentials这个终结点行为改变默认的认证模式。

ClientCredentials和之前提到的ServiceCredentials是两个相对的“行为”类型,前者是使用在客户端的终结点行为,后者则是使用在服务端的服务行为。在本章后续的内容中,我们还将不断的使用到它们。现在我们先看讨论一下如何通过ClientCredentials来改变客户端对服务证书的认证模式。

首选你可以通过通过ChannelFactory的Credentials属性得到ClientCredentials对象。ClientCredential具有一个类型为的ServiceCertificate只读属性表示服证书。证书的认证行为定义在X509CertificateRecipientClientCredential的Authentication只读属性中,该属性的类型为。我们通过X509ServiceCertificateAuthentication的CertificateValidationMode属性设置相应的证书认证模式。关于服务证书认证模式涉及到的应用编程接口反映在如下所示的代码片断中。

1: public abstract class ChannelFactory
2: {
3:     //其他成员
4:     public ClientCredentials Credentials { get; }
5: }
6: public class ClientCredentials : SecurityCredentialsManager, IEndpointBehavior
7: {
8:     //其他成员
9:     public X509CertificateRecipientClientCredential ServiceCertificate { get; }
10: }
11:  public sealed class X509CertificateRecipientClientCredential
12: {
13:     //其他成员
14:     public X509ServiceCertificateAuthentication Authentication { get; }
15:  }
16:  public class X509ServiceCertificateAuthentication
17:  {
18:      //其他成员
19:      public X509CertificateValidator CustomCertificateValidator { get; set; }
20:      public X509CertificateValidationMode CertificateValidationMode { get; set; }
21:  }

证书认证模式通过枚举表示,它具有如下五个选项:None、PeerTrust、ChainTrust、PeerOrChainTrust和Custom。选择None意味着无需认证,而ChainTrust则要求证书的颁发机构必须是“受信任根证书颁发机构”存储区,而PerTrust要求证书本身(不是CA证书)存在于“受信任的个人(Trusted People)”存储区。如果这些认证模式不能满足你的需求,你还可以选择Custom。在这种情况下,你需要通过继承抽象类自定义验证规则,并将验证逻辑定义在抽象方法Validate中。最终将自定义的X509CertificateValidator赋给X509ServiceCertificateAuthentication的CustomCertificateValidator属性。X509CertificateValidationMode和X509CertificateValidator的定义如下。

1: public enum X509CertificateValidationMode
2:  {
3:      None,
4:      PeerTrust,
5:      ChainTrust,
6:      PeerOrChainTrust,
7:      Custom
8:  }
9: public abstract class X509CertificateValidator
10: {
11:     //其他成员
12:     public abstract void Validate(X509Certificate2 certificate);
13: }

对于本例来说,我们创建的证书既不再受信任根证书颁发机构存储区,也不在受信任的个人存储区。如果我们不愿意自定义X509CertificateValidator,可以通过如下的代码选择None模式以避免异常的发生。

1: using (ChannelFactory
channelFactory = new ChannelFactory
("calculatorService"))
2: {
3:     channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
4:     ICalculator calculator = channelFactory.CreateChannel();
5:     Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2)); ;
6: }
7: Console.Read();

我们也可以通过配置的方式来对ClientCredentials这个终结点行为进行相应的设置,通过上面这段程序对服务证书验证模式的设置与下面的这段配置在功能上是等效的。

1: 
2:  ...
3:   
4:     
5: address="net.tcp://jinnan-PC/calculatorservice" binding="netTcpBinding" bindingConfiguration="transportTcpBinding"
6: contract="Artech.WcfServices.Contracts.ICalculator" />
7:   
8:   
9:     
10:       
11:         
12:           
13:             
14:           
15:         
16:       
17:     
18:   
19: 

通过终结点行为ClientCredentials改变服务证书认证不仅仅可以适用于非HTTPS下的Transport安全模式,同时适用于Message安全模式。但是当我们采用HTTPS的时候,我们需要采用另外一种改变证书认证模式的方式,详情请关注下篇。

作者:蒋金楠
微信公众账号:大内老A
微博:
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号
蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
你可能感兴趣的文章
android listView嵌套gridview的使用心得
查看>>
[ES7] Descorator: evaluated & call order
查看>>
安卓动态调试七种武器之离别钩 – Hooking(上)
查看>>
从P6 EPPM 8 R3 到P6 EPPM 16 R1 有哪些改变?
查看>>
Android Studio2.0 教程从入门到精通Windows版 - 安装篇
查看>>
Linux 系统磁盘满处理方法
查看>>
Java HashMap Demo
查看>>
yaml官方介绍
查看>>
three.js模型
查看>>
网络流24题 餐巾计划问题
查看>>
基于 Android NDK 的学习之旅-----序言
查看>>
InnoDB recovery过程解析
查看>>
鼓浪屿
查看>>
alloc_skb申请函数分析
查看>>
WPF PRISM开发入门二(Unity依赖注入容器使用)
查看>>
使用 data-* 属性来嵌入自定义数据:
查看>>
炒股的常见技术指标
查看>>
工控随笔_07_西门子_WinCC利用命令行实现操作log日志
查看>>
解决MySQL报错The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents ........
查看>>
HUST 1017 Exact cover
查看>>