OP-TEE系列之(四)实现CA_TA调用与加密算法实现

OP-TEE架构分析

post_04_3.jpg

基本上,存在两个独立的世界:一个Normal世界,另一个是Secure世界。 每个世界都有自己的操作系统(OS)和用户应用程序,简单地说,正常世界中的用户应用程序和正常操作系统是传统的,而安全世界中的用户应用程序和正常操作系统具有专门的用途(例如数字版权管理,认证等)。

两个世界通过安全Monitor进行通信。

当受信任的OS从其非特权模式加载信任关系时,它首先检查其签名及软件完整性,以查看其是否由正确的方签名。这种完整性检查旨在消除加载被篡改的trustlets的风险。

通过设计,从硬件(例如,eFuse / qFuse或ROM)开始,形成信任链。此链包括引导加载程序、受信任的操作系统和派生的证书或密钥。这种逐步验证程序确保了安全世界的完整性。潜在地,整个系统的安全性和完整性依赖于这种验证。

post_04_1.jpg
  • TEE subsystem
      管理共享内存
      提供通用API作为IOCTL
  • tee-supplicant
      TEE辅助程序
  • OP-TEE driver
      从客户端到OP-TEE的转发命令
      从OP-TEE向请求方管理RPC请求

可信应用的设计分为两个部分 :客户端应用和可信端应用。客户端应用和可信端应用通过 uuid 进行识别,只有使用相同的 uuid,双方才能实现交互。下图显示了客户端应用和可信端应用的交互过程。

post_04_5.jpg

TA部分

  • TA分为Static TA 和 Dynamic TA两种

    静态TA运行在内核模式中,动态TA运行在用户模式中,一般存储在文件系统中,通过TEE_Supplicant被OP-TEE加载。

    以下内容帮助对其进行理解:

    静态TA只是一个接口,它不是TA本身

    静态TA是将特殊服务暴露给动态TAs和客户端的一种方式

    静态TA可以通过调用TEE内核中的任何驱动程序代码直接访问硬件。 这意味着您可以使用静态TA扩展内部API提供的服务,而无需添加任何新的系统调用。

    一个case必须实现app作为static TA,是因为TA必须在REE准备好之前被启动。 在REE准备好和动态TAs启动完成之后,可以为该静态TA提供服务,这些是需要的对于静态TA想要打开、调用动态TA。

    动态TA可以使用Internal Client API与静态TA进行通信

  • MMU L1 Translation tables

    保留4GiB大小用于OP-TEE内核,还存在若干32MiB大小的用于TA虚拟内存,并且为每个进程分配这样一个小的tables。

    借助OP-TEE来实现特定安全需求时,一次完整的功能调用一般都是起源于CA,TA做具体功能实现并返回数据到CA,而整个过程需要经过OP-TEE的client端接口,OP-TEE在Linux kernel端的驱动,Monitor模式下的SMC处理,OP-TEE OS的thread处理,OP-TEE中的TA程序运行,OP-TEE端底层库或者硬件资源支持等几个阶段。当TA执行完具体请求之后会按照原路径将得到的数据返回给CA。

    类似于以下流程:

post_02_6.png

非安全的应用程序A将数据传递给linux内核,linux内核将用户层数据拷贝到内核层,linux内核向守护进程申请一块共享内存,linux内核又把数据拷贝到共享内存,将数据所在的虚拟地址转为物理地址,将物理地址作为参数放在r1,r2中,产生svc中断传递给OPTEE,OPTEE再将物理地址转为虚拟地址,读取数据,传递给安全应用程序B。

