How to get a UTC timestamp in OP-TEE Trusted Application (TA) in datetime format?

517 views Asked by At

Disclaimer: It took me a solid 4-5 hours of looking for an answer and after figuring it out I decided to post it here for people in the same place.

OP-TEE is quite a good environment to develop TAs and CAs, however, there is no straightforward method of acquiring a datetime formatted properly. There is no struct tm either. Therefore, it made me wonder how do I get a datetime format in OP-TEE TAs?

What I spent a long time trying was to utilize the already supported mbedTLS libraries which, for a newcomer, would seem like they do support getting datetime format. After all, they do have gmtime which is supposed to return this value.

However, unfortunately, the gmtime and relevant functions have no implementation for the platform OP-TEE on ARMv8. That's a pretty tough luck.

So how do you get UTC time in an OP-TEE TA?

2

There are 2 answers

2
Everyone On BEST ANSWER

All OP-TEE development for ARMv8 is done using C. However, it lacks major libc support. Practically, it has very little libraries (e.g. string.h) which are skimmed down versions from the original libc corresponding libraries.

With that, the provided <time.h> in the OP-TEE contains nothing but a typedef for time_t and that's it.

The problem can be broken down to two sections:

  1. How do you get the epochs since 1970 Jan 1st 00:00:00?

This is an interesting problem, and while the straight forward solution is to simply do this:

TEE_Time tt;
TEE_GetREETime(&tt); 

This solution may not be satisfactory for many people who would not want to rely on REE's (Rich Execution Environment, AKA the vulnerable environment) count of epochs. This can be problematic for security sensitive operations where you need time to be legit and with no room for REE to modify it to perform a certain attack.

In the case described above, you will have to obtain the epochs from a hardware clock which will depend on the hardware board you are developing the TA on. You can even retrieve it from location devices which also return UTC time in NMEA statements. While it may not be 100% precise to the second, it may just be enough. If you need very high precision, you will need to get it from a hardware device securely.

Either way, you have to figure out how to get the epochs on your own. This answer focuses on the second part: getting the datetime.

  1. Getting the datetime from the epochs. Once you resolved step 1, you need to process it to datetime. For this purpose, you need gmtime which does not exist in the OP-TEE. It has no implementation. And you need a minimalist implementation to keep things simple.

Luckily, I was able to find this answer. Which links to newlib libraries that develop for Free BSD which are ideal for embedded systems. Hence why it's useful here!

I was able to put it together from their implementation and you can use it here:

gmtime_r.h:

#include <inttypes.h>

#define SECSPERMIN  60L
#define MINSPERHOUR 60L
#define HOURSPERDAY 24L
#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY  (SECSPERHOUR * HOURSPERDAY)
#define DAYSPERWEEK 7
#define MONSPERYEAR 12

#define YEAR_BASE   1900
#define EPOCH_YEAR      1970
#define EPOCH_WDAY      4
#define EPOCH_YEARS_SINCE_LEAP 2
#define EPOCH_YEARS_SINCE_CENTURY 70
#define EPOCH_YEARS_SINCE_LEAP_CENTURY 370

#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)


typedef int64_t time_t;

struct tm
{
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;
    int tm_yday;
    int tm_isdst;
};

struct tm* gmtime_r (time_t tim_p, struct tm* res);

gmtime_r.c:

#include "gmtime_r.h"

#define EPOCH_ADJUSTMENT_DAYS   719468L
/* year to which the adjustment was made */
#define ADJUSTED_EPOCH_YEAR 0
/* 1st March of year 0 is Wednesday */
#define ADJUSTED_EPOCH_WDAY 3
/* there are 97 leap years in 400-year periods. ((400 - 97) * 365 + 97 * 366) */
#define DAYS_PER_ERA        146097L
/* there are 24 leap years in 100-year periods. ((100 - 24) * 365 + 24 * 366) */
#define DAYS_PER_CENTURY    36524L
/* there is one leap year every 4 years */
#define DAYS_PER_4_YEARS    (3 * 365 + 366)
/* number of days in a non-leap year */
#define DAYS_PER_YEAR       365
/* number of days in January */
#define DAYS_IN_JANUARY     31
/* number of days in non-leap February */
#define DAYS_IN_FEBRUARY    28
/* number of years per era */
#define YEARS_PER_ERA       400

