I've recently been working on an interesting problem for detecting clock changes in Windows Mobile 5.0.

The problem I’ve been trying to solve is this: lets say you have a business operation that takes place at date/time x and the data is only valid for y minutes past that point: so that beyond x+y the content or data generated by the business process cannot be accessed any more. Sounds pretty straight forward except that y is likely to be a number of days and the business process (or even the device) will be restarted in that time.

The initial solution might be to take the file time stamp x, add y minutes and ensure its greater than 'now' date/ time. That will work so long as the system date / time remains unchanged, but in Windows Mobile there is no way of knowing if the system date changes... well that’s not entirely true: there is a system notification that occurs when the date changes, but if you register for that event and receive notification, by the time you get the notification the clock has already changed and there is no way of knowing by how much it changed!

I don’t really want to stop people from changing the time on the device, but I need to know how much the time has changed since the content was retrieved in order to accurately calculate when the content expires. So the validity test should really be 'now' < x + y + delta where delta is the cumulative time changes to the clock. So what I am really looking at is some way of implementing a "linear clock" rather than a "secure clock", and a linear clock is something I can use to determine elapsed time.

So how can it be done in Windows Mobile 5.0? The design I’m working to is pretty straight forward and relies on a couple of premises:

1>    The clock cannot be altered outside the OS (e.g. through the bootloader)

2>    Protection of the clock is only required from UnTrusted applications – e.g. trusted applications are trusted not to mess with the reg keys, and notification events.

The principals of the design are based on the GetTickCount API that returns the number of ticks (milliseconds in WM 5.0) that the OS has been running.

1>    Set an event to fire when the system starts

2>    Set an event to fire when the clock is changed

3>    Set an event to fire every 2^32 milliseconds (minus a small buffer for error)

1>    When the system start event fires take the time and tick count and store them in a secure registry location.

2>    When the periodic timer fires take the time and tick count and overwrite the secure reg location

3>    When the time is changed:

a.       Read the stored time and tick count in the registry

b.      Add the tick count to the stored time and take the delta with ‘now’

c.       Read the existing delta from the registry and add the two together

d.      Store the delta back to the registry

An app can then calculate the linear time by adding the delta value held in the registry to the current clock value. Alternatively the application could register for State Notify broker of the delta registry key and then it will receive an event when the clock changes, and again can calculate the linear time. 

From a security perspective the SecureClock app needs to be signed and running in the Trusted code group otherwise it cant update the registry.

These are the important bits of my test implementation:

Registration step:

void Register()

{

      // register for device wake up

      if (0==CeRunAppAtEvent(buff, NOTIFICATION_EVENT_WAKEUP))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to set Wakeup event in SecureClock register\r\n")));

            return;

      }

 

      // Now register for time change

      if (0==CeRunAppAtEvent(buff,NOTIFICATION_EVENT_TIME_CHANGE))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to set TimeChange event in SecureClock register\r\n")));

            return;

      }

}

Unregister step – if the whole things needs to be pulled out:

void Unregister()

{

      // Clear all entries for this app.

      if (0==CeRunAppAtEvent(buff, NOTIFICATION_EVENT_NONE))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to clear Wakeup event in SecureClock Unregister\r\n")));

      }

}

 

Device startup and periodic timer:

 

void ProcessRegReset()

{

      // Clear registry info and write time and system ticks

      SYSTEMTIME sysTime;

      DWORD ticks = 0;

      HKEY hKey=0;

      DWORD dispo;

 

      if (!GetTimeAndTicks(&sysTime, &ticks))

      {

            goto error;

      }

 

      if (ERROR_SUCCESS != RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\SecureClock"), 0, TEXT("SecureClock DEMO"),REG_OPTION_NON_VOLATILE, 0,0,&hKey, &dispo))

      {

        DEBUGMSG(TRUE, (TEXT("Error creating the reg key\r\n")));

            goto error;

      }

 

      // Set run option next execution

      // Setup next run time.

      FILETIME TempNowFileTime;

 

      // Convert NOW to filetime

      if (0==SystemTimeToFileTime(&sysTime,&TempNowFileTime))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to convert system time to file time\r\n")));

            goto error;

      }

 

      // build trigger time

      __int64 base = 0xFFFF0000;    //set it to 2^32 miliseconds - 0xFFFF (allow a 66 second error - timer granulrity is OEM defined but defaults to 10 seconds)

      base = base * 10000; // turn the mili's to 100 nano's

 

      // Add Base time to Now

      // Stuff the 2 FILETIMEs into their own __int64s.

      __int64 t1 = TempNowFileTime.dwHighDateTime;

      t1 <<= 32;                   

      t1 |= TempNowFileTime.dwLowDateTime;

 

      // Write the tick count and time (as filetime

      if (ERROR_SUCCESS!= RegSetValueEx(hKey, TEXT("TickSync"), 0,REG_DWORD, (BYTE*)&ticks, sizeof(ticks)))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to set TickSync\r\n")));

            goto error;

      }

 

      if (ERROR_SUCCESS!= RegSetValueEx(hKey, TEXT("TimeSync"), 0,REG_BINARY, (BYTE*)&t1, sizeof(t1)))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to set TimeSync\r\n")));

            goto error;

      }

 

      if (ERROR_SUCCESS!= RegFlushKey(hKey))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to flush registry\r\n")));

            goto error;

      }

 

      if (ERROR_SUCCESS!= RegCloseKey(hKey))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to close reg key\r\n")));

            goto error;

      }

      hKey = 0;

 

      // Add the 64bit ints.

      t1  = t1 + base;

 

      // Set it back to the file time

      TempNowFileTime.dwHighDateTime = (long)(t1>>32);

      TempNowFileTime.dwLowDateTime = (long)(t1);

 

      // Convert back to system time

      if (!FileTimeToSystemTime(&TempNowFileTime,&sysTime))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to convert to Systime\r\n")));

            goto error;

      }

           

      if (!CeRunAppAtTime(buff,&sysTime))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to call CeRunAppAtTime\r\n")));

            goto error;

      }

 

      return;

