今天继续介绍WCF分布式安全开发实践(12):消息安全模式之自定义X509证书验证,Message_CustomX509Certificate_WSHttpBinding 。本文介绍的内容主要是:主要是消息安全模式的自定义证书身份验证方式,基于WSHttpBinding绑定协议的实现过程。主要内容:基本概念,然后是制作证书、服务端配置、客户端配置、总结。这里应该和Transport传输安全模式之自定义证书身份验证对应,但是消息安全模式这里不使用https。安全基于TLS,传输层安全连接。  
    和 WCF分布式安全开发实践(11):消息安全模式之Certificate身份验证:Message_Certificate_WSHttpBinding大部分过程相同。差别在于客户端证书的验证过程由我们自己提供。而不是有WCF框架自己完整。
【0】消息安全模式之自定义证书客户端身份验证:
       消息安全模式之自定义证书身份验证需要服务器需要一个有效的可用于TLS 加密和向客户端验证服务身份的 X.509 证书,并且客户端必须信任此服务器证书。而客户端同样要提供一个有效的证书,来证明自己的身份。 这里使用http协议。建议安全上下文以后,使用共享安全上下文对SOAP消息进行加密和签名。使用证书对客户端和服务进行身份验证。也就是客户端提供有效证书才可以访问此服务。

1.身份验证(服务器):提供证书,(使用 HTTP)用于初始会话协商和证明服务身份。
2.身份验证(客户端):客户端证书进行身份验证,自定义验证代码


   WCF消息安全模式之自定义证书身份验证的架构如下:

skywalking 生成es密钥_服务器

    

    客户端建立TLS安全上下文以后,会使用商定的密码对消息签名,客户端使用证书加密数据,服务端使用证书解密数据,保证数据的安全和机密性,消息签名放置被篡改。

   这里客户端提供的是有效的证书,服务器端使用自定义代码对客户端证书进行验证。而不是有WCF框架自动完成。

   下面是制作证书的过程,和传输安全模式的过程一样,这里直接使用相同证书制作工具,新启用端口8001。

【1】制作证书:

(1)使用makecert 工具:Microsoft Visual Studio 2008-->Visual Studio Tools-->Visual Studio 2008 命令提示行。

输入:makecert -sr localmachine -ss My -n CN=WCFServerPK -sky exchange -pe -r


输入:makecert -sr localmachine -ss My -n CN=WCFClientPK -sky exchange -pe -r。

-这里制作了连个证书,主要只使用一个WCFServerPK,可以到出密钥文件pfx,后续我们要导入到其他存储区,设置为信任的证书。WCFClientPK -是为以后文章准备的,也是可以设置为信任的证书。 (3)使用MMC建立证书控制单元查看证书的信息:

  开始--运行--MMC--控制台--添加删除单元--证书--当前用户和计算机各添加一个。能查看和管理CurrentUser和LocalMachine的证书。如图:

skywalking 生成es密钥_skywalking 生成es密钥_02


(4)导入证书到信任的人和信任的CA机构里。步骤如下:

    1.导出证书文件,带密钥的pfx文件。使用mmc,保存到桌面位置(方便查找)。这里记住你制作证书的密码。要使用。

    2.导入证书到信任的人。使用任务-导入向导--选择证书文件,导入即可。

    3.导入证书到信任的机构,使用任务-导入向导--选择证书文件,导入即可。这个证书就被信任了。

【3】服务端配置:

   服务器证书配置完成以后,我们来配置服务端相关的文件,这里简单。也可以使用代码来完成。

    (1)服务类定义:

    重复使用以前定义的服务类代码。 这里服务类就一个方法就是更具用户的name来打印调用时间,代码如下:  //1.服务契约

