0

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?

BlackTuareg
  • 160
  • 1
  • 11
  • Make the Server a time server to the client. Then both will be at same time. – jdweng Sep 11 '19 at 10:48
  • Unfortunatelly this is not possible, because n clients connect to m server implementations. The best option is probably to get client and server to use time servers, but we cannot ensure that (at least not for the 5 minutes wcf's default value). – BlackTuareg Sep 11 '19 at 10:59

1 Answers1

0

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.

Abraham Qian
  • 7,117
  • 1
  • 8
  • 22
  • Works by configuration when I add maxClockSkew="02:00:00" to localClientSettings and localServiceSettings in the client's app.config. Without these configuration it doesn't work on my side. So I'm one step further. Next issue is to get it working by code. – BlackTuareg Sep 12 '19 at 12:01