My first trusted app

For the first blog post I wanted to introduce Trusted application development with a simple hello world example.

Normal world and secure world

Before coding your application you must decide which parts of the application need to be implemented in the secure world environment of the TEE. Typically this will be security sensitive operations - key generation, encrpytion, etc.

Hello secure world!

For this simple trusted application I want to display "Hello secure world!" from the TEE.

  • My normal world side of the application will look after initialising the Trusted application and telling it to do something
  • My secure world side will display a hello world message

Normal world

Before we can implement our secure hello world example, we need normal world client code to handle communication with our trusted application. To this end I have some skeleton client code that will berfore the tasks of:

  • Opening the trusted application
  • Opening a trusted application session
  • Closing a trusted application session
  • Getting the trusted application to perform a task (print hello world)
  • Closing the trusted application

My normal world code is three source files, main.c:

#include <stdlib.h>
#include "caSkel.h"

#define LOG_TAG "CASkel"
#include "log.h"

static void returnExitCode(int exitCode);

int main(int argc, char *args[])
{
    TEEC_Result nResult;

    nResult = caOpen();
    if (nResult != TEEC_SUCCESS) {
        LOG_E("Could not open session with Trusted Application.");
        fprintf(stderr, "Could not open session with Trusted Application.\n");
        returnExitCode(2);
    }

    nResult = caDo();

    if (nResult != TEEC_SUCCESS) {
        LOG_E("Could not send command to Trusted Application.");
        fprintf(stderr, "Could not send command to Trusted Application.\n");
        returnExitCode(2);
    }

    caClose();

    returnExitCode(0);
    return 0;
}

static void returnExitCode(int exitCode)
{
    if (0 != exitCode) {
        LOG_E("Failure");
    } else {
        LOG_I("Success");
    }
    fprintf(stderr, "CA exit code: %08x\n", exitCode);
    exit(exitCode);
}

 

caSkel.h for the function prototypes:

#include <tee_client_api.h>

TEEC_Result caOpen(void);

TEEC_Result caDo();

void caClose(void);

 

and caSkel.cpp for the implementation:


#include <stdlib.h>

#include "taSkel.h"
#include "taSkel_uuid.h"

#define LOG_TAG "CASkel:lib"
#include "log.h"

#include "tee_client_api.h"

const TEEC_UUID uuid = taSkel_UUID;
TEEC_Context  *context;
TEEC_Session  *session;