error:

      if (hKey)

            RegCloseKey(hKey);

 

      return;

}

 

And last but not least the time change function:

void ProcessTimeChanged()

{

      // Time has changed. Calculate delta and store in the registry

      // take new time, now ticks

      SYSTEMTIME NewTime;

      __int64 OldTime;

      DWORD NowTicks = 0;

      DWORD StoredTicks = 0;

      HKEY hKey=0;

      DWORD dispo;

 

      if (!GetTimeAndTicks(&NewTime, &NowTicks))

      {

            goto error;

      }

 

      // calc diff ticks from the registry and now ticks

      if (ERROR_SUCCESS != RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\SecureClock"), 0, TEXT("SecureClock DEMO"),REG_OPTION_NON_VOLATILE, 0,0,&hKey, &dispo))

      {

        DEBUGMSG(TRUE, (TEXT("Error creating the reg key\r\n")));

            goto error;

      }

 

      DWORD type;

      DWORD size = sizeof(DWORD);

      if (ERROR_SUCCESS!=RegQueryValueEx(hKey, TEXT("TickSync"), 0, &type, (BYTE*)&StoredTicks, &size))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to read TickSync\r\n")));

            goto error;

      }

 

      size = sizeof(OldTime);

      if (ERROR_SUCCESS!=RegQueryValueEx(hKey, TEXT("TimeSync"), 0,&type, (BYTE*)&OldTime, &size))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to read TimeSync\r\n")));

            goto error;

      }

 

      DWORD TickDifference = 0;

      if (StoredTicks >NowTicks)

            TickDifference = NowTicks + (0xFFFFFFFF - StoredTicks);

      else

            TickDifference = NowTicks-StoredTicks;

 

      // add diff ticks to stored time

      __int64 base = TickDifference;

      base = base * 10000; // turn the mili's to 100 nanos

 

      // Add Base time to Now

      OldTime  = OldTime + base;

 

      // calc difference from now time

      FILETIME TempNewFileTime;

      // Convert NOW to filetime

      if (!SystemTimeToFileTime(&NewTime,&TempNewFileTime))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to convert SysTime to FileTime\r\n")));

            goto error;

      }

 

      __int64 t2 = TempNewFileTime.dwHighDateTime;

      t2 <<= 32;                   

      t2 |= TempNewFileTime.dwLowDateTime;

 

      __int64 timediff = OldTime-t2;      // Calc the difference in nano seconds (SIGNED!)

 

      // store the difference

      __int64 regDelta = 0;

      size = sizeof(regDelta);

 

      if (ERROR_SUCCESS!=RegQueryValueEx(hKey, TEXT("TimeDelta"), 0, &type, (BYTE*)&regDelta, &size))

      {

        DEBUGMSG(TRUE, (TEXT("No TimeDelta found - continuing \r\n")));

            //NO ERROR - just reset the delta to 0;

            regDelta = 0;

      }

 

      regDelta+=timediff;

      type=REG_BINARY;

      if (ERROR_SUCCESS!=RegSetValueEx(hKey, TEXT("TimeDelta"), 0, type, (BYTE*)&regDelta, sizeof(regDelta)))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to set TimeDelta \r\n")));

            goto error;

      }

 

      if (ERROR_SUCCESS!=RegCloseKey(hKey))

      {

        DEBUGMSG(TRUE, (TEXT("Failed to close reg key\r\n")));

            goto error;

      }

      hKey = 0;

 

      // Re-synch the reg keys

      ProcessRegReset();

 

      return;

error:

      if (hKey)

            RegCloseKey(hKey);

 

      return;

}

 

If you want to see the whole thing and try it out, grab the ZIP (with a test harness) here

Usual code disclaimer applies.

Marcus