本文研究了openHarmony的security_huks模块的结构,尝试做一个较清楚的整体架构梳理,如有错误敬请指正!
该模块分为三个大文件夹framework,interface,service,整体上可以分为四个独立的子模块:lite版的Client-Service模型,crypto_lite封装的CipherModule ,标准版的IPC_Client-Service模型,以及可在本地调用的API接口。同时本文还新增了关于OpenSSL & Mbedtls与常见的密钥集合的总结。
文章目录
首先先放一张整体的文件夹导图。
我们依次来看这四个子模块。
1.lite版的Client-Service模型
该模型实现在frameworks/huks_lite/hw_keystore_sdk部分:
①common:该文件夹涉及安全密钥服务的基础数据结构、加解密计算、日志、内存申请与释放、密钥格式转换、hardware_udid的获取等内容,提供一些基本函数与加密函数。
②soft_service:
-
a.hks_storage.h:该文件管理密钥数据在缓冲区数据与文件之间的来往,包括密钥数据的封装存储、同步文件的写入、密钥数据的销毁等。
-
b.hks_file.h:该文件给hks_storage提供基本的文件操作函数。
-
c.hks_rkc.h:rkc即root key component,负责管理keystore files(KSF)的生成、读写、销毁等,还可通过此获取rawKey,值得注意的是,rawKey包含了 udid 的数据。KSF文件用于存储加密后的mainKey,且每个KSF中的数据都必须一样。mainKey用于密钥的派生,mac计算。密钥服务的实现需要有rkc系统的支持,是整个服务启动不可缺少的重要部分。
-
d.hks_service.h:即lite版密钥服务端的接口函数,各类加密算法也在此实现。
③hks_access.c:管理client与service之间的来往,client端的请求发送给access模块,它将该请求交给相应的service端处理函数,再将处理结果返回给client。具体地,这里client端的请求被放置到sec_mod_msg 消息盒子中,同时还有一个全局的处理函数指针集合,盒子中的消息传递给 __hks_handle_secure_call() 函数,该函数调用对应的函数即完成消息处理,然后将处理结果写回消息盒子,client端从盒子中读走结果。
④hks_client.h:lite版密钥服务客户端接口函数。
huks_lite的大致逻辑框架如下:
2.crypto_lite
涉及的文件夹有frameworks/crypto_lite
这个部分构造了 CipherModule 类,用于AES与RSA加解密处理。本人查了对于该部分的头文件、函数接口的引用,发现这部分似乎与其他子模块孤立,并没有引用 CipherModule 类,且有一个 jsi.h 的头文件,猜测可能是用于前端的开发。
在看标准版的 IPC 模型之前先看看 OpenSSL 与 Mbedtls,这两大框架在密钥加解密中起到重要的支撑作用。(下面复制一段队长博客中的内容原文链接)
3.OpenSSL VS Mbedtls
在安全模块中我们看到了两个安全算法实现框架:OpenSSL和Mbedtls,那么在实际的应用场景中我们该如何选择呢?
其实鸿蒙已经给出了答案:lite版本中主要调用了mbedtls框架,standard版本中主要调用了OpenSSL框架,那么我们看看它们两个各自的特点吧!
OpenSSL
OpenSSL主要实现了三类算法:
-
对称算法比如经典的AES,然后实现了四种主要的加密模式:
- 电子密码本模式(ECB):将加密数据分成若干组,每组的大小跟加密密钥长度相同,然后每组使用相同的密钥进行加密
- 加密块链模块(CBC):同样时将明文分成固定长度的块,然后将前面一个加密快输出和下一个要加密的明文进行异或操作,以此类推,由于第一明文加密时没有前面加密的密文,所以需要一个初始化向量,也就是我们在代码中可以看到的参数 IV(init vector)
- 加密反馈模式(CFB):面向字符的应用程序要使用流加密法则可以使用CFB模式
- 输出反馈模式(OFB):与CFB类似
-
摘要算法
- 能够产生特殊输出格式的算法——也就是固定长度的输出,无论输入字串的长度如何。根据一定的运算规则对原始数据进行某种形式的提取得到摘要,原数据变化则摘要必然变化,理论上信息摘要算法不可逆,所以一般用来做数据完整性的验证
- SHA族算法:SHA1输出结果为20字节
- MD族算法:MD5输出为8字节
-
公钥算法——也称非对称算法比如著名的RSA算法,既能够应用于数据加密也能够应用于数字签名
- RSA:基于数论的非对称密码体制,是一种分组密码体制
- DSA:数字签名算法,其安全性基于解离散对数的困难性
- DH:密钥交换算法,其安全性基于有限域上计算离散对数的困难性
- ECC:椭圆曲线密码体制,依据是定义在椭圆曲线点群上的离散对数问题的难解性
OpenSSL的另一特点就是回调函数——一般定义在数据结构中,是一个函数指针。通过回调函数——让openSSL的函数来调用它,即用户调用库中函数,而库中函数又调用用户提供的函数,方便用户对OpenSSL函数的操作
其实现的功能:
- SSL协议的实现
- 对称、非对称和摘要算法的实现
- 大数运算的实现
- 非对称密钥的生成
- 证书请求(PKCS10)编解码
- 数字证书编解码
- 数字证书验证
- PKCS7标准实现
- PKCS12个人证书格式实现等
从代码目录中我们也可以看到在鸿蒙的安全模块中,实现了那些功能:
1. aes
2. curve25519
3. ecc
4. ed25519tox25519
5. hash
6. hmac
7. kdf
Mbedtls
主要面向嵌入式产品,加入加密和SSL/TLS功能
从功能的角度来看,大致分为三个部分:
- SSL/TLS协议实施
- 加密库
- X509证书处理库
为什么会广泛应用于轻量级领域呢?——因为它足够小但是全而且提供了强大的API,即开即用
同样我们可以在安全模块的代码中看到实现了的算法和功能:
1. ed25519
2. aes
3. bn
4. ecc
5. ecdh
6. ecdsa
7. hash
8. hmac
9. kdf
10. rsa
11. x25519
这里两个框架有个共同点——都应用了X509证书标准,为统一进行数据处理提供了方便
注:
SSL:Secure socket layer 由SSL记录协议和握手协议组成
TLS:Transport layer security
TLS与SSL在传输层与应用层之间对网络连接进行加密,保证数据的安全
当然OpenSSL和Mbedtls都实现了它们
4.标准版的IPC_Client-Service模型
首先贴一张IPC_Client-Service的图:
涉及的文件夹有frameworks/huks_standard/main,service/huks_standard
4.1 standard版IPC通信client模型
涉及到的文件夹有frameworks/huks_standard/os_dependency,frameworks/huks_standard/main/common/src
-
a.main/common/src:该文件夹下的函数负责client端的基础参数检查与构造,为加解密准备必要的参数等,为其他子部分,如client端的接口,各类加解密的算法实现(service端要用到的mbedtls/openssl框架)等提供必需的基础支持。
-
b.hks_ipc_check.h:检查密钥、参数集等数据的有效性,并保证其大小的合法性。
-
-c.hks_ipc_serialization.h:负责将client端的请求数据打包(pack),便于向service端发送。
-
d.hks_ipc_slice.h:在面对密钥的签名验签、加解密等服务时,往往遇到传输数据过大的情况,需要将数据进行切分传输,该子模块就实现了这样的功能。
-
e.hks_request.h:给client提供了发送请求的接口,并从该接口获得服务处理的结果反馈。
-
f.hks_client_service_ipc.c:集合了前面几个子模块提供的支持,提供了client端的服务请求接口。
client_service_ipc的大致逻辑结构:
除了client端的接口,这部分中还包括了日志输出、内存分配管理以及进程名与硬件标识符的获取,进程名唯一对应KSF,用于对KSF文件的标识;硬件标识符需要添加到rawKey中,用于生成rawKey。
4.2 standard版IPC通信service模型
service/huks_standard下有两大子文件夹:
4.2.1 huks_engine/core
a.hks_rkc_rw:与前面frameworks/huks_lite/hw_keystore_sdk中的rkc类型,管理密钥存储文件(KSF)数据与缓冲区数据之间的来往,包括rootKey的各类信息通过缓冲区写入到KSF文件,以及读取KSF文件数据到缓冲区中。值得注意的是,每次在文件中写入rootKey信息会在最后加入哈希值,从文件中读取密钥信息时在最后会根据计算一次哈希值,然后与其比对用于验证数据的完整性。
b.hks_rkc.c: rkc系统的建立,包括创建或者载入rkc的keystore files(KSF)(由hks_rkc_rw.c提供相应的文件读写支持),该文件用于存储加密后的mainKey,且每个KSF中的数据都必须一样。mainKey用于密钥的派生,mac计算,keyblob通过派生密钥完成对其他密钥的加解密、rawKey的获取等;另外轻量存储端(STORAGE_LITE)直接使用mainKey实现MAC的计算。此外hks_rkc还负责对hksService密钥服务的初始化,是很重要的子模块。
c.hks_keyblob.c: 在签名验签、消息的加解密等服务中传递的密钥往往是加密过的,通过KeyNode的构造获取得到原始密钥rawKey才能继续后续的操作,对应地,该子模块还负责密钥的加密,即keyblob的构造;另外通过它提供的接口还可以获取rawKey。
d.hks_auth.h:提供加解密、签名验签的认证功能。
e.hks_crypto_hal.h:其中的函数链接到 mbedtls 和 openssl 中实现(crypto_engine),调用的其实是这两个里面的函数。
f.hks_core_service.h: services函数的底层实现,由前面的几块提供相应的支持。
g. crypto_engine:使用mbedtls与openssl两大框架,前面已经提到,它为各种密钥服务提供具体的函数实现。
4.2.2 huks_service
-
a.core
- I. hks_access.c:链接到hks_passthrough_access.c,实则调用hks_core_service的函数
- II. hks_client_check.c:检查各个密钥服务函数的参数
- III. hks_client_service_adapter.c:实现客户端和服务端数据格式的转换——X509格式与其他格式的适配
- IV. hks_operation.c:在具体的密钥服务中管理加解密、签名验签、mac计算等服务进程的三个阶段:initial,update,final
- V. hks_storage.c:管理密钥文件系统,包括密钥存储、读写等
- VI. hks_client_service.c:由参数检查、密钥格式适配、密钥文件系统等子模块支持,
各项密钥服务由huks_engine/hks_core_service.c提供,它为后面的ipc_service端提供服务处理的函数接口
-
b.os_dependency
I. ipc:hks_ipc_service作为IPC服务端的函数接口,由前面的几个子模块作为底层支持,通过response函数将处理结果写入MessageParcel缓冲区,供client端取走。值得注意的是,hks_response.cpp提供了获取进程名的函数HksGetProcessNameForIPC(),该进程名与进程的UID直接绑定,每个进程名与KSF一一对应,有了processName就知道密钥该存到哪里了。此外进程的UID
II. sa:SA即系统能力(SystemAbility),作为hks服务的管理者,管理该服务的初始化、启动、停止,以及对各类消息的处理,保证该服务的正常运行。值得一提的是,与前面的frameworks/huks_lite/hw_keystore_sdk类似,sa有一个处理各类服务的全局变量g_hksIpcMessageHandler,它有一个存放了针对各类消息请求的函数指针的集合,根据消息类型即可调用对应的hks_ipc_service提供的接口函数,只不过这里使用的是MessageParcel缓冲区,前者使用消息盒子通信。
III. posix:提供基本文件操作的标准版与lite版函数,供hks_storage,huks_engine中的hks_keyblob调用。
5.各类Key集合
看过了大的模型,接下来看看小的细节,即在代码中遇到的各类Key:
a. rootKey:更全面地说它应该叫root main Key,在文件 services\huks_standard\huks_engine\main\core\src\hks_rkc.c 中可以看到其中的信息:
/* the data of main key */
struct HksRkcMk g_hksRkcMk = { false, { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0 }, {0} };
/* the additional data of main key. 'H', 'K', 'S', 'R', 'K', 'C', 'M', 'K' */
const uint8_t g_hksRkcMkAddData[HKS_RKC_MK_ADD_DATA_LEN] = { 0x48, 0x4B, 0x53, 0x52, 0x4B, 0x43, 0x4D, 0x4B };
整个密钥服务的一开始,会将 rkc 加密(它被派生过)过的 mainKey 存入各个 keystore files 中,hks_keyblob.c 会从中获取到 mainKey,经过派生得到 derivedKey,而进一步 derivedKey 用于对keyblob的加解密。所以看来,rkc 中的 mainKey 用于对keyblob的加解密。此外在 STORAGE_LITE 端(轻量级存储) rkc的 mainKey 被用于计算 MAC。
b. genKey:不是单独的什么密钥类型,应该是 generate key 的缩写
c. PublicKey,PrivateKey与 agreeKey:生成密钥有两种方式,默认与agree方式,当通过 agree 方式生成密钥时,需要有 PublicKey 与 PrivateKey,开始时 PublicKey 与 PrivateKey 都存放在文件中,从文件中获取后将二者结合为密钥对,称为 DeriveMainKey,把它传递给生成函数后,该函数需要将它解密为两个 rawKey,再对着两个 rawKey 再结合再加密,得到 agreeKey,最后 agreeKey 再封装(即 keyblob 的加密)为 keyBlob 存到文件中去。
d. WrapKey与UnWrapKey:目前的源码中它的处理函数直接 return 0。推测可能是为了更复杂的密钥加密存储。
e. RawKey:从密钥存储文件中拿到的 key 都是加密过的,当要进行例如签名、验签、消息的加解密、密钥派生等操作时需要将 key 解密为 keyNode,其中就包含了 rawKey 数据,有了它才能继续后面的操作。
f. MainKey(或者说MasterKey)与DeriveKey:mainKey是被派生的密钥,从文件中获得后需要解为 rawKey,然后用 rawKey 去派生,得到 DeriveKey。
g.ImportKey:客户端会发送导入密钥的请求,在请求中包含了要导入的密钥数据,服务端经过参数检查后,将该密钥封装为 keyblob,然后存入文件中即可。
h. ExportKey:具体指的是 ExportPublicKey 服务,客户端发送要导出的密钥的 keyAlias,服务端根据此从文件中获取对应的密钥,解密获得 rawKey,从中获取 publicKey 发送给客户端。
贴一张关系图:
6.本地API接口
涉及的文件夹有interface,frameworks/huks_standard/core下的hks_local_engine,它可以分为3个子部分:
6.1 hks_client_ipc
这里的client_ipc不同于前面提到的standard的client模型,它链接到hks_client_service_passthrough.c
依据:由宏定义*ifndef CUT_AUTHENTICATE*指明,在hks_api.c的源码中可以看到:
HKS_API_EXPORT int32_t HksRefreshKeyInfo(void)
{
#ifndef _CUT_AUTHENTICATE_
return HksClientRefreshKeyInfo();
#else
return HKS_ERROR_NOT_SUPPORTED;
#endif
}
且在hks_client_service_passthrough.c中有:
#include "hks_client_ipc.h"
#include "hks_client_service.h"
#include "hks_get_process_info.h"
#include "hks_log.h"
#ifndef _CUT_AUTHENTICATE_
...//此处省略其他函数
int32_t HksClientRefreshKeyInfo(void)
{
char *processName = NULL;
if (HksGetProcessName(&processName) != HKS_SUCCESS) {
HKS_LOG_E("get process name failed");
return HKS_ERROR_INTERNAL_ERROR;
}
struct HksBlob processNameBlob = { strlen(processName), (uint8_t *)processName };
/*这里直接调用service的处理函数*/
return HksServiceRefreshKeyInfo(&processNameBlob);
}
证明这里的client_ipc是链接到hks_client_service_passthrough.c的。
在上面的代码中可以看到,函数最后直接调用service端的函数,没有sa的介入,故中间不经过request和response,而且根据一些参数特征(paramSet中的isKeyAlias),某些服务可直接在本地完成,即调用hks_local_engine提供的接口,而不需要调用service端的接口。
6.2 hks_local_engine
作为本地的密钥服务实现函数,提供了哈希算法、MAC计算、密钥生成、明文加密、签名验签等函数,供hks_api调用。
6.3 hks_api
由上面两部分作支持,提供本地可调用的密钥服务的API接口。
interface中表现出的逻辑图如下:
7.小结
该模块不可谓不复杂,有很多细节值得去研究学习,比如宏的使用、进程间的通信、消息盒子等,同时可以发现lite和standard两种模式下有很多相似的地方,比如进程间的共享、全局的服务维护者等。感谢阅读,如有错误敬请指正!