Monotonic Counter

This section describes the implementation of a monotonic counter inside the Trusted Execution Environment (TEE). A monotonic counter is a counter that strictly increases over time and is used to prevent replay attacks, track state changes, or maintain an immutable sequence of events within secure applications.

Overview

The monotonic counter is stored persistently inside the TEE to guarantee its integrity and non-repudiability. The implementation provides functions to:

  • Load the counter state from persistent storage,

  • Save the counter state to persistent storage,

  • Update the counter value based on elapsed time,

  • Retrieve the current counter value.

Data Structures

The counter state is stored in a structure typically defined as:

typedef struct {
    uint64_t counter;          /* Current counter value */
    TEE_Time last_update;      /* Timestamp of last update */
} counter_state_t;

Loading the Counter State

The function load_counter() is responsible for loading the counter’s state from persistent storage:

  • It opens a persistent object identified by a predefined storage name.

  • Reads the stored counter state into the provided structure.

  • Returns an error if the counter is not initialized or if data corruption occurs.

This ensures the counter state is preserved across reboots or power cycles.

 1TEE_Result load_counter(counter_state_t *state)
 2{
 3    TEE_ObjectHandle object;
 4    TEE_Result res;
 5
 6    uint32_t flag = TEE_DATA_FLAG_ACCESS_READ; /* we can read the object */
 7    uint32_t read_bytes;
 8
 9    /* Open the persistent object where the counter state is stored */
10    res = TEE_OpenPersistentObject(
11        TEE_STORAGE_PRIVATE,          /* storageID */
12        COUNTER_STORAGE_NAME,         /* objectID */
13        strlen(COUNTER_STORAGE_NAME), /* objectIDLen */
14        flag,                         /* flags */
15        &object                       /* object */
16    );
17    if (res != TEE_SUCCESS)
18    {
19        EMSG("Counter not initialized or failed to open persistent object, res=0x%08x", res);
20        return res;
21    }
22
23    /* Read the counter state from the object */
24    res = TEE_ReadObjectData(object, state, sizeof(*state), &read_bytes);
25    TEE_CloseObject(object);
26
27    if (res != TEE_SUCCESS || read_bytes != sizeof(*state))
28    {
29        EMSG("TEE_ReadObjectData failed 0x%08x, read %u over %zu", res, read_bytes, sizeof(*state));
30        return TEE_ERROR_CORRUPT_OBJECT;
31    }
32
33    return TEE_SUCCESS;
34}

Saving the Counter State

The function save_counter() saves the current counter state:

  • Creates or overwrites the persistent storage object for the counter.

  • Writes the provided counter state structure into persistent storage.

  • Closes the object handle after successful write.

This operation guarantees the updated counter value is securely stored.

 1TEE_Result save_counter(const counter_state_t *state)
 2{
 3    TEE_ObjectHandle object;
 4    TEE_Result res;
 5
 6    uint32_t flag = TEE_DATA_FLAG_OVERWRITE; /* we can overwrite the object */
 7
 8    /* Create or open the persistent object where the counter state will be stored */
 9    res = TEE_CreatePersistentObject(
10        TEE_STORAGE_PRIVATE,          /* storageID */
11        COUNTER_STORAGE_NAME,         /* objectID */
12        strlen(COUNTER_STORAGE_NAME), /* objectIDLen */
13        flag,                         /* flags */
14        TEE_HANDLE_NULL,              /* attributes */
15        state,                        /* initialData */
16        sizeof(*state),               /* initialDataLen */
17        &object                       /* object */
18    );
19    if (res != TEE_SUCCESS)
20    {
21        EMSG("Failed to create persistent object, res=0x%08x", res);
22        return res;
23    }
24
25    TEE_CloseObject(object);
26    return TEE_SUCCESS;
27}

Updating the Counter

The function update_counter() performs the logic of advancing the monotonic counter:

  1. It attempts to load the existing counter state. - If the counter is uninitialized, it initializes it to zero and sets the last update timestamp to the current system time.

  2. It obtains the current system time and calculates the elapsed time since the last update.

  3. If time has passed, it increments the counter (in this implementation by a fixed increment of 1 per update call). - This could be modified to include a random factor or other logic if desired.

  4. Updates the last update timestamp to the current time.

  5. Saves the new state persistently.

By tracking elapsed time and increasing the counter accordingly, this function ensures the counter strictly increases with time.

 1TEE_Result update_counter(void)
 2{
 3    TEE_Result res;
 4    TEE_Time now;
 5    counter_state_t state = {0};
 6
 7    /*
 8     * Load the current counter state
 9     * If the counter is not initialized, it will be set
10     */
11    res = load_counter(&state);
12    if (res != TEE_SUCCESS)
13    {
14        TEE_GetSystemTime(&now);
15        state.counter = 0;
16        state.last_update = now;
17        res = save_counter(&state);
18        if (res != TEE_SUCCESS)
19        {
20            EMSG("Failed to initialize counter state, res=0x%08x", res);
21            return res;
22        }
23    }
24
25    /* Get the current time */
26    TEE_GetSystemTime(&now);
27
28    /*
29     * Compute elapsed time since last update
30     * If no time has passed, return the current counter value
31     */
32    uint64_t elapsed = now.seconds - state.last_update.seconds;
33    if (elapsed == 0)
34        return TEE_SUCCESS;
35
36    /*
37     * Update counter by multiplying elapsed time with a random factor
38     * By doing that, the counter will increase at a variable rate
39     */
40    state.counter += 1;
41    state.last_update = now;
42
43    /* Save the updated state */
44    res = save_counter(&state);
45    if (res != TEE_SUCCESS)
46    {
47        EMSG("Failed to save counter state, res=0x%08x", res);
48        return res;
49    }
50
51    return TEE_SUCCESS;
52}

Retrieving the Counter Value

The function get_counter() simply loads the current counter state and returns the counter value to the caller. This provides a reliable and consistent way to query the monotonic counter.

 1TEE_Result get_counter(uint64_t *counter_value)
 2{
 3    TEE_Result res;
 4    counter_state_t state = {0};
 5
 6    /* Load the current counter state */
 7    res = load_counter(&state);
 8    if (res != TEE_SUCCESS)
 9    {
10        EMSG("Failed to load counter state, res=0x%08x", res);
11        return res;
12    }
13
14    /* Get the current counter value */
15    *counter_value = state.counter;
16    return TEE_SUCCESS;
17}

Retrieving the Last Update Timestamp

The function get_last_update_time() retrieves the timestamp of the last counter update. This will be used when doing the attestation to ensure the counter is not only increasing but also reflects the time of the last update.

 1TEE_Result get_counter_timestamp(TEE_Time *timestamp)
 2{
 3    TEE_Result res;
 4    counter_state_t state = {0};
 5
 6    /* Load the current counter state */
 7    res = load_counter(&state);
 8    if (res != TEE_SUCCESS)
 9    {
10        EMSG("Failed to load counter state, res=0x%08x", res);
11        return res;
12    }
13
14    /* Get the last update timestamp */
15    *timestamp = state.last_update;
16    return TEE_SUCCESS;
17}

Security Considerations

  • The counter cannot decrease, protecting against rollback attacks.

  • Use of the TEE persistent storage API ensures counter state confidentiality and integrity.

  • The counter update logic relies on secure system time provided by the TEE.