2009-05-30 19 views
6

Ho cercato di ottenere la sicurezza della WCF per il mio progetto e ho avuto poca fortuna. Sto cercando di creare un servizio che utilizzi net.tcp come associazione e che faccia sia la sicurezza dei messaggi che quella del trasporto. La sicurezza dei messaggi viene effettuata utilizzando nome utente e password e la sicurezza del trasporto viene effettuata (presumibilmente!) Utilizzando i certificati.La sicurezza del trasporto WCF che utilizza i certificati ignora il trust della catena

Per i test di sviluppo, ho creato la mia autorità di certificazione e ho inserito questo certificato nell'archivio attendibile del mio computer (LocalMachine). Ho quindi creato due certificati, ciascuno firmato dall'autorità di certificazione, uno per il servizio da utilizzare e uno per l'app client da utilizzare. Ho inserito entrambi in Personal store (My) in LocalMachine. Quindi, per il test, ho creato un certificato casuale che non è stato firmato dall'autorità di certificazione (e quindi non è affidabile) e l'ho inserito nell'archivio personale in LocalMachine. Ho usato makecert per creare questi certificati.

Ho quindi configurato l'app client che si connette al servizio per utilizzare il certificato non valido non valido come certificato client. Il servizio è impostato (presumibilmente) per controllare i certificati client utilizzando la catena di fiducia. Tuttavia, questo client è in grado di connettersi e parlare con successo al servizio! Dovrebbe essere rifiutato, perché il suo certificato non è affidabile!

Non so cosa stia causando questo comportamento, quindi invio il problema a voi ragazzi per vedere cosa ne pensate. Qui sono le mie configurazioni WCF: conf

Servizio:

<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> 

Il codice di autenticazione del client nome utente/password:

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

Grazie y ou per il tuo aiuto in anticipo.

EDIT (2009/06/01):

Uno dei miei amici mi ha puntato verso a blog che risponde alla domanda sul motivo per cui questo sta accadendo. Apparentemente, quando si specifica TransportWithMessageCredential si intende esattamente che: Trasporto con credenziali di messaggio solo. Questo è il motivo per cui i miei certificati vengono ignorati a livello di trasporto.

Tuttavia, non considero il problema completo e chiuso, perché continuo a farlo. :) Vado a esaminare convalida di certificati personalizzati che penso di poter collegare e vedere se funziona. Tornerò a tutti voi con i risultati.

EDIT (2009/06/08):

No, validatori certificato personalizzato non funzionano neanche. WCF semplicemente non li chiama.

risposta

4

Ho trovato una soluzione al mio problema, tuttavia, si è rivelato molto più cattivo di quanto mi aspettassi.

In sostanza, per ottenere il controllo delle credenziali di trasporto e dei messaggi è necessario definire un bind personalizzato. (Ho trovato informazioni su questo effetto here).

Ho trovato il modo più semplice per farlo è quello di continuare a eseguire la configurazione in XML, ma in fase di runtime, copiare e modificare leggermente l'associazione netTcp dalla configurazione XML. C'è letteralmente un interruttore che devi abilitare. Ecco il codice sul lato servizio e sul lato client:

Servizio laterale

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; 

lato client

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; 

Si potrebbe pensare che sarebbe, ma ti sbaglieresti! :) Questo è dove diventa più zoppo. Mi attribuiva i miei metodi di servizio in cemento con PrincipalPermission per limitare l'accesso in base ai ruoli degli utenti servizio come questo:

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

Questo è iniziato in mancanza, non appena ho applicato le modifiche di cui sopra. Il motivo era perché il

OperationContext.Current.ServiceSecurityContext.PrimaryIdentity 

stava finendo con l'essere uno sconosciuto, nome utente-less, IIdentity non autenticato. Ciò è dovuto al fatto che esistono in realtà due identità che rappresentano l'utente: una per il certificato X509 utilizzato per l'autenticazione su Transport e una per le credenziali nome utente e password utilizzate per l'autenticazione a livello di messaggio. Quando ho decodificato i file binari della WCF per capire perché non mi stesse dando la mia PrimaryIdentity, ho scoperto che ha una linea esplicita di codice che fa sì che restituisca tale IIdentity vuota se trova più di una IIdentity. Immagino sia perché non ha modo di capire quale sia la primaria primaria.

Ciò significa che l'utilizzo dell'attributo PrincipalPermission è fuori dalla finestra. Invece, ho scritto un metodo per simulare la funzionalità che può accordo con molteplici 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."); 
    } 
} 

non è abbastanza (soprattutto il mio modo di rilevare il nome utente/password credenziale IIdentity), ma funziona! Ora, in cima dei miei metodi di servizio ho bisogno di chiamare in questo modo:

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

+1 so che è vecchio ma ben fatto, sembra molto meglio di quanto la mia soluzione (o dovrei chiamarla hack) qui: http://stackoverflow.com/questions/9111361/wcf-service-with-wshttpbinding-manipulating-http-request-headers –

Problemi correlati