共享内存就是安全世界和非安全世界通信用的,比如安全世界应用调用安全存储API,API函数内部会申请共享内存,然后把加密过得数据存放在共享内存中发送给linux内核,内核再给linux守护进程,linux守护进程在把数据存储到linux文件系统中

  • TA部分的通信与调度


    post_04_02.jpg
  1. 从SMC进入安全世界(FIQ)

  2. Command到达 —> 分配可用线程,TA上下文建立并调用

    如果需要tee-supplicant,RPC将被启动并挂起

  3. 任务完成、RPC 或者IRQ返回Normal

  • TA实现

    在TA档案中就是在TEE中要执行的内容,主要使用五个API来控制CA-TA连结与指令操作

    • TA_CreateEntryPoint:建立进入点,让TA可以被呼叫
    • TA_DestroyEntryPoint:移除进入点,结束TA的功能
    • TA_OpenSessionEntryPoint:建立CA呼叫TA的通道,为连接CA-TA的起始点
    • TA_CloseSessionEntryPoint:关闭CA-TA的通道
    • TA_InvokeCommandEntryPoint:接收CA传送的指令,并在这边执行

TA 进程。每一个可信应用的进程都被分成了两个线程,一个线程专门负责进程间通信,另一个是工作线程,用于处理数据及 IO 任务。这种结构的好处一方面在于当发生中断时,进程不会完全停止 ;另一方面,这种结构也有利于 TA 进程从可信执行环境框架中更好地分离和抽象。

CA部分

典型的CA程序流程遵循GP客户端API

CA就是在REE中用来控制TA的应用程序,主要也是包含五个与TA中对应的api来控制CA-TA连结与指令操作,在制作应用程序时也可以利用呼叫CA或相关的API来控制TA。

post_04_4.jpg
  • TEEC_InitializeContext

    Connect to the OP-TEE Linux driver

    建立context,提供CA连接TA的方式

  • TEEC_OpenSession

    Loads the TA

    根据UUID建立CA呼叫TA的通道,为连接CA-TA的起始点

  • TEEC_InvokeCommand

    Control TA functions

    传送指令与需要的参数给TA

  • TEEC_CloseSession

    关闭CA-TA的通道

  • TEEC_FinalizeContext

    移除建立好的背景

自定义TA_CA实现

post_04_5.jpg

TA_代码分析

根据上篇博文的源码进行分析,熟悉项目结构

TEE_Result TA_CreateEntryPoint(void){…}

创建TA的实例时调用。 这是TA的入口

DMSG("has been called");

TEE_Result TA_OpenSessionEntryPoint( uint32_t p1, TEE_Param __maybe_unused p2, void __maybe_unused p3){…}

当TA开启新的会话时被调用。 sess_ctx可以更新一个值,以便能够在后续调用TA中识别此会话。 在此功能中,您通常会对TA进行全局初始化。

DMSG("This is Hiro!\n");

TEE_Result TA_InvokeCommandEntryPoint(void __maybe_unused p1, uint32_t p2, uint32_t p3, TEE_Param p4){…}

TA调用时被call。 sess_ctx(p1)保存由TA_OpenSessionEntryPoint()分配的值。 其余参数来自正常世界。

static TEE_Result inc_value(uint32_t param_types, TEE_Param params[4]){…}

被TA_InvokeCommandEntryPoint中的

inc_value(param_types, params);

调用,实现加1

params[0].value.a++;

DMSG("Got value: … " & "Increase value to:")

void TA_CloseSessionEntryPoint(void __maybe_unused *sess_ctx){…}

在会话关闭时调用,sess_ctx保存由TA_OpenSessionEntryPoint()分配的值。

DMSG("Goodbye!_new_taps\n");

void TA_DestroyEntryPoint(void){…}

如果TA没有被crashed or panicked时,当TA实例被销毁时调用,这是TA最后一个调用。

DMSG("has been called");

CA代码分析

CA部分主程序代码

int main(int argc, char *argv[])
{
    TEEC_Result res;
    TEEC_Context ctx;
    TEEC_Session sess;
    TEEC_Operation op;
    TEEC_UUID uuid = TA_NEW_TAPS_UUID;
    uint32_t err_origin;

/* 
 * CA第一个调用,初始化上下文连接至TEE 
 */
    res = TEEC_InitializeContext(NULL, &ctx);
    if (res != TEEC_SUCCESS)
        errx(1, "TEEC_InitializeContext failed with code 0x%x", res);

/*
 * 开启会话,当与TA断会话建立完成,Log输出
 * This is Hiro!
 */
    res = TEEC_OpenSession(&ctx, &sess, &uuid,
                   TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin);
    if (res != TEEC_SUCCESS)
        errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x",
            res, err_origin);