[ServiceContract(Namespace = "" 
  )]
    public interface    IWCFService
    {
        //操作契约   
           [OperationContract]
        string SayHello(string    name);

    }
    //2.服务类,继承接口。实现服务契约定义的操作   
       public class 
   WCFService : IWCFService
    {
        //实现接口定义的方法   
           public string SayHello(string 
   name)
        {
            Console.WriteLine("Hello! {0},Calling at {1} 
"   , name,DateTime.Now.ToLongTimeString());
            return "Hello! " +    name;
        }
    }

    (2)消息安全模式配置:
       使用消息安全模式,采用客户端证书身份验证策略,Message安全模式下的证书验证方式配置信息如下:


<wsHttpBinding>   
         <binding name="BindingConfigration">   
           <security mode="Message">   
             <transport clientCredentialType="None"/>   
             <message clientCredentialType="Certificate" negotiateServiceCredential="true" establishSecurityContext="true"/>   
           </security>   
         </binding>   
       </wsHttpBinding>


   这里允许启用安全协商和建立安全上下文。这个配置要应用到服务的终结点配置上。才会生效。
    (1)服务验证代码:
    证书制作完整以后,就需要来实现自定义用户名和密码的验证程序。这里要重写X509CertificateValidator类的 Validate(X509Certificate2 certificate)方法。具体代码如下:


//证书验证程序,这里可以访问证书数据库或者其他证书存储机构,来验证客户端证书的真伪。   
           public class    CustomX509CertificateValidator : X509CertificateValidator
        {
            public override void    Validate(X509Certificate2 certificate)
            {
                Console.WriteLine("Certificate Subject is :{0}"   , certificate.Subject);
                Console.WriteLine("Certificate Thumbprint is :{0}"   , certificate.Thumbprint);
                //This is the Client  Certificate Thumbprint,In Production,We can validate the Certificate With CA   
                   if (certificate.Thumbprint != "748e3f8d07f14750460244045e21633cf1f5b211"   )
                {

                    Console.WriteLine("CertificateValidatation is failed !{0}"   , certificate.Subject);
                    throw new SecurityTokenException("Unknown Certificate"   );
                }
                else   
                {
                    Console.WriteLine(   "CertificateValidatation is sucessfully !:{0}"   , certificate.Subject);
                }
            } 
        }


    这里客户端证书的指纹是748e3f8d07f14750460244045e21633cf1f5b211。如果客户端提供的证书信息不对,就直接抛出异常,验证失败,实际应用我们可以到CA证书机构或者证书数据库查询数据,来判定客户端证书的有效性。传输安全保证了用户名密码的机密性,而验证代码可以灵活地访问我们系统的数据库或者CA来判定客户端是否是有效的客户端。可以很好的保护WCF服务。与自定义用户名密码的方式一样,验证程序比较灵活。服务器不需要导入客户端证书。
    自定义客户端代码我们在配置文件里设置。代码如下:


<   clientCertificate  
  > 
  
                 <   authentication   certificateValidationMode 
  = 
  " 
  Custom 
  " 
    customCertificateValidatorType 
  = 
  " 
  WCFService.CustomX509CertificateValidator,WCFService 
  " 
  /> 
  
               </   clientCertificate 
  >


    (4)服务器证书使用:
        服务器端证书主要是在建立TLS连接会话的时候,证明服务端的身份合法性。
    在服务行为节点属性里配置使用证书WCFServerPK,其它设置采用默认方式。这里和WCF分布式安全开发实践(7):消息安全模式之匿名客户端:Message_None_WSHttpBinding 配置一样。具体代码如下:


< serviceBehaviors >

<   behavior name 
  = 
  " 
  WCFService.WCFServiceBehavior 
  " 
  > 
  
             <   serviceMetadata httpGetEnabled 
  = 
  " 
  true 
  " 
    
  /> 
  
             <   serviceDebug includeExceptionDetailInFaults 
  = 
  " 
  false 
  " 
    
  /> 
  
             <   serviceCredentials 
  > 
  
                 <   serviceCertificate  storeName 
  = 
  " 
  My 
  " 
    x509FindType 
  = 
  " 
  FindBySubjectName 
  " 
   findValue 
  = 
  " 
  WCFServerPK 
  " 
   storeLocation 
  = 
  " 
  LocalMachine 
  " 
  /> 
  
               <   clientCertificate  
  > 
  
                 <   authentication   certificateValidationMode 
  = 
  " 
  Custom 
  " 
    customCertificateValidatorType 
  = 
  " 
  WCFService.CustomX509CertificateValidator,WCFService 
  " 
  /> 
  
               </   clientCertificate 
  > 
  
             </   serviceCredentials 
  > 
  
           </   behavior 
  > 
  
       
         </   serviceBehaviors 
  >


    这里指定了服务端证书的查找位置和查找条件,我们证书存储在LocalMachine 个人区域。使用标题进行查找。如果相同标题,需要制定唯一的查找条件。保证查找证书的唯一性。否则会出异常。
   (5)这里我们不需要使用Https传输协议,直接使用http协议即可,服务终结点的配置信息如下:

<services>   
         <service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFService" >   
           <   endpoint 
          address="WCFService"    
          binding="wsHttpBinding"    
          bindingConfiguration="BindingConfigration"   
          contract   ="WCFService.IWCFService">   
           </endpoint>   
           <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />   
           <host>   
             <baseAddresses>   
               <add baseAddress="http://localhost:8001/"/>   
             </baseAddresses>   
           </host>   
         </service>   
       </services>

   我们的服务元数据终结点使用基地址:http://localhost:8001/。消息安全模式基本使用的都是相同的端口。

【4】客户端配置:

    这个过程和之前的传输安全模式下,参考WCF分布式安全开发实践(11):消息安全模式之Certificate身份验证:Message_Certificate_WSHttpBinding , 添加服务的过程一样。直接引用。

    (1)引用元数据:

    因为服务的元数据交换节点启用了Http协议,我们在客户端项目添加元数据地址http://localhost:8001/mex查找服务信息的时候,界面如下:

skywalking 生成es密钥_skywalking 生成es密钥_03

    

   继续就会添加完毕服务引用。过程和普通的添加服务元数据引用一样,会产生客户端相关代码文件。输入命名空间,现在我们点击Ok。等待完成即可。  

    (2)配置文件:

    客户端配置文件使用默认设置,主要是安全模式的设置要如下,与服务端匹配。使用证书验证方式。

bindings>   
               <wsHttpBinding>   
                   <binding name="WSHttpBinding_IWCFService">   
                       <security mode="Message">   
                           <transport clientCredentialType="None" proxyCredentialType="None"   
                            realm   ="" />   
                           <message clientCredentialType="Certificate" negotiateServiceCredential="true"   
                            algorithmSuite   ="Default" establishSecurityContext="true" />   
                       </security>   
                   </binding>   
               </wsHttpBinding>   
           </bindings>


    (3)客户端证书:
        这里我们要在配置文件里提供客户端的证书,也可以采用代码方式提供客户端证书。配置文件的方式比较简单。
直接在endpointBehaviors里设置以后,应用到终结点行为配置上就可以了。代码如下:


<behaviors>   
           <endpointBehaviors>   
             <behavior name="endpointBehavior">   
               <clientCredentials>   
                 <clientCertificate storeName="My"   
                                 x509FindType   ="FindBySubjectName"   
                                 findValue   ="WCFClientPK"   
                                 storeLocation   ="CurrentUser"/>   
               </clientCredentials>   
             </behavior>   
           </endpointBehaviors>   
         </behaviors>


    (4)测试代码:
      等待代码生成结束,我们这里就直接生成客户端代理类的实例来调用服务进行测试。这里客户端在调用服务以前,必须信任证书,这里我们使用了一段通用的代码,来建立TLS使用。这里会信任服务器的证书。代码如下:


public static class    Util
    {
        /// <summary>   
           ///    Sets the cert policy.
        /// </summary>   
           public static void    SetCertificatePolicy()
        {
            ServicePointManager.ServerCertificateValidationCallback
                       +=    RemoteCertificateValidate;
        }

        /// <summary>   
           ///    Remotes the certificate validate.
        /// </summary>   
           private static bool    RemoteCertificateValidate(
           object    sender, X509Certificate cert,
            X509Chain chain, SslPolicyErrors error)
        {
            // trust any certificate!!!   
               System.Console.WriteLine("Warning, trust any certificate"   );
            return true   ;
        }
    }


   客户端测试代码很简单,我们不需要提供客户端证书了,因为配置文件里已经设置完毕。接下来就是通过客户端代理来调用WCF服务。代码如下:


       

static void Main(string   [] args)
        {

            try   
            {
                Console.ForegroundColor    =    ConsoleColor.Green;
                WCFClient.ClientProxy.WCFServiceClient wcfServiceProxy = new WCFClient.ClientProxy.WCFServiceClient("WSHttpBinding_IWCFService"   );
                //通过代理调用SayHello服务   
                   string sName = "Frank Xu Lei  Message Certificate WSHttpBinding"   ;
                string sResult = string   .Empty;
                Util.SetCertificatePolicy();
                sResult =    wcfServiceProxy.SayHello(sName);
                Console.WriteLine("Returned Result is {0}"   , sResult);
            }
            catch    (Exception e)
            {
               Console.WriteLine("Exception : {0}"   , e.Message);
            }
            //For Debug   
               Console.WriteLine("Press any key to exit"   );
            Console.Read();
            
        }

    这里也可以使用代码来设置证书wcfServiceProxy.ClientCredentials.ClientCertificate.Certificate = new X509Certificate2(" WCFClientPK.pfx", "password"); WCFClientPK.pfx是导出的客户端证书的文件,包含密钥,密码为保护密码。

  (4)测试结果:

   启动宿主程序,然后启动客户端程序,客户端成功调用服务,宿主打印的消息。如图:

skywalking 生成es密钥_soap_04


【5】总结

     Windows Communication Foundation (WCF) 服务和客户端。WCF安全机制都是依赖现有的安全体系和框架来完成的。这里的消息安全模式下的客户端证书身份验证,其实没有太大的变化。就是使用证书来验证客户端的有效性,合法性。

   (1)服务器需要一个证明服务器身份和有效的可用于TLS传输层安全的 X.509 证书,并且客户端必须信任此服务器证书。

   (2)客户端提交证书的方式与传输安全之证书验证方式一样,服务器端需要提供证书,但是不需要httpcfg.exe设置。

   (3)初始协商需要服务器证书来建立TLS连接,协商完毕以后,建立共享安全上下文,这里使用商定的加密算法对SOAP消息进行加密和签名。

   (4)如果你启用协商,而不把服务器证书在客户端设置信任,导入信任的办法机构,会出现SOAP安全协商失败的异常。

   (5)和消息安全模式的客户端证书验证,过程基本一样。差别只是在于客户端证书有效性验证,前者使用的是WCF框架自定来验证,这里我们使用的是自定义验证程序,重新实现了CustomX509CertificateValidator : X509CertificateValidator的Validate(X509Certificate2 certificate)方法。这里我们可以方便的定义具体的验证策略,也可以把证书信息存在特定的数据库,或者直接代码从证书存贮区域获取证书信息进行验证。

   (6)参考代码: 

/Files/frank_xl/6.5.WCFServiceSecurityDemoFrankXuLei_Message_CustomX.509Certificate_WSHttpBinding.rar   

     以上就是消息安全模式下的自定义X509证书的验证过程。这个安全系列的Demo程序基本结束。我会再整理一个总结性的文章给《WCF分布式安全开发实践》做个总结。 这些例子都可以扩展,比如更换绑定,等等,但是开发过程基本类似。。有什么建议可以提出,欢迎留言交流~

参考文章:

1.WCF分布式安全开发实践(11):消息安全模式之Certificate身份验证:Message_Certificate_WSHttpBinding

2.WCF分布式安全开发实践(6):传输安全模式之自定义X509Certificate证书验证:Transport_X509Certificate_WSHttpBinding

3.http://social.microsoft.com/Forums/zh-CN/wcfzhchs/thread/e1aa7bea-90d8-41e6-b91b-7addba44f8e34.WSE3.0构建Web服务安全(2):非对称加密、公钥、密钥、证书、签名的区别和联系以及X.509 证书的获得和管理

【作      者】:Frank Xu Lei