2009-05-30 17 views
6

He intentado que la seguridad de WCF funcione para mi proyecto, y he tenido poca suerte. Intento crear un servicio que use net.tcp como el enlace, y envía mensajes y seguridad de transporte. La seguridad de los mensajes se realiza utilizando el nombre de usuario y la contraseña, y la seguridad del transporte se realiza (¡supuestamente!) Utilizando certificados.WCF Transport Security usando Certificates está ignorando la confianza de la cadena

Para mis pruebas de desarrollo, creé mi propia autoridad de certificación y coloqué este certificado en la tienda de confianza de mi computadora (LocalMachine). Luego creé dos certificados, cada uno firmado por mi autoridad de certificación, uno para el servicio a usar y otro para que la aplicación cliente use. Coloqué ambos en la tienda Personal (Mi) en LocalMachine. Luego, para las pruebas creé un certificado aleatorio que no fue firmado por mi autoridad de certificación (y por lo tanto no es de confianza) y lo coloqué en la tienda Personal en LocalMachine. Usé makecert para crear estos certificados.

Luego configuré la aplicación cliente que se conecta al servicio para usar el certificado inválido no confiable como su certificado de cliente. El servicio está configurado (supuestamente) para verificar los certificados del cliente utilizando la confianza de la cadena. Sin embargo, este cliente puede conectarse y hablar con éxito con el servicio. Debería ser rechazado, porque su certificado no es de confianza.

No sé qué está causando este comportamiento, por lo que les presento el problema para que vean qué es lo que hace. Aquí están mis configuraciones WCF: conf

Servicio:

<system.serviceModel> 
    <services> 
     <service behaviorConfiguration="DHTestBehaviour" name="DigitallyCreated.DHTest.Business.DHTestBusinessService"> 
      <endpoint address="" binding="netTcpBinding" contract="DigitallyCreated.DHTest.Business.IDHTestBusinessService" bindingConfiguration="DHTestNetTcpBinding" bindingNamespace="http://www.digitallycreated.net/DHTest/v1" /> 

      <host> 
       <baseAddresses> 
        <add baseAddress="net.tcp://localhost:8090/"/> 
        <add baseAddress="http://localhost:8091/"/> 
       </baseAddresses> 
      </host> 
     </service> 
    </services> 
    <behaviors> 
     <serviceBehaviors> 
      <behavior name="DHTestBehaviour"> 
       <serviceMetadata httpGetEnabled="true"/> 
       <serviceDebug includeExceptionDetailInFaults="true"/> 
       <serviceCredentials> 
        <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="DHTestMembershipProvider"/> 
        <serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" findValue="CN=business.dhtestDHTest.com" /> 
        <clientCertificate> 
         <authentication certificateValidationMode="ChainTrust" trustedStoreLocation="LocalMachine" revocationMode="NoCheck" /> 
        </clientCertificate> 
       </serviceCredentials> 
       <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="DHTestRoleProvider" /> 
      </behavior> 
     </serviceBehaviors> 
    </behaviors> 
    <bindings> 
     <netTcpBinding> 
      <binding name="DHTestNetTcpBinding"> 
       <security mode="TransportWithMessageCredential"> 
        <message clientCredentialType="UserName"/> 
        <transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign"/> 
       </security> 
      </binding> 
     </netTcpBinding> 
    </bindings> 
</system.serviceModel> 

cliente Conf:

<system.serviceModel> 
    <bindings> 
     <netTcpBinding> 
      <binding name="NetTcpBinding_IDHTestBusinessService" closeTimeout="00:01:00" 
      openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 
      transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" 
      hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="524288" 
      maxBufferSize="65536" maxConnections="10" maxReceivedMessageSize="65536"> 
       <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" 
       maxBytesPerRead="4096" maxNameTableCharCount="16384" /> 
       <reliableSession ordered="true" inactivityTimeout="00:10:00" 
       enabled="false" /> 
       <security mode="TransportWithMessageCredential"> 
        <transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign" /> 
        <message clientCredentialType="UserName" /> 
       </security> 
      </binding> 
     </netTcpBinding> 
    </bindings> 
    <behaviors> 
     <endpointBehaviors> 
      <behavior name="DHTestBusinessServiceEndpointConf"> 
       <clientCredentials> 
        <clientCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" findValue="CN=invalid"/> 
        <serviceCertificate> 
         <authentication revocationMode="NoCheck" trustedStoreLocation="LocalMachine"/> 
        </serviceCertificate> 
       </clientCredentials> 
      </behavior> 
     </endpointBehaviors> 
    </behaviors> 
    <client> 
     <endpoint address="net.tcp://phoenix-iv:8090/" binding="netTcpBinding" 
     behaviorConfiguration="DHTestBusinessServiceEndpointConf" 
     bindingConfiguration="NetTcpBinding_IDHTestBusinessService" 
     contract="DHTest.NetTcp.Business.IDHTestBusinessService" 
     name="NetTcpBinding_IDHTestBusinessService"> 
      <identity> 
       <dns value="business.dhtest.com" /> 
      </identity> 
     </endpoint> 
    </client> 
</system.serviceModel> 

El código de autenticación nombre de usuario cliente/contraseña:

DHTestBusinessServiceClient client = new DHTestBusinessServiceClient(); 
client.ClientCredentials.UserName.UserName = "ratfink"; 
client.ClientCredentials.UserName.Password = "testpassword"; 

Gracias y O por tu ayuda con anticipación.

EDITAR (2009/06/01):

Uno de mis amigos me señaló hacia a blog que responde a la pregunta de por qué esto está ocurriendo. Aparentemente, cuando especifica TransportWithMessageCredential significa exactamente que: Transporte con credenciales de mensaje solo. Es por eso que mis certificados están siendo ignorados en el nivel de transporte.

Sin embargo, no considero el problema completo y cerrado, porque todavía quiero hacer esto. :) Voy a buscar validadores de certificados personalizados que creo que puedo conectar y ver si eso funciona. Les responderé a todos con los resultados.

EDITAR (2009/06/08):

No, validadores certificado personalizado no funcionan bien. WCF simplemente no los llama.

Respuesta

4

Encontré una solución a mi problema, sin embargo, resultó ser mucho más desagradable de lo que esperaba.

Básicamente, para lograr la verificación de las credenciales de transporte y mensaje, necesita definir un enlace personalizado. (Encontré información en este sentido here).

He encontrado la manera más fácil de hacer esto es seguir haciendo la configuración en el XML, pero al tiempo de ejecución de copia y ligeramente modificar la unión de la configuración XML netTcp. Literalmente hay un interruptor que debes habilitar. Aquí está el código en el lado del servicio y en el lado del cliente:

Servicio lateral

ServiceHost businessHost = new ServiceHost(typeof(DHTestBusinessService)); 
ServiceEndpoint endpoint = businessHost.Description.Endpoints[0]; 
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements(); 
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>(); 
sslElement.RequireClientCertificate = true; //Turn on client certificate validation 
CustomBinding newBinding = new CustomBinding(bindingElements); 
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding; 
newBinding.Namespace = oldBinding.Namespace; 
endpoint.Binding = newBinding; 

lado del cliente

DHTestBusinessServiceClient client = new DHTestBusinessServiceClient(); 
ServiceEndpoint endpoint = client.Endpoint; 
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements(); 
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>(); 
sslElement.RequireClientCertificate = true; //Turn on client certificate validation 
CustomBinding newBinding = new CustomBinding(bindingElements); 
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding; 
newBinding.Namespace = oldBinding.Namespace; 
endpoint.Binding = newBinding; 

Se podría pensar que habría, pero ¡estarías equivocado! :) Aquí es donde se pone más cojo. Me atribuyo mis métodos de servicios concretos con PrincipalPermission para restringir el acceso basado en roles del usuario del servicio de la siguiente manera:

[PrincipalPermission(SecurityAction.Demand, Role = "StandardUser")] 

Esto comenzó a fallar en cuanto he aplicado los cambios anteriores. La razón era porque el

OperationContext.Current.ServiceSecurityContext.PrimaryIdentity 

terminaba siendo una,, IIdentity no autenticado nombre de usuario-a menos desconocido. Esto se debe a que en realidad hay dos identidades que representan al usuario: una para el certificado X509 utilizado para autenticar en Transporte y otra para las credenciales de nombre de usuario y contraseña utilizadas para autenticarse en el nivel de Mensaje. Cuando realicé ingeniería inversa de los archivos binarios de WCF para ver por qué no me proporcionaba PrimaryIdentity, descubrí que tiene una línea de código explícita que hace que devuelva ese IIdentity vacío si encuentra más de un IIdentity. Supongo que es porque no hay forma de averiguar cuál es el primario uno.

Esto significa que el uso del atributo PrincipalPermission está fuera de la ventana. En cambio, escribí un método para imitar la funcionalidad que puede trato con múltiples IIdentities:

private void AssertPermissions(IEnumerable<string> rolesDemanded) 
{ 
    IList<IIdentity> identities = OperationContext.Current.ServiceSecurityContext.AuthorizationContext.Properties["Identities"] as IList<IIdentity>; 
    if (identities == null) 
     throw new SecurityException("Unauthenticated access. No identities provided."); 

    foreach (IIdentity identity in identities) 
    { 
     if (identity.IsAuthenticated == false) 
      throw new SecurityException("Unauthenticated identity: " + identity.Name); 
    } 

    IIdentity usernameIdentity = identities.Where(id => id.GetType().Equals(typeof(GenericIdentity))).SingleOrDefault(); 
    string[] userRoles = Roles.GetRolesForUser(usernameIdentity.Name); 

    foreach (string demandedRole in rolesDemanded) 
    { 
     if (userRoles.Contains(demandedRole) == false) 
      throw new SecurityException("Access denied: authorisation failure."); 
    } 
} 

no es bastante (especialmente la forma en que detecto el nombre de usuario/contraseña de credenciales IIdentity), pero funciona! Ahora, en la parte superior de mis métodos de servicio que necesito llamarlo así:

AssertPermissions(new [] {"StandardUser"}); 
+1

+1 sé que es antiguo, pero bien hecho, se ve mucho mejor que mi solución (o debería llamarlo piratear) aquí: http://stackoverflow.com/questions/9111361/wcf-service-with-wshttpbinding-manipulating-http-request-headers –

Cuestiones relacionadas