/*
 * 通过调用它来执行TA中的一个功能,在这种情况下我们增加一个数字。 
 * 命令ID部分的值以及如何解释参数是TA提供的接口的一部分。
 */

/* 明确/组合TEEC_Operation结构体 */
    memset(&op, 0, sizeof(op));

/*
 * 准备参数。 在第一个参数中传递一个值,剩下的三个参数是未使用的。
 */
    op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INOUT, TEEC_NONE,
                     TEEC_NONE, TEEC_NONE);
    op.params[0].value.a = 42;

/*
 * TA_NEW_TAPS_CMD_INC_VALUE是要调用的TA中的实际功能。
 */
    printf("Invoking TA to increment %d _NT\n", op.params[0].value.a);
    res = TEEC_InvokeCommand(&sess, TA_NEW_TAPS_CMD_INC_VALUE, &op,
                 &err_origin);
    if (res != TEEC_SUCCESS)
        errx(1, "TEEC_InvokeCommand failed with code 0x%x origin 0x%x",
            res, err_origin);
    printf("TA incremented value to %d _NT\n", op.params[0].value.a);

/*
 * 结束
 */

    TEEC_CloseSession(&sess);

    TEEC_FinalizeContext(&ctx);

    return 0;
}

完成一次完整的CA请求时在linux 用户需要执行的操作依次如下 :

  1. 调用TEEC_InitializeContext函数打开op-tee驱动文件,获取到操作句柄并存放到TEE_Context类型的变量中。
  2. 调用TEEC_OpenSession函数,通过获取到的TEE_Context类型的变量创建一个特定CA与特定TA之间进行通信的通道,如果TA image被存放在file system中,那个在创建session的时候,OP-TEE OS端还会将TA image从file system中加载到OP-TEE。
  3. 初始化TEEC_Operation类型的变量,并根据实际需要借助TEEC_PARAM_TYPES宏来设定TEEC_Operation类型变量中paramTypes成员的值,该值规定传递到OP-TEE中的最多4个变量缓存或者是数据的作用(作为输入还是输出)。并且还要根据paramTypes的值设定对应的params[x]成员的值或者是指向的地址以及缓存的长度。
  4. 使用已经创建好的session,TA与CA端规定的command ID以及配置好的TEEC_Operation类型变量作为参数调用TEEC_InvokeCommand函数来真正发起请求。 调用TEEC_InvokeCommand成功之后,剩下的事情就有OP-TEE和TA进行处理并将结果和相关的数据通过TEEC_Operation类型变量中的params成员返回给CA。
  5. 调用成功之后如果不需要再次调用该TA则需要注销session和释放掉context,这两个操作一次通过调用TEEC_CloseSession函数和TEEC_FinalizeContext函数来实现。
post_04_6.png

自定义算法实现

本部分实现调用sha1算法和sha256算法分别计算hash值,并将计算结果返回给CA端。

  • CA_interface
