Search code examples
c#wcfwcf-securitynettcpbindingnet-tcp

Set max time skew in wcf with net.tcp binding


I have a WCF service with net.tcp endpoints using custom usernamePassswordValidator, custom authorization and TransportWithMessageCredential with credential type "Username" (see below). Server and client work fine - unless the time skew between server and client machine are more than 5 minutes.

Now I try to set the max skew time in code. I tried to adapt code snippets intended for WSHttpBindings from MSDN and used the custom binding on server and client:

binding = GetSecuredBindingFromServerOrClient(); 
CustomBinding myCustomBinding = new CustomBinding(binding);
var security = myCustomBinding.Elements.Find<TransportSecurityBindingElement>(); // TransportSecurityBindingElement or SecurityBindingElement
security.LocalClientSettings.MaxClockSkew = timeSkew;
security.LocalServiceSettings.MaxClockSkew = timeSkew;
security.LocalServiceSettings.DetectReplays = false;
security.IncludeTimestamp = false;
// on client: use this custom binding in channel factory
var channelFactory = new ChannelFactory<ICheckerService>(customBinding, someAddress);
// on server: Update binding with customBinding
endpoint.Binding = myCustomBinding;

Still the connection fails with a MessageSecurityException when there is a time skew for more than 5 minutes (default value). I set also IncludeTimestamp to false or true but neither of them improved the situation.

The server behavior is:

<behavior name="customUserNamePasswordSecurityBehavior">
 <serviceCredentials>
   <userNameAuthentication userNamePasswordValidationMode="Custom"  customUserNamePasswordValidatorType="MySecurity.BasicAuthenticationValidator, MySecurity.Services"/>
 </serviceCredentials>
 <serviceAuthorization principalPermissionMode="Custom">
   <authorizationPolicies>
     <add policyType="Security.CustomAuthorizationPolicy, MySecurity.Services"/>
   </authorizationPolicies>
 </serviceAuthorization>
</behavior>

Then endpoint bindings are:

<binding name="tcpUserNameAuthentication">
   <reliableSession enabled="true"/>
   <security mode="TransportWithMessageCredential">
      <message clientCredentialType="UserName"/>
   </security>
</binding>

Did anybody get the time skew working based on the above configuration with TransportWithMessageCredential and net.tcp? Or is there a basic misunderstanding?


Solution

  • On my side, MaxClockSkew works well if I use NetTcp protocol, which requires a certificate on the server-side (need to add the management permission of the private key to the account running the service) and username/password on the client-side.
    At first, I transform the Nettcpbinding to Custombinding.

    <customBinding>
            <binding name="mybinding">
              <security authenticationMode="SecureConversation">
                <localClientSettings maxClockSkew="00:07:00" />
                <localServiceSettings maxClockSkew="00:07:00" />
                <secureConversationBootstrap authenticationMode="UserNameForCertificate">
                  <localClientSettings maxClockSkew="00:30:00" />
                  <localServiceSettings maxClockSkew="00:30:00" />
                </secureConversationBootstrap>
              </security>
              <binaryMessageEncoding></binaryMessageEncoding>
              <tcpTransport />
            </binding>
          </customBinding>
    

    Then I invocate the service with the client proxy class when I change the time on the client-side, it works well when the client time varies within 7minutes on the server-side. if I didn't set up the MaxClockSkew on the server-side. it only works within 5minutes the server-side time.
    Please refer to the below example, wish it is useful to you.
    Server-side(console application)

    class Program
        {
            static void Main(string[] args)
            {
                using (ServiceHost sh=new ServiceHost(typeof(MyService)))
                {
                    sh.Open();
                    Console.WriteLine("Service is ready....");
    
                    Console.ReadLine();
                    sh.Close();
                }
            }
        }
    
        [ServiceContract]
        interface IService
        {
            [OperationContract]
            string GetData();
        }
        public class MyService : IService
        {
            public string GetData()
            {
                return DateTime.Now.ToString();
            }
        }
        public class MyValidator : UserNamePasswordValidator
        {
            public override void Validate(string userName, string password)
            {
                if (userName != "jack" || password != "123456")
                {
                    throw new Exception("My Error");
                }
    
            }
        }
    

    App.config

    <system.serviceModel>
        <services>
          <service name="Server1.MyService" behaviorConfiguration="mybehavior">
            <endpoint address="" binding="customBinding" contract="Server1.IService" bindingConfiguration="mybinding"></endpoint>
            <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"></endpoint>
            <host>
              <baseAddresses>
                <add baseAddress="net.tcp://localhost:800"/>
              </baseAddresses>
            </host>
          </service>
        </services>
        <bindings>
          <customBinding>
            <binding name="mybinding">
              <security authenticationMode="SecureConversation">
                <localClientSettings maxClockSkew="00:07:00" />
                <localServiceSettings maxClockSkew="00:07:00" />
                <secureConversationBootstrap authenticationMode="UserNameForCertificate">
                  <localClientSettings maxClockSkew="00:30:00" />
                  <localServiceSettings maxClockSkew="00:30:00" />
                </secureConversationBootstrap>
              </security>
              <binaryMessageEncoding></binaryMessageEncoding>
              <tcpTransport />
            </binding>
          </customBinding>
        </bindings>
        <behaviors>
          <serviceBehaviors>
            <behavior name="mybehavior">
              <serviceMetadata />
              <serviceDebug includeExceptionDetailInFaults="true" />
              <serviceCredentials>
                <serviceCertificate findValue="5ba5022f527e32ac02548fc5afc558de1d314cb6" x509FindType="FindByThumbprint" storeLocation="LocalMachine" storeName="My"/>
                <userNameAuthentication customUserNamePasswordValidatorType="Server1.MyValidator,Server1" userNamePasswordValidationMode="Custom"/>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>
    

    Client-side.

    ServiceReference1.ServiceClient client = new ServiceClient();
                client.ClientCredentials.UserName.UserName = "jack";
                client.ClientCredentials.UserName.Password = "123456";
                try
                {
                    var result = client.GetData();
                    Console.WriteLine(result);
                }
                catch (Exception)
                {
                    throw;
                }
    

    App.config(auto-generated)

    <system.serviceModel>
            <bindings>
                <customBinding>
                    <binding name="CustomBinding_IService">
                        <security defaultAlgorithmSuite="Default" authenticationMode="SecureConversation"
                            requireDerivedKeys="true" includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
                            requireSignatureConfirmation="false" canRenewSecurityContextToken="true">
                            <secureConversationBootstrap defaultAlgorithmSuite="Default"
                                authenticationMode="UserNameForCertificate" requireDerivedKeys="true"
                                includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
                                requireSignatureConfirmation="false">
                                <localClientSettings detectReplays="true" />
                                <localServiceSettings detectReplays="true" />
                            </secureConversationBootstrap>
                            <localClientSettings detectReplays="true" />
                            <localServiceSettings detectReplays="true" />
                        </security>
                        <binaryMessageEncoding />
                        <tcpTransport />
                    </binding>
                </customBinding>
            </bindings>
            <client>
                <endpoint address="net.tcp://10.157.13.69:800/" binding="customBinding"
                    bindingConfiguration="CustomBinding_IService" contract="ServiceReference1.IService"
                    name="CustomBinding_IService">
                    <identity>
                        <certificate encodedValue="blablabla" />
                    </identity>
                </endpoint>
            </client>
    </system.serviceModel>
    

    Feel free to let me know if there is anything I can help with.