- 一、Keystore
- 二、Keystore架构及接口函数
- 1. Keystore组件架构
- 2. IKeymasterDevice.hal中的几个重要接口函数
- 2.1 begin函数
- 2.2 update函数
- 2.3 finish函数
- 2.4 abort函数
- 3. Keymaster TA
- 4. 对称密码函数API
- 三、从Keystore到Keymaster的完整分析
- 1. cts问题
- 2. 代码流程分析
- 2.1 模块调用关系
- 2.2 代码分析
keystore主要是对密钥库的控制 *** 作,包括密钥的生成导入导出、加解密、签名验签、访问控制等。
概念的详细介绍就请看看google官网的介绍。本篇主要是想总结一下keystore的使用流程,貌似其他博客讲这个流程的不多,就整理一篇出来。
原创不易,转载请标明出处 https://blog.csdn.net/jackone12347/article/details/122252644
二、Keystore架构及接口函数 1. Keystore组件架构keystore涉及到的模块之间的关系,一共有四个角色:
Andriod Keystore: 提供Android framework API,封装密钥库相关的接口; Keystore:守护进程,通过Binder提供密钥库功能,通过AIDL与framework keystore通讯; HIDL Keymaster: HIDL进程,封装调用keymaster TA的密钥函数接口;如android.hardware.keymaster@xxx-xxx KeyMaster TA: TEE中的TA,提供安全的密钥库 *** 作和安全环境
架构图如下:
需要看一下这几个相关的接口,HAL层的IKeymasterDevice.hal都有介绍,下面是我理解的几个,因为和接下来的章节中分析CTS问题是相关的。
2.1 begin函数作用:使用指定的密钥开始加密 *** 作。 如果一切顺利,begin() 必须返回 ErrorCode::OK 并创建一个 *** 作句柄,该句柄必须传递给后续对 update()、finish() 或 abort() 的调用。
函数原型:
begin(KeyPurpose purpose, vec2.2 update函数keyBlob, vec inParams, HardwareAuthToken authToken) generates (ErrorCode error, vec outParams, OperationHandle operationHandle);
作用:向正在进行的加密 *** 作提供数据并可能从begin()接收输出。
函数原型:
update(OperationHandle operationHandle, vecinParams, vec input, HardwareAuthToken authToken, VerificationToken verificationToken) generates (ErrorCode error, uint32_t inputConsumed, vec outParams, vec output);
说明:
1、为了为缓冲区处理提供更大的灵活性,此方法的实现具有使用比提供的更少的数据的选项。
调用者负责循环到在后续调用中提供其余数据。 必须返回consumed的输入量在 inputConsumed 参数中。
实现必须始终至少消耗一个字节,除非operation不能再接受;
如果提供的字节多于0且0字节是消耗,调用者必须认为这是一个错误并中止 *** 作。
2、作为update的结果,实现还可以选择返回多少数据。 这仅与加密和解密 *** 作相关,因为签名和验证在完成之前不会返回任何数据。 建议尽早返回数据,而不是缓冲它。
2.3 finish函数作用:完成以 begin() 开始的加密 *** 作并使 operationHandle 无效。此方法是 *** 作中最后调用的方法,因此必须返回所有处理过的数据。
函数原型:
finish(OperationHandle operationHandle, vec2.4 abort函数inParams, vec input, vec signature, HardwareAuthToken authToken, VerificationToken verificationToken) generates (ErrorCode error, vec outParams, vec output);
中止以 begin() 开始的加密 *** 作,释放所有内部资源并使 *** 作句柄无效。
函数原型:
abort(OperationHandle operationHandle) generates (ErrorCode error);3. Keymaster TA
keymaster_operation_t结构体,TA中会反复用到这个结构体中的数据。
@ ta/include/operations.h typedef struct { uint8_t key_id[TAG_LENGTH]; keymaster_key_blob_t *key; keymaster_blob_t nonce; keymaster_blob_t last_block; keymaster_operation_handle_t op_handle; keymaster_purpose_t purpose; keymaster_padding_t padding; keymaster_block_mode_t mode; keymaster_blob_list_item_t *sf_item; TEE_Time *last_access; TEE_OperationHandle *operation; TEE_OperationHandle *digest_op; size_t prev_in_size; uint32_t min_sec; uint32_t mac_length; uint32_t digestLength; uint32_t a_data_length; uint8_t *a_data; bool do_auth; bool got_input; bool buffering; bool padded; bool first; } keymaster_operation_t;
keymaster_blob_t结构体
typedef struct { uint8_t* data; size_t data_length; } keymaster_blob_t;4. 对称密码函数API
Cryptographic API调用流程
- some_function() (Trusted App) - [1] TEE_*() User space (libutee.a) ------- utee_*() ---------------------------------------------- [2] tee_svc_*() Kernel space [3] crypto_*() (libtomcrypt.a and crypto.c) [4] (libtomcrypt.a)
对称密码函数定义了执行对称密码 *** 作(例如AES)的方式,涵盖分组密码和流密码。
Cryptographic Operations API - Symmetric Cipher Functions
void TEE_CipherInit(TEE_OperationHandle operation, const void *IV, uint32_t IVLen)
该函数启动对称密码 *** 作, *** 作必须关联一个密钥。
TEE_Result TEE_CipherUpdate(TEE_OperationHandle operation, const void *srcData, uint32_t srcLen, void *destData, uint32_t *destLen)
该函数用来加密或解密输入数据,输入数据不必是块大小的倍数,除非对此函数的一个或多个调用提供了足够的输入数据,否则不会生成任何输出。
TEE_Result TEE_CipherDoFinal(TEE_OperationHandle operation, const void *srcData, uint32_t srcLen, void *destData, uint32_t *destLen)
完成密码 *** 作,处理以前未通过调用TEE_CipherUpdate函数处理的数据以及srcData中提供的数据。随后 *** 作句柄可以重用或重新初始化。
三、从Keystore到Keymaster的完整分析直接干巴巴地看源码,有时候也不是很直观地看出这几大模块是如何串联起来 及如何完成从上到下的功能串调。
我们带着一个cts的问题来分析一下相关的流程吧。
运行
adb shell am instrument -r -e class android.keystore.cts.AES128CBCNoPaddingCipherTest#testDoFinalResets -w android.keystore.cts/androidx.test.runner.AndroidJUnitRunner
报错:
javax.crypto.IllegalBlockSizeException at android.security.keystore.AndroidKeyStoreCipherSpibase.engineDoFinal(AndroidKeyStoreCipherSpibase.java:490) at javax.crypto.Cipher.doFinal(Cipher.java:2055) at android.keystore.cts.BlockCipherTestbase.doFinal(BlockCipherTestbase.java:1400) at android.keystore.cts.BlockCipherTestbase.assertDoFinalResetsCipher(BlockCipherTestbase.java:594) at android.keystore.cts.BlockCipherTestbase.testDoFinalResets(BlockCipherTestbase.java:563) at android.keystore.cts.AES128CBCNoPaddingCipherTest.testDoFinalResets(AES128CBCNoPaddingCipherTest.java:19) at java.lang.reflect.Method.invoke(Native Method) at junit.framework.TestCase.runTest(TestCase.java:168) at junit.framework.TestCase.runBare(TestCase.java:134) at junit.framework.TestResult.protect(TestResult.java:115) at androidx.test.internal.runner.junit3.AndroidTestResult.runProtected(AndroidTestResult.java:73) at junit.framework.TestResult.run(TestResult.java:118) at androidx.test.internal.runner.junit3.AndroidTestResult.run(AndroidTestResult.java:51) at junit.framework.TestCase.run(TestCase.java:124) at androidx.test.internal.runner.junit3.NonLeakyTestSuite$NonLeakyTest.run(NonLeakyTestSuite.java:62) at androidx.test.internal.runner.junit3.AndroidTestSuite.run(AndroidTestSuite.java:101) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923) Caused by: android.security.KeyStoreException: Keystore consumed 0 of 8 bytes provided. at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:143) at android.security.keystore.AndroidKeyStoreCipherSpibase.engineUpdate(AndroidKeyStoreCipherSpibase.java:338) at javax.crypto.Cipher.update(Cipher.java:1682) at android.keystore.cts.BlockCipherTestbase.update(BlockCipherTestbase.java:1454) at android.keystore.cts.BlockCipherTestbase.assertDoFinalResetsCipher(BlockCipherTestbase.java:593)2. 代码流程分析
下面针对以上报错内容,进行详细的分析,包含涉及到哪些模块,以及是怎么完成这项AES测试的。
2.1 模块调用关系根据前面的内容得知一共有四个角色,其实还应该包含一个角色,就是JAVA应用程序APK,即keystore调用者。
所以五个角色为如下:
应用程序APK android.keystore.cts
framework Keystore API
Keystore守护进程
Keymaster: HIDL进程:android.hardware.keymaster@xxx-xxx
Keymaster TA程序
五个角色的调用关系:
cts apk进程通过API ==> framework Keystore API framework Keystore API 通过Binder调用 ==> Keystore进程 Keystore进程通过HIDL ==> Keymaster HAL进程 Keymaster HAL进程 ==> 调用TEE中的keymaster TA2.2 代码分析
顺着cts报错,我们分析一下相关代码。
报错内容:
12-29 13:39:04.818 3206 3228 E TestRunner: Caused by: android.security.KeyStoreException: Keystore consumed 0 of 8 bytes provided. 12-29 13:39:04.818 3206 3228 E TestRunner: at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:143) 12-29 13:39:04.818 3206 3228 E TestRunner: at android.security.keystore.AndroidKeyStoreCipherSpibase.engineUpdate(AndroidKeyStoreCipherSpibase.java:338) 12-29 13:39:04.818 3206 3228 E TestRunner: at javax.crypto.Cipher.update(Cipher.java:1682) 12-29 13:39:04.818 3206 3228 E TestRunner: at android.keystore.cts.BlockCipherTestbase.update(BlockCipherTestbase.java:1454) 12-29 13:39:04.818 3206 3228 E TestRunner: at android.keystore.cts.BlockCipherTestbase.assertDoFinalResetsCipher(BlockCipherTestbase.java:593)
- framework KeyStore API
BlockCipherTestbase.java的assertDoFinalResetsCipher函数如下:
private void assertDoFinalResetsCipher(int opmode) throws Exception { byte[] input = getKatInput(opmode); byte[] expectedOutput = getKatOutput(opmode); createCipher(); initKat(opmode); assertEquals(expectedOutput, doFinal(input)); if ((opmode == Cipher.ENCRYPT_MODE) && (getKatIv() != null)) { // Assert that this cipher cannot be reused (thus making IV reuse harder) try { doFinal(input); fail(); } catch (IllegalStateException expected) {} return; } // Assert that the same output is produced after the above reset assertEquals(expectedOutput, doFinal(input)); assertEquals(expectedOutput, concat( update(subarray(input, 0, getBlockSize() * 3 / 2)), doFinal(subarray(input, getBlockSize() * 3 / 2, input.length)))); assertEquals(expectedOutput, doFinal(input)); // Assert that the IV with which the cipher was initialized is still there after the resets. assertEquals(getKatIv(), mCipher.getIV()); assertAlgoritmParametersIv(getKatIv()); }
getBlockSize()返回的是16字节,刚好是AES128的128bit, concat将两个24字节update和final送入,一共48个字节。
关于AES算法以及工作模式,请参考我写的另一博客AES及其工作模式详解
如果送入的数据是48字节,刚好是16字节的整数倍,没有问题;
但如果一次送入的数据是24字节,这样需要分多次送入,第一次先送入16字节,然后第二次送入8字节进行存储,然后再送入8字节,能拼成一个Blocksize 16字节进行处理。
这项cts测试大概就是这么个目的,想看一下设备中能否处理这种不是16字节整数倍的情况。
=》调用 mCipher.update函数
protected byte[] update(byte[] input) { byte[] output = mCipher.update(input); assertUpdateOutputSize( (input != null) ? input.length : 0, (output != null) ? output.length : 0); return output; }
内部的调用流程如下:
=》android.security.keystore.AndroidKeyStoreCipherSpibase.engineUpdate
=》KeyStoreCryptoOperationChunkedStreamer.update
=》内部类MainDataStream的update实现
=》调用KeyStore.java的update方法
KeyStore》java的update方法,开始binder调用到守护进程Keystore
public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) { OperationPromise promise = new OperationPromise(); try { mBinder.asBinder().linkToDeath(promise, 0); int errorCode = mBinder.update(promise, token, arguments, input); if (errorCode == NO_ERROR) { return interruptedPreservingGet(promise.getFuture()); } else { return new OperationResult(errorCode); } } catch (RemoteException e) { ,,, }
到这里的整个过程基本上都是AOSP的流程,所以问题一般不会出现在原生的代码流程中。
需要继续分析HAL层的调用。
Keystore服务端返回的流程在key_store_service.cpp中,能看到AIDL相关内容了。
调用dev->update函数和HAL keymaster进程通讯。
@system/security/keystore/key_store_service.cpp Status KeyStoreService::update(const ::android::sp& cb, const ::android::sp<::android::IBinder>& token, const ::android::security::keymaster::KeymasterArguments& params, const ::std::vector & input, int32_t* _aidl_return) { ALOGE("key_store_service update entry"); if (!checkAllowedOperationParams(params.getParameters())) { return AIDL_RETURN(ErrorCode::INVALID_ARGUMENT); } auto dev = mKeyStore->getOperationDevice(token); dev->update(token, params.getParameters(), input, [this, cb, token](OperationResult result_) { if (!result_.resultCode.isOk()) { mKeyStore->removeOperationDevice(token); } cb->onFinished(result_); }); ALOGE("key_store_service update done"); return AIDL_RETURN(ResponseCode::NO_ERROR); }
因为每家的keymaster方案,基本上都属于定制,为什么呢?因为HAL封装了和keymaster TA交互的接口。所以每家遇到的CTS问题可能都不大相同,需要具体问题具体分析了。
但各家基本上都会参考GP标准来开发
CA调用接口
TEEC_InvokeCommand(&sess, cmd, &op, &err_origin);
TA调用接口
TEE_CipherUpdate
最后这个CTS的修改是在keymaster TA中修复,具体code就不方便贴了,遇到类似问题时请具体看各家的keymaster TA及TEE中对应的libutee中封装的函数实现。
修复后的复测结果:
# adb shell am instrument -r -e class android.keystore.cts.AES128CBCNoPaddingCipherTest#testDoFinalResets -w android.keystore.cts/androidx.test.runner.AndroidJUnitRunner INSTRUMENTATION_STATUS: class=android.keystore.cts.AES128CBCNoPaddingCipherTest INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: numtests=1 INSTRUMENTATION_STATUS: stream= android.keystore.cts.AES128CBCNoPaddingCipherTest: INSTRUMENTATION_STATUS: test=testDoFinalResets INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: class=android.keystore.cts.AES128CBCNoPaddingCipherTest INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: numtests=1 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: test=testDoFinalResets INSTRUMENTATION_STATUS_CODE: 0 INSTRUMENTATION_RESULT: stream= Time: 8.063 OK (1 test)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)