struct tm* gmtime_r (time_t tim_p, struct tm* res)
{
    time_t days, rem;
    const time_t lcltime = tim_p;
    int era, weekday, year;
    unsigned erayear, yearday, month, day;
    unsigned long eraday;

    days = lcltime / SECSPERDAY + EPOCH_ADJUSTMENT_DAYS;
    rem = lcltime % SECSPERDAY;
    if (rem < 0)
        {
        rem += SECSPERDAY;
        --days;
        }

    /* compute hour, min, and sec */
    res->tm_hour = (int) (rem / SECSPERHOUR);
    rem %= SECSPERHOUR;
    res->tm_min = (int) (rem / SECSPERMIN);
    res->tm_sec = (int) (rem % SECSPERMIN);

    /* compute day of week */
    if ((weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYSPERWEEK)) < 0)
        weekday += DAYSPERWEEK;
    res->tm_wday = weekday;

    /* compute year, month, day & day of year */
    /* for description of this algorithm see
    * http://howardhinnant.github.io/date_algorithms.html#civil_from_days */
    era = (days >= 0 ? days : days - (DAYS_PER_ERA - 1)) / DAYS_PER_ERA;
    eraday = days - era * DAYS_PER_ERA; /* [0, 146096] */
    erayear = (eraday - eraday / (DAYS_PER_4_YEARS - 1) + eraday / DAYS_PER_CENTURY -
        eraday / (DAYS_PER_ERA - 1)) / 365; /* [0, 399] */
    yearday = eraday - (DAYS_PER_YEAR * erayear + erayear / 4 - erayear / 100); /* [0, 365] */
    month = (5 * yearday + 2) / 153;    /* [0, 11] */
    day = yearday - (153 * month + 2) / 5 + 1;  /* [1, 31] */
    month += month < 10 ? 2 : -10;
    year = ADJUSTED_EPOCH_YEAR + erayear + era * YEARS_PER_ERA + (month <= 1);

    res->tm_yday = yearday >= DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY ?
        yearday - (DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY) :
        yearday + DAYS_IN_JANUARY + DAYS_IN_FEBRUARY + isleap(erayear);
    res->tm_year = year - YEAR_BASE;
    res->tm_mon = month;
    res->tm_mday = day;

    res->tm_isdst = 0;

    return (res);
}

You can put both of these files in your TA folder, and make sure you add gmtime.c in the sources list in the sub.mk. And finally, in the TA itself you can use it:

TEE_Time tt; 
TEE_GetREETime(&tt);
struct tm *lt, temp;
lt = gmtime((time_t)tt.seconds, &temp);
DMSG("%4d-%2d-%2d %2d:%2d:%2d", lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec); 

This will print the time and date in a proper format.

Currently, I have not yet ported the implementation of strftime, but soon I will do that as well which will auto-format the tm struct and add the 1900 to tm_year and 1 to tm_mon.

In the meantime, I hope this finds someone in need.

1
Gilles 'SO- stop being evil' On

Timer security

To retrieve the current time in milliseconds since 1970-01-01, call TEE_GetSystemTime. The security level depends on what secure clocks are available on the specific system. The absolute minimum guarantee is that this value cannot decrease while a trusted application is running, but the REE may be able to control at what speed this proceeds. There is no guarantee across reboots.

OP-TEE comes with an implementation of TEE_GetSystemTime based on CNTPCT. This can be configured to be exclusive to the TrustZone secure world on any armv8 chip, but I don't know if all chip manufacturers actually do so.

When the timer is secure, this is a guarantee that short delays will be respected. For example, if you want to block at least 1 second between authentication attempts, this is good enough.

Many platforms cannot keep the date securely, because that requires a clock that's always powered on, which requires a battery that doesn't run out. Often the non-trusted world can arrange to set the time to an arbitrary value after a system reset. So if you need the current date with enough precision to verify that a certificate has not expired, that's not good enough, and you need to do something like establish a connection to a secure time server. Which is a bootstrap problem, since you can't verify that the time server's certificate hasn't expired.

Breaking down the time

OP-TEE provides a thin C library, not including anything like gmtime. If you want to calculate the date and time from the epoch time, you need to provide your own implementation.

Mbed TLS is a consumer, not a provider, of gmtime (more precisely gmtime_r). It uses this function to check the validity of certificates.