int main(int argc, char *argv[])
{
    if(0 == memcmp(argv[1], "random", 6))
    {
        
        TF("Entry get random CA\n");
        g_CryptoVerifyCa_Random(atoi(argv[2]), g_RandomOut);
        TF("The Respond random from TA just like follow:\n");
        g_CA_PrintfBuffer(g_RandomOut, atoi(argv[2]));
    }

    /* The sha1 result should be:
    0x21, 0x9b, 0x5b, 0x8b, 0x25, 0x6f, 0x0e, 0x52, 0xcb, 0x2f, 0xfe, 0xfd, 0x6c, 0x47, 0xd7, 0xb4,
    0x44, 0x00, 0x57, 0xc3
    */
    if(0 == memcmp(argv[1], "sha1", 4))
    {
        
        TF("Entry sha1 CA\n");
        g_CryptoVerifyCa_Sha(g_ShaTestBuf, sizeof(g_ShaTestBuf), EN_OP_SHA1, g_ShaOutput, 20);
        TF("The Respond hash data from TA just like follow:\n");
        g_CA_PrintfBuffer(g_ShaOutput, 20);
    }


    /* The sha256 result should be:
    0xda, 0x52, 0xe9, 0xc2, 0x53, 0xae, 0x03, 0x30, 0xbd, 0x97, 0x3f, 0xa5, 0xf3, 0xea, 0x51, 0x1d, 
    0x31, 0x0a, 0xdf, 0x1f, 0x0a, 0xc0, 0x0e, 0x62, 0x0f, 0x2d, 0x5e, 0x99, 0xf5, 0xc8, 0x6b, 0x8f
    */
    if(0 == memcmp(argv[1], "sha256", 6))
    {
        
        TF("Entry sha256 CA\n");
        g_CryptoVerifyCa_Sha(g_ShaTestBuf, sizeof(g_ShaTestBuf), EN_OP_SHA256, g_ShaOutput, 32);
        TF("The Respond hash data from TA just like follow:\n");
        g_CA_PrintfBuffer(g_ShaOutput, 32);
    }
    
    if(0 == memcmp(argv[1], "hmac", 4))
    {
        
        TF("Entry hmac CA\n");
        g_CryptoVerifyCa_hmac(atoi(argv[2]), g_HmacOutput, atoi(argv[3]));
        TF("The Respond HMAC data from TA just like follow:\n");
        g_CA_PrintfBuffer(g_HmacOutput, atoi(argv[2]));
    }

    if(0 == memcmp(argv[1], "base64", 6))
    {
        
        TF("Entry base64 CA\n");
        if(0 == memcmp(argv[2], "enc", 3))
        {
            g_CryptoVerifyCa_base64(g_Base64Raw, 372, g_Base64out, 500, 1U);
        }
        else if(0 == memcmp(argv[2], "dec", 3))
        {
            g_CryptoVerifyCa_base64(g_Base64enced, 496, g_Base64out, 500, 2U);
        }
        TF("The Respond HMAC data from TA just like follow:\n");
        g_CA_PrintfBuffer(g_Base64out, 500);
    }
    


    if(0 == memcmp(argv[1], "aes", 3))
    {
        
        TF("Entry aes operaton\n");
        memset(g_AesOutpUT, 0, 256);
        l_Aes_Test(argv[2], argv[3]);
        g_CA_PrintfBuffer(g_AesOutpUT, 80);
    }


    if(0 == memcmp(argv[1], "pbkdf", 5))
    {
        
        TF("Entry generate pbkdf CA\n");
        g_CryptoVerifyCa_Pbkdf(atoi(argv[2]), g_PbkdfOut);
        g_CA_PrintfBuffer(g_PbkdfOut, atoi(argv[2]));
    }

    if(0 == memcmp(argv[1], "rsa1024", 7))
    {
        
        TF("Entry rsa1024 operation\n");
        /* rsa1024 + sign/verify/enc/dec + pcks1/nopadding */
        l_Rsa_Test(argv[2], EN_KEY_1024, g_Output1024, 128);
        if(0 == memcmp(argv[2], "verify", 6))
        {
            TF("The verify result is: %s\n", g_Output1024);
        }
    }  

    if(0 == memcmp(argv[1], "rsa2048", 7))
    {
        
        TF("Entry rsa1024 operation\n");
        /* rsa1024 + sign/verify/enc/dec + pcks1/nopadding */
        l_Rsa2048_Test(argv[2], EN_KEY_2048, g_Output2048, 256);
        if(0 == memcmp(argv[2], "verify", 6))
        {
            TF("The verify result is: %s\n", g_Output2048);
        }
    }  
    return 0;
}
  • CA_TEEC_InitializeContext_TEEC_OpenSession
static int l_CryptoVerifyCa_TaskInit(void)
{
        TEEC_Result result;
        int l_RetVal = OK;
        
        /**1) Check if need to do task initialization operation */
        if(false == g_TaskInitFlag)
        {
                result = TEEC_InitializeContext(NULL, &g_TaskContext);
                if(result != TEEC_SUCCESS) 
                {
                        TF("InitializeContext failed, ReturnCode=0x%x\n", result);
                        l_RetVal= FAIL;
                } 
                else 
                {
                        g_TaskInitFlag = true;
                        TF("InitializeContext success\n");
                        l_RetVal = OK;
                }
        }
        
        return l_RetVal;
}


