Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

DateValidator using SNTP

4.25/5 (8 votes)
8 Jul 2009CPOL4 min read 33.9K   472  
Check if the expiration date of your application has been passed regardless of system date.

Overview

The DateValidator is a .NET component that compares a given date against one retrieved from a Time Server. I recently needed to produce a time limited version of an application. In this particular case, the expiry date of the application did not need to be flexible so it was hard coded into the application. Checking against the system's date was not enough as it can easily be changed, so the obvious path was to get the 'real' date from another source. This article is the quick solution I came up with.

This is not a comprehensive solution to securing your time limited applications or to NTP/SNTP, but could act as a good starting point.

SNTP

There are many time servers around the world, and there are several different protocols in use. This component uses a small part of the Simple Network Time Protocol (SNTP). SNTP works by sending/receiving packets of 48 (or more) bytes over a UDP socket on port 123. In the case of this component (due to the low precision required), we are only interested in 5 of those bytes (0, and 40-43). The client connects to the server and sends a request packet, then receives a response packet which contains the data. The actual Date/Time is not contained in the data! Instead, a Timestamp containing the number of seconds since 1st Jan 1900 00:00:00 UTC is embedded in bytes 40-43 (and fractions of seconds in bytes 44-47). From this, we can calculate the current UTC and compare against ours to see if it's valid.

For full time synchronization, many other bytes/Timestamps are used to get high precision and to compensate for delays in the sending/receiving. This is not needed for this component as we only need accuracy to 1 day; so it is not covered here, but is very easy to implement should you need to.

The Component

Note: Any exceptions that are thrown during the validation process are caught and passed to the event args rather than being re-thrown. You can examine the args to determine which exceptions to treat as validation failure.

The component is very simple. It has one method (+3 overloads) ValidateAsync and one event Validated. The method takes a DateTime that is the expiry date of the application, and optionally a TimeServer and a TimeOut value. This method starts a BackgroundWorker, where the work is actually done (on a separate thread). When it's completed, the event is raised along with a ValidatedEventArgs instance that gives us all the information we need.

This is the code that does the validation (details below):

C#
ValidatedEventArgs  validationArgs = 
           new ValidatedEventArgs(_TimeServer, _MaxDate);
UdpClient client = null;
try
{
    client = new UdpClient();
    IPEndPoint ipEndPoint = _TimeServer.GetIPEndPoint();
    client.Client.SendTimeout = _TimeOut * 1000;
    client.Client.ReceiveTimeout = _TimeOut * 1000;
    client.Connect(ipEndPoint);

    client.Send(Request, byteCount);
    byte[] received = client.Receive(ref ipEndPoint);

    if (received.Length >= byteCount && 
       ((received[0] & modeMask) == modeServer))
    {
        ulong transmitTimeStamp = 0;
        for (int i = 40; i <= 43; i++)
            transmitTimeStamp = (transmitTimeStamp << 8) | received[i];

        DateTime result = rootDateTime;
        result += TimeSpan.FromSeconds(transmitTimeStamp);
        validationArgs.TimeServerDate = result.Date;
        if (validationArgs.TimeServerDate <= _MaxDate)
        {
            validationArgs.ValidationResult = ValidationResult.OK;
        }
        else
        {
            validationArgs.ValidationResult = ValidationResult.Fail;
            validationArgs.ErrorText = "This software has expired.";
        }
    }
    else
    {
        validationArgs.ValidationResult = ValidationResult.Invalid;
        validationArgs.ErrorText = "Invalid result from TimeServer.";
    }
}
catch (Exception ex)
{
    validationArgs.ValidationResult = ValidationResult.Exception;
    validationArgs.ErrorText = ex.Message;
    validationArgs.Exception = ex;
}
finally
{
    // clean up and set the result to our args.
    if(client!=null)
        client.Close();
    e.Result = validationArgs;
}

First, we create, initialize, and connect the UDP socket. Then, we send the request packet. This is just an array of 48 bytes. Bits 0, 1, and 2 of byte[0] indicate the mode. We are a client, which is indicated by the value 3 (011). Bits 3, 4, and 5 of byte[0] indicate the version of SNTP we're using. This is using version 4, so we left shift '4' (100) three bits and OR it with the mode to give us byte[0] (00100011). We could have just set byte[0] to equal 35, but we are following the logic in the protocol properly, and this will make refactoring the code easier in case of a change in the protocol.

C#
private const int byteCount = 48;
private const byte sntpVersion = 4;
private const int versionBitOffset = 3;
private const byte modeMask = 7;
private const byte modeClient = 3;
private const byte modeServer = 4;
private static byte[] Request
{
    get
    {
        byte[] result = new byte[byteCount];
        result[0] = modeClient | (sntpVersion << versionBitOffset);
        return result;
    }
}

Next, we wait to receive a response from the server. Once we have the received byte array, we check the length and the mode bits to make sure the data is valid and it came from a server. Then, we iterate over the integer part of the timestamp (bytes 40-43) to get the seconds, and convert to a DateTime (UTC). If there are any errors or exceptions along the way, these are placed accordingly in an instance of ValidatedEventArgs. Finally, we close the socket.

At this point, the BackgroundWorker's RunWorkerCompleted method is called, where we retrieve the event args and raise the event; now, we're back on the original thread.

C#
backgroundWorker = null;
ValidatedEventArgs result = e.Result as ValidatedEventArgs;
OnValidated(result);

Other Classes

TimeServer

This is a simple class that holds the host name of the server and the port. I have put several static TimeServers in there (some grouped in arrays) for convenience.

ValidatedEventArgs

A simple class derived from System.EventArgs that holds data about the validation.

Useful References

History

  • 8th July 2009: Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)