/**
*  Function skelInitialize:
*  Description:
*           Initialize: create a device context.
*  Output : TEEC_Context **context     = points to the device context
*
**/
static TEEC_Result skelInitialize(OUT TEEC_Context **context)
{
    TEEC_Result    nError;
    TEEC_Context  *pContext;

    LOG_D("[CASkel] %s", __func__);

    *context = NULL;

    pContext = (TEEC_Context *)malloc(sizeof(TEEC_Context));
    if (pContext == NULL) {
        return TEEC_ERROR_BAD_PARAMETERS;
    }
    memset(pContext, 0, sizeof(TEEC_Context));
    /* Create Device context  */
    nError = TEEC_InitializeContext(NULL, pContext);
    if (nError != TEEC_SUCCESS) {
        LOG_E("[CASkel] %s: TEEC_InitializeContext failed (%08x), %d",
               __func__, nError, __LINE__);
        if (nError == TEEC_ERROR_COMMUNICATION) {
            LOG_E("[CASkel] %s: The client could not communicate with the service,
            %d", __func__, __LINE__);
        }
        free(pContext);
    } else {
        *context = pContext;
    }

    LOG_D("[CASkel] %s finished", __func__);
    return nError;
}

/**
*  Function skelFinalize:
*  Description:
*           Finalize: delete the device context.
*  Input :  TEEC_Context *context     = the device context
*
**/

static TEEC_Result skelFinalize(IN OUT TEEC_Context *context)
{

    LOG_D("[CASkel] %s", __func__);

    if (context == NULL) {
        LOG_E("[CASkel] %s: Device handle invalid, %d", __func__, __LINE__);
        return TEEC_ERROR_BAD_PARAMETERS;
    }

    TEEC_FinalizeContext(context);
    free(context);
    return TEEC_SUCCESS;
}

/**
*  Function skelCloseSession:
*  Description:
*           Close the client session.
*  Input :  TEEC_Session *session - session handler
*
**/
static TEEC_Result skelCloseSession(IN TEEC_Session *session)
{
    LOG_D("[CASkel] %s", __func__);

    if (session == NULL) {
        LOG_E("[CASkel] %s: Invalid session handle, %d", __func__, __LINE__);
        return TEEC_ERROR_BAD_PARAMETERS;
    }
    TEEC_CloseSession(session);
    free(session);
    return TEEC_SUCCESS;
}

/**
*  Function skelOpenSession:
*  Description:
*           Open a client session with a specified service.
*  Input :  TEEC_Context  *context,    = the device context
*  Output:  TEEC_Session  **session   = points to the session handle
*
**/
static TEEC_Result skelOpenSession(
    IN    TEEC_Context  *context,
    OUT   TEEC_Session  **session)
{
    TEEC_Operation sOperation;
    TEEC_Result    nError;

    LOG_D("[CASkel] %s", __func__);

    *session = (TEEC_Session *)malloc(sizeof(TEEC_Session));
    if (*session == NULL) {
        return TEEC_ERROR_OUT_OF_MEMORY;
    }
    memset(*session, 0, sizeof(TEEC_Session));
    memset(&sOperation, 0, sizeof(TEEC_Operation));
    sOperation.paramTypes = 0;
    nError = TEEC_OpenSession(context,
                              *session,                    /* OUT session */
                              &uuid,                      /* destination UUID */
                              TEEC_LOGIN_PUBLIC,          /* connectionMethod */
                              NULL,                       /* connectionData */
                              &sOperation,                /* IN OUT operation */
                              NULL                        /* OUT returnOrigin, optional */
                             );
    if (nError != TEEC_SUCCESS) {
        LOG_E("[CASkel] %s: TEEC_OpenSession failed (%08x), %d", __func__, nError, __LINE__);
        free(*session);
        return nError;
    }
    return TEEC_SUCCESS;
}

TEEC_Result caOpen(void)
{
    TEEC_Result     nError;

    LOG_D("[CASkel] %s", __func__);

    nError = skelInitialize(&context);
    if (nError != TEEC_SUCCESS) {
        LOG_E("[CASkel] %s: skelInitialize failed (%08x), %d", __func__, nError, __LINE__);
        return nError;
    }

    /* Open a session */
    nError = skelOpenSession(context, &session);
    if (nError != TEEC_SUCCESS) {
        LOG_E("[CASkel] %s: skelOpenSession failed (%08x), %d", __func__, nError, __LINE__);
        return nError;
    }

    return nError;
}

TEEC_Result caDo()
{
    TEEC_Result    nError;
    TEEC_Operation sOperation;

    LOG_D("[CASkel] %s", __func__);

    memset(&sOperation, 0, sizeof(TEEC_Operation));
    sOperation.paramTypes = TEEC_PARAM_TYPES(
                                TEEC_MEMREF_TEMP_INPUT,
                                TEEC_MEMREF_TEMP_OUTPUT,
                                TEEC_NONE,
                                TEEC_NONE);

    nError = TEEC_InvokeCommand(session,
                                CMD_SKEL_HELLO_WORLD,
                                &sOperation,       /* IN OUT operation */
                                NULL               /* OUT returnOrigin, optional */
                               );

    if (nError != TEEC_SUCCESS) {
        LOG_E("[CASkel] %s: TEEC_InvokeCommand failed (%08x), %d", __func__, nError, __LINE__);
    }

    return nError;
}

void caClose(void)
{
    TEEC_Result       nError;

    LOG_D("[CASkel] %s", __func__);

    /* Close the session */
    nError = skelCloseSession(session);
    if (nError != TEEC_SUCCESS) {
        LOG_E("[CASkel] %s: skelCloseSession failed (%08x), %d", __func__, nError, __LINE__);
    }

    /* Finalize */
    nError = skelFinalize(context);
    if (nError != TEEC_SUCCESS) {
        LOG_E("[CASkel] %s: skelFinalize failed (%08x), %d", __func__, nError, __LINE__);
    }
}

 

So with that the normal world side implemented! Now lets look at the trusted application code.

Secure world

A trusted application must define entry points for:

  • When an instance is created
  • When an instance is being destroyed
  • When a new client connects
  • When the client disconnects
  • When the client invokes a command

Here, the work to display hello world will be implemented by the TA_InvokeCommandEntryPoint function, where we use the library function TEE_DbgPrintLnf to display our message. Again I have a skelelton Trusted application with default implementations of the above.

We have two source files for our trusted application - taskel.h - It just defines a command id that both the client and trusted application will use to identify which task the trusted application is being asked to perform:

/** Service-specific Command identifiers **/
#define CMD_SKEL_HELLO_WORLD   1

 

ta.c contains our trusted application:

/* ----------------------------------------------------------------------------
 *   Includes
 * ---------------------------------------------------------------------------- */
#include "taStd.h"
#include "tee_internal_api.h"
#include "taSkel.h"

/* ----------------------------------------------------------------------------
 *   Global regions and defines
 * ---------------------------------------------------------------------------- */

// Reserve 2048 byte for stack.
DECLARE_TRUSTED_APPLICATION_MAIN_STACK(2048);

// Reserve 2048 byte for heap.
DECLARE_TRUSTED_APPLICATION_MAIN_HEAP(2048);

#define TATAG "TA Skel "

/* ----------------------------------------------------------------------------
 *   Service Entry Points
 * ---------------------------------------------------------------------------- */

/**
 *  Function TA_CreateEntryPoint:
 *  Description:
 *        The function TA_CreateEntryPoint is the service constructor, which the system
 *        calls when it creates a new instance of the service.
 *        Here this function implements nothing.
 **/
TEE_Result TA_EXPORT TA_CreateEntryPoint(void)
{
    TEE_DbgPrintLnf(TATAG "TA_CreateEntry Point");
    return TEE_SUCCESS;
}

/**
 *  Function TA_DestroyEntryPoint:
 *  Description:
 *        The function TA_DestroyEntryPoint is the service destructor, which the system
 *        calls when the instance is being destroyed.
 *        Here this function implements nothing.
 **/
void TA_EXPORT TA_DestroyEntryPoint(void)
{
    TEE_DbgPrintLnf(TATAG "TA_DestroyEntryPoint");
}

/**
 *  Function TA_OpenSessionEntryPoint:
 *  Description:
 *        The system calls the function TA_OpenSessionEntryPoint when a new client
 *        connects to the service instance.
 **/
TEE_Result TA_EXPORT TA_OpenSessionEntryPoint(  uint32_t nParamTypes,
                                                IN OUT TEE_Param pParams[4],
                                                OUT void** ppSessionContext
)
{
    S_VAR_NOT_USED(nParamTypes);
    S_VAR_NOT_USED(pParams);
    S_VAR_NOT_USED(ppSessionContext);

    TEE_DbgPrintLnf(TATAG "TA_OpenSessionEntryPoint");
    return TEE_SUCCESS;
}

/**
 *  Function TA_CloseSessionEntryPoint:
 *  Description:
 *        The system calls this function to indicate that a session is being closed.
 **/
void TA_EXPORT TA_CloseSessionEntryPoint(IN OUT void* pSessionContext)
{
    TEE_DbgPrintLnf(TATAG "TA_CloseSessionEntryPoint");
}

/**
 *  Function TA_InvokeCommandEntryPoint:
 *  Description:
 *        The system calls this function when the client invokes a command within
 *        a session of the instance.
 *        Here this function perfoms a switch on the command Id received as input,
 *        and returns the main function matching to the command id.
 **/
TEE_Result TA_EXPORT TA_InvokeCommandEntryPoint(IN OUT void* pSessionContext,
                                                uint32_t nCommandID,
                                                uint32_t nParamTypes,
                                                TEE_Param pParams[4])
{
    TEE_Result ret;

    TEE_DbgPrintLnf(TATAG "TA_InvokeCommandEntryPoint");

    switch(nCommandID)
    {
        case CMD_SKEL_HELLO_WORLD:
            TEE_DbgPrintLnf(TATAG "Hello secure world!");
            return TEE_SUCCESS;

        default:
        TEE_DbgPrintLnf(TATAG "invalid command ID: 0x%X", nCommandID);
        return TEE_ERROR_BAD_PARAMETERS;
    }
}

 

That's it! Having compiled and installed both the trusted application and client application on my device, I can see debug traces from the trusted application using dmesg, or via putty on a development board.