static int l_CryptoVerifyCa_OpenSession(TEEC_Session* session)
{
        TEEC_Result result;
        int l_RetVal = FAIL;
        uint32_t origin;

        result = TEEC_OpenSession(&g_TaskContext, session, &svc_id, TEEC_LOGIN_PUBLIC, NULL, NULL, &origin);
        if(result != TEEC_SUCCESS) 
        {
                TF("OpenSession failed, ReturnCode=0x%x, ReturnOrigin=0x%x\n", result, origin);
                g_TaskInitFlag = false;
                l_RetVal = FAIL;
        } 
        else 
        {
                TF("OpenSession success\n");
                l_RetVal = OK;
        }

        return l_RetVal;
}

  • TA_CreateEntryPoint
  • TA_OpenSessionEntryPoint
  • TEEC_InvokeCommand
static int l_CryptoVerifyCa_SendCommand(TEEC_Operation* operation, TEEC_Session* session, uint32_t commandID)
{
        TEEC_Result result;
        int l_RetVal = FAIL;
        uint32_t origin;

        result = TEEC_InvokeCommand(session, commandID, operation, &origin);
        if (result != TEEC_SUCCESS) 
        {
                TF("InvokeCommand failed, ReturnCode=0x%x, ReturnOrigin=0x%x\n", result, origin);
                l_RetVal = FAIL;
        } 
        else 
        {
                TF("InvokeCommand success\n");
                l_RetVal = OK;
        }


        return l_RetVal;
}
  • TA_InvokeCommandEntryPoint
  • TEEC_CloseSession & TEEC_FinalizeContext
int g_CryptoVerifyCa_Random(UINT32 len, CHAR* output)
{
        TEEC_Session   l_session;    /* Define the session of TA&CA */
        TEEC_Operation l_operation;  /* Define the operation for communicating between TA&CA */
        int l_RetVal = FAIL;       /* Define the return value of function */

        /**1) Initialize this task */
        l_RetVal = l_CryptoVerifyCa_TaskInit();
        if(FAIL == l_RetVal)
        {
                goto cleanup_1;
        }

        /**2) Open session */
        l_RetVal = l_CryptoVerifyCa_OpenSession(&l_session);
        if(FAIL == l_RetVal)
        {
                goto cleanup_2;
        }

        /**3) Set the communication context between CA&TA */
        memset(&l_operation, 0x0, sizeof(TEEC_Operation));
        l_operation.started = 1;
        l_operation.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT,TEEC_NONE, 
                                                                                            TEEC_NONE, TEEC_NONE);
        l_operation.params[0].tmpref.size = len;
        l_operation.params[0].tmpref.buffer = output;

        /**4) Send command to TA */
        l_RetVal = l_CryptoVerifyCa_SendCommand(&l_operation, &l_session, CMD_GEN_RANDOM_OPER);
        if(FAIL == l_RetVal)
        {
                goto cleanup_3;
        }

        /**5) The clean up operation */
        cleanup_3:
                TEEC_CloseSession(&l_session);
        cleanup_2:
                TEEC_FinalizeContext(&g_TaskContext);
        cleanup_1:
                return l_RetVal;
}

  • TA_CloseSessionEntryPoint
  • TA_DestroyEntryPoint
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 系列一中实现了QEMU虚拟机中的OP-TEE环境搭建,对于如何直观的理解OP-TEE的文件系统,本文中通过对其中集...
    Hiro_Wang阅读 6,494评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 休息了两天。 心塞。 不会好好说话
    ZML1024阅读 209评论 0 0
  • 一片叶子,就好像秋天的翅膀,被微风吹下,又被狂风推上。 一片叶子,好像许多飞舞的蝴蝶,从大树妈妈身上飞下来,飞向温...
    xiaoxiao果阅读 561评论 0 3