前几天成功建立起了一套基于证书的安全的WCF服务,很兴奋,现在做一下总结,希望大侠们指点,也希望能帮助到一些菜鸟,哈哈。
一、安全通信的基本过程
这里只做定性的理论上的分析,因为再详细一点的我也不懂。一个通信分为客户端A和服务端B,A发送请求,B响应请求。安全通信必须满足3个方面的要 求,1是保密性,即数据要经过加密,防止第三者偷窥到。2是防篡改(不知道用完整性来表示合适不),即保证B收到的数据与A发出的数据是一样的,3是抗否 认,即如果A向B发送了数据,要从技术上保证A没办法抵赖说A没发,或者说A发的与B收到的不一样。2和3从某种意义上是同义词,因为既然没人能冒充A发 数据,那么A就不能抵赖。
二、PKI系统
PKI系统由信息发送方,接收方,和CA(证书认证中心)3部分组成。PKI系统可以满足以上安全通信的所有要求。
发送方A和接收方B都有一对密钥,A有公钥A和私钥A,B有公钥B和私钥B,这些密码是由CA签发的,A和B都信任CA,信任的关系是建立在证书链 的基础上的,CA有自己的根证书Root(包含公钥Root和私钥Root),A和B的密钥实际上是由CA根据自己的密钥所签发的子密钥。PKI系统实现 了这样一个逻辑,就是如果一个PC信任一个Root证书,那么它也信任由Root签发的子证书。这一逻辑由系统软件(比如浏览器、WCF框架)所实现,我 们不必实现和管理此逻辑。所以,A和B机里的受信任的根证书区里都要包含Root的公钥,此外A和B都有自己的公钥和私钥,因为A和B都信任Root,所 以这样配置下来,A就会信任B,B也会信任A。
(1)发送信息I之前,A首先生成某种对称加密算法(比如DES,AES)的密钥Q,Q是用来加密信息I的,因为对称加密比非对称加密快很多,所以虽然我们可以用私钥A来对I加密,但却不这么做。
(2)A用公钥B对Q加密,生成Q’。
(3)A用Q对I加密,生成I’。
(4)A用私钥A做签名。具体过程是:先对(Q’+I’)做哈希(MD5、SHA1等)得S,再用私钥A对S做加密,得S’。
(5)A把(Q’+I’+S’)发给B。
(6)B用公钥A对签名做验证。具体过程是:先对((Q’+I’)做哈希(MD5、SHA1等)得S,再用公钥A对S’做解密得S’’,看S是否等于S’’,相等则有效,不等则验证失败。
(7)B用私钥B对Q’做解密得Q。
(8)用Q对I’做解密,得I。至此,A完成了向B发送I,B完成了接收I。
在第5步,信息流传于公网上,所以(Q’+I’+S’)可能被黑客X所截获,但我们可以证明,X不可能获得有用的信息(即保密性),X也不能篡改I并发给B(防篡改,检测篡改),A也不能抵赖(抗否认):
1. X要想知道I,则必须先知道Q,但它只知道Q’,所以它还要知道私钥B才能得到Q,但这是不可能的,因为只有B和CA才知道私钥B,而CA是绝对公正和安全的,B又不会把私钥B告诉别人。
2. 如果第(6)步验证成功,我们可以断定I没有被篡改过。因为假如I被篡改过,则签名S’经过公钥A解密后得的S’’,一定不会等于(Q’+I’)的哈希值S,而这与已知的条件不符,所以是不可能的。
3. 假如A要想否认自己发过I,则说明A可能把私钥A泄露给了别人Y,Y就是坏蛋。如果A说没有告诉别人,则A就在说谎,因为签名S’只有A才能生成。所以A不能抵赖。
三、一个Case
有3台PC(W,Y,P),W为客户端,W请求Y,Y又请求P,那么Y即是服务端又是客户端,P是服务端,Y和P上布署的有WCF。
要保证这三台PC间的安全通信还必须有个CA,我们可以自己架设,也可以买第三方的服务。在开发阶段,我们可以用makecert来模拟CA签发证书的过程。
具体过程如下:
(1)生成根证书
makecert -n "CN=TempCA" -r -sv TempCA.pvk TempCA.cer
TempCA就是模拟的Root证书
(2)生成W,Y,P子证书
makecert -n "CN=W" -iv TempCA.pvk -ic TempCA.cer -sv W.pvk W.cer -pe -sky exchange
pvk2pfx.exe -pvk W.pvk -spc W.cer -pfx W.pfx
==========================================================
makecert -n "CN=Y" -iv TempCA.pvk -ic TempCA.cer -sv Y.pvk Y.cer -pe -sky exchange
pvk2pfx.exe -pvk Y.pvk -spc Y.cer -pfx Y.pfx
==========================================================
makecert -n "CN=P" -iv TempCA.pvk -ic TempCA.cer -sv P.pvk P.cer -pe -sky exchange
pvk2pfx.exe -pvk P.pvk -spc P.cer -pfx P.pfx
(3)导入证书
把TempCA.cer导入W,Y,P的受信任的根证书区,把各自的pfx导入各自的个人证书区,这样每台机器都有2个证书,一个根证书,一个自己的证书。
(4)创建WCF
首先在P上创建WCF服务,再在Y上创建WCF服务并引入P的服务,再在W上创建客户端引入Y的服务。
(5)配置安全选项
W上的配置类似于:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="ServerBinding">
<security mode="Message">
<message clientCredentialType="Certificate" negotiateServiceCredential="true" algorithmSuite="Default" establishSecurityContext="true"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://Y/Test.svc" binding="wsHttpBinding" bindingConfiguration="ServerBinding" contract="WcfClients.ITest" name="WSHttpBinding_ITest" behaviorConfiguration="TestEndpointBehavior">
<identity>
<dns value="Y"/>
</identity>
</endpoint>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="TestEndpointBehavior">
<clientCredentials>
<clientCertificate findValue="W" storeLocation="LocalMachine" x509FindType="FindBySubjectName" />
<serviceCertificate>
<authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
简单解释一下:
binding节配置了消息的传输方式和安全策略,并配置有name,在后面的endpoint里可以引用。服务端和客户端的binding要一致。
client节点代表客户端引用了哪些endpoint,每个endpoint的binding是什么。
endpointBehavior节点是一种扩展,针对本例,只需要配置客户端需要的具体身份凭据。服务端和客户端都可以定义 endpointBehavior,它是针对endpoint的。另外还有针对service的serviceBehavior,它只需配置在服务端,客 户端不需要。
==========================================================
Y的配置:
<system.serviceModel>
<client>
<endpoint address="http://P/Service1.svc" binding="wsHttpBinding" bindingConfiguration="ServerBinding" contract="IService1" name="WSHttpBinding_IService1" behaviorConfiguration="ServiceBehavior">
<identity>
<dns value="P" />
</identity>
</endpoint>
</client>
<bindings>
<wsHttpBinding>
<binding name="ServerBinding">
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="WcfSample.TestBehavior" name="WcfSample.Test">
<endpoint address="" binding="wsHttpBinding" contract="WcfSample.ITest" bindingConfiguration="ServerBinding">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WcfSample.TestSecureBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<serviceCertificate storeName="My"
storeLocation="LocalMachine"
x509FindType="FindBySubjectName"
findValue="TomYang-PC"/>
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" />
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="ServiceBehavior">
<clientCredentials>
<clientCertificate storeName="My"
storeLocation="LocalMachine"
x509FindType="FindBySubjectName"
findValue="Y"/>
<serviceCertificate>
<authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Y的配置之所以如此之长,是因为它既要配置服务端,又要配置客户端。其中的service节和serviceBehavior节是与W的对应之处,也就是服务端需要什么样的凭据,客户端就要提供,反过来,客户端需要服务端提供什么样的凭据,服务端也要提供相应的配置。
==========================================================
P的配置:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="ServerBinding">
<security>
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="Service1" behaviorConfiguration="Service1Behavior">
<endpoint address="" binding="wsHttpBinding" contract="IService1" bindingConfiguration="ServerBinding">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" />
</clientCertificate>
<serviceCertificate findValue="P" storeName="My" x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
OK, 所有代码与配置均写好了,还有要注意的一点是防火墙的问题,假如你所做的WCF使用了被防火墙屏蔽的端口时,你需要配置一下防火墙,方法可以是添加例外的 应用程序,或者添加端口。还有元数据的配置(即address为mex的那些东东),在正式的生产环境中是需要去掉的,它们可能会泄露系统敏感信息。
四、剩余的工作
这个Case只做到了安全通信,没有实现更详细的控制,比如身份识别(服务端收到请求后如何知道请求者是谁),权限控制(如何把一个用户映射到角 色,某个功能只对某些用户名角色开放)。身份识别可以使用证书的thumbprint,权限控制可以用代码访问安全性设置。这方面还没深入了解,有谁有这 方面的经验可以分享啊?
对于有些应用,比如不需要太详细的控制,只要求实现两点间的安全通信即可,那么这个Case已经够用了。