一、概述
在前面的文中,客户端首先发起start请求,然后服务端进行响应,本文将介绍客户端收到来自服务端的响应(response)消息的处理过程以及客户端针对该响应发起end请求的过程。
二、源码分析
这一模块的源码位于:/base/security/deviceauth。
1. 首先解析该response消息,调用parse_pake_response函数实现:
scss
/*
函数功能:解析pake服务端响应消息
函数参数:
payload:消息负载
data_type:数据类型
函数返回值:
成功:返回存放解析完成后的数据结构pake_start_response_data
失败:NULL
*/
void *parse_pake_response(const char *payload, enum json_object_data_type data_type)
{
struct pake_start_response_data *pake_response =
(struct pake_start_response_data *)MALLOC(sizeof(struct pake_start_response_data));//为"响应消息"内容申请空间
if (pake_response == NULL) {
return NULL;
}
(void)memset_s(pake_response, sizeof(*pake_response), 0, sizeof(*pake_response));//清空该空间
json_pobject obj = parse_payload(payload, data_type);//解析消息负载部分,将json格式的字符串数据解析成cjson结构体对象
if (obj == NULL) {
LOGE("Parse Pake Response parse payload failed");
goto error;
}
/* challenge */
int32_t result = byte_convert(obj, FIELD_CHALLENGE, pake_response->challenge.challenge,
&pake_response->challenge.length, CHALLENGE_BUFF_LENGTH);//获取挑战值,字节转换函数,将十六进制字符串转换为byte数组
if (result != HC_OK) {
LOGE("Parse Pake Response failed, field is null in challenge");
goto error;
}
/* salt */
result = byte_convert(obj, FIELD_SALT, pake_response->salt.salt,
(uint32_t *)&pake_response->salt.length, HC_SALT_BUFF_LEN);//获取salt值,字节转换函数,将十六进制字符串转换为byte数组
if (result != HC_OK) {
LOGE("Parse Pake Response failed, field is null in salt");
goto error;
}
/* epk */
result = byte_convert(obj, FIELD_EPK, pake_response->epk.epk,
&pake_response->epk.length, PAKE_EPK_LENGTH);//获取epk值,字节转换函数,将十六进制字符串转换为byte数组
if (result != HC_OK) {
LOGE("Parse Pake Response failed, field is null in epk");
goto error;
}
/* version */
json_pobject obj_ver = get_json_obj(obj, FIELD_VERSION);//获取版本号
bool ret = parse_version(obj_ver, &pake_response->self_version, &pake_response->self_support_version);//解析版本号
if (!ret) {
LOGE("Parse Pake Response failed, field is null in version");
goto error;
}
free_payload(obj, data_type);
return (void *)pake_response;//返回解析完成的消息数据
error:
free_payload(obj, data_type);
FREE(pake_response);
return NULL;
}
/*
函数功能:如果消息负载为json格式的字符串,则将json格式的数据解析成cjson结构体对象;如果消息负载为cjson结构体对象类型,则返回原内容;否则返回NULL
函数参数:
payload:消息负载地址
data_type:数据类型:只有JSON_STRING_DATA和JSON_OBJECT_DATA
*/
void *parse_payload(const char *payload, enum json_object_data_type data_type)
{
if (data_type == JSON_STRING_DATA) {//如果json对象类型为:json格式的字符串
return parse_json(payload);//将json格式的数据解析成cjson结构体对象
} else if (data_type == JSON_OBJECT_DATA) {//如果json对象类型为:cjson结构体对象
return (void *)payload;//直接返回原内容
} else {
return NULL;//如果都不是,返回NULL
}
}
/*
函数功能:字节转换函数,将十六进制字符串转换为byte数组
函数参数:
obj:cjson结构体对象
field:目标字段
hex:目标数组地址
length:数组长度
max_len:最大长度
函数返回值:
成功:0
失败:error number
*/
int32_t byte_convert(json_pobject obj, const char *field, uint8_t *hex, uint32_t *length, uint32_t max_len)
{
const char *str_json = get_json_string(obj, field);//获取field对象的字符串值
if (str_json == NULL) {
return HC_INPUT_ERROR;
}
uint32_t len = strlen(str_json);//获取字符串长度
if ((len / BYTE_TO_HEX_OPER_LENGTH) > max_len) {
return HC_INPUT_ERROR;
}
if (hex_string_to_byte(str_json, len, hex) != HC_OK) {//将十六进制字符串转换为byte数组
return HC_INPUT_ERROR;
}
*length = len / BYTE_TO_HEX_OPER_LENGTH;//byte数组长度
return HC_OK;
}
2. 接着进入消息处理阶段,根据相关消息字段,应调用proc_pake_response_message函数进行处理。
rust
/*
函数功能:处理pake响应消息
函数参数:
handle:hichain实例
nav:导航消息,消息头格式
receive:接收的消息
send:待发送消息
函数返回值:
成功:0
失败:error num
*/
static int32_t proc_pake_response_message(struct hichain *handle, struct header_analysis *nav,
struct message *receive, struct message *send)
#if !(defined(_CUT_PAKE_) || defined(_CUT_PAKE_CLIENT_))
{
DBG_OUT("Object %u proc pake %d response message", pake_client_sn(handle->pake_client), nav->msg_type);
int32_t ret;
if (nav->msg_type == PAKE_START_MSG) {//根据消息类型进行不同的处理
ret = send_pake_end_request(handle->pake_client, receive, send);//准备发送pake end请求,客户端根据该响应消息准备回复数据
} else if (nav->msg_type == PAKE_END_MSG) {
ret = receive_pake_end_response(handle->pake_client, receive);//接收pake end响应
if (ret == HC_OK) {
handle->cb.set_session_key(&handle->identity, &handle->pake_client->service_key);//设置会话密钥为service_key
(void)memset_s(handle->pake_client->service_key.session_key, HC_SESSION_KEY_LEN, 0, HC_SESSION_KEY_LEN);
}
} else {
return HC_UNKNOW_MESSAGE;
}
return ret;
}
3. 根据消息类型,接下来执行send_pake_end_request函数,准备发送pake end请求。
scss
/*
函数功能:准备发送pake end请求,客户端根据该响应消息准备回复数据
函数参数:
pake_client:pake客户端对象
receive:接收到的消息
send:待发送消息地址
函数返回值:
成功:0
失败:error num
*/
int32_t send_pake_end_request(struct pake_client *pake_client, const struct message *receive, struct message *send)
{
check_ptr_return_val(pake_client, HC_INPUT_ERROR);//检查参数有效性
check_ptr_return_val(receive, HC_INPUT_ERROR);
check_ptr_return_val(send, HC_INPUT_ERROR);
DBG_OUT("Receive pake start response message object %u success", pake_client_sn(pake_client));
struct pake_start_response_data *receive_data = (struct pake_start_response_data *)receive->payload;//用pake_start_response_data结构接收响应消息数据
struct pake_end_request_data *send_data =
(struct pake_end_request_data *)MALLOC(sizeof(struct pake_end_request_data));//为待发送数据申请空间,to 服务端
if (send_data == NULL) {
LOGE("Malloc struct pake_end_request_data failed");
return HC_MALLOC_FAILED;
}
(void)memset_s(send_data, sizeof(*send_data), 0, sizeof(*send_data));//清空该空间
int32_t ret = send_end_request(pake_client, receive_data, send_data);//根据收到的响应消息准备待发送的end请求数据,保存在send_data中
if (ret != HC_OK) {//准备end请求失败
LOGE("Called send_end_request failed, error code is %d", ret);
FREE(send_data);
send->msg_code = INFORM_MESSAGE;
} else {//准备end请求成功
DBG_OUT("Called send_end_request success");
send->msg_code = PAKE_CLIENT_CONFIRM;//置消息码为PAKE_CLIENT_CONFIRM
send->payload = send_data;//赋值消息负载
}
return ret;
}
4. send_end_request函数,准备待发送的end请求数据。
scss
/*
函数功能:准备待发送的end请求数据
函数参数:
handle:句柄,可用相关结构体获取
receive_data:接收到的消息数据
send_data:待发送数据地址
函数返回值:
成功:0
失败:error num
*/
int32_t send_end_request(void *handle, void *receive_data, void *send_data)
{
check_ptr_return_val(handle, HC_INPUT_ERROR);//检查参数有效性
check_ptr_return_val(receive_data, HC_INPUT_ERROR);
check_ptr_return_val(send_data, HC_INPUT_ERROR);
struct key_agreement_client *client = (struct key_agreement_client *)handle;//用密钥协商客户端接收该对象
struct key_agreement_protocol *base = &client->protocol_base_info;//定义密钥协商协议基础信息
DBG_OUT("Object %u begin receive start response data and send end request data", base->sn);
if (is_state_error(client, SEND_END_REQUEST)) {//判断协议状态和协议动作是否对应错误
LOGE("Object %u state error", base->sn);
return PROTOCOL_STATE_ERROR;
}
struct client_virtual_func_group *funcs = &client->package_funcs;//客户端虚函数组,定义打包函数
int32_t ret = funcs->parse_start_response_data(handle, receive_data);//解析start响应数据
if (ret != HC_OK) {
set_state(base, PROTOCOL_ERROR);
LOGE("Object %u parse start response data failed, error code is %d", base->sn, ret);
return ret;
}
ret = funcs->build_end_request_data(handle, send_data);//构造end请求数据保存在send_data中
if (ret != HC_OK) {
set_state(base, PROTOCOL_ERROR);
LOGE("Object %u build end request data failed, error code is %d", base->sn, ret);
return ret;
}
set_state(base, END_REQUEST);//设置协议状态为END_REQUEST
set_last_time_sec(base);//设置上一次的时间
DBG_OUT("Object %u receive start response data and send end request data success", base->sn);
return HC_OK;
}
5. parse_start_response_data函数,解析start响应数据。
ini
/*
函数功能:解析start响应数据
函数参数:
handle:pake客户端对象
data:服务端回复的响应数据
函数返回值:
成功:返回0
失败:返回error num
*/
static int32_t parse_start_response_data(void *handle, void *data)
{
struct pake_client *pake_client = (struct pake_client *)handle;
struct pake_start_response_data *receive = (struct pake_start_response_data *)data;
if (!is_peer_support_current_version(&receive->self_version, &receive->self_support_version)) {//检查对端是否支持当前版本
LOGE("Unsupport version received");
return HC_VERSION_UNSUPPORT;
}
pake_client->salt = receive->salt;//保存salt值
pake_client->peer_epk = receive->epk;//保存对端epk值
pake_client->peer_challenge = receive->challenge;//保存对端的挑战值
struct hkdf secret = { 0, {0} };//hkdf派生密钥
int32_t ret = compute_hkdf((struct var_buffer *)&pake_client->pin, &pake_client->salt,
HICHAIN_SPEKE_BASE_INFO, HC_HKDF_SECRET_LEN,
(struct var_buffer *)&secret);//基于HKDF算法的密钥派生函数,根据密钥派生种子和salt值计算出派生密钥保存在secret中
if (ret != HC_OK) {
LOGE("Object %u generate hkdf failed, error code is %d", pake_client_sn(pake_client), ret);
return ret;
}
uint32_t esk_len = 0;
uint32_t prime_len = 0;
ret = gen_esk_prime_len(pake_client, receive, &esk_len, &prime_len);//产生esk素数长度
if (ret != HC_OK) {
return ret;
}
if (pake_client->client_info.protocol_base_info.state == START_REQUEST) {
struct random_value rand = generate_random(esk_len);//生成随机数
if (rand.length == 0) {
LOGE("Generate random value failed");
return HC_GEN_RANDOM_FAILED;
}
pake_client->self_esk = *(struct esk *)&rand;//将生成的随机数作为本端esk
}
struct exponent exp = {.length = 1};
exp.exp[0] = 2; /* square */
struct epk base = { 0, {0} };
ret = cal_bignum_exp((struct var_buffer *)&secret, (struct var_buffer *)&exp,
prime_len, (struct big_num *)&base);//计算大素数指数输出到base中
if (ret != HC_OK) {
return HC_CAL_BIGNUM_EXP_FAILED;
}
struct epk self_epk = { 0, {0} };
ret = cal_bignum_exp((struct var_buffer *)&base, (struct var_buffer *)&pake_client->self_esk,
prime_len, (struct big_num *)&self_epk);//再次计算大素数指数作为本端临时公钥self_epk
if (ret != HC_OK) {
return HC_CAL_BIGNUM_EXP_FAILED;
}
pake_client->self_epk = self_epk;//赋值本端epk
return HC_OK;
}
6. build_end_request_data函数,构造end请求数据。
ini
/*
函数功能:构造end请求数据
函数参数:
handle:pake客户端对象
data:待发送数据地址
函数返回值:
成功:0
失败:error num
*/
static int32_t build_end_request_data(void *handle, void *data)
{
struct pake_client *pake_client = (struct pake_client *)handle;//接收pake客户端对象
struct pake_end_request_data *send = (struct pake_end_request_data *)data;//接收之前准备的待发送数据
int32_t ret = generate_session_key(pake_client, &pake_client->peer_epk);//根据对端epk生成会话密钥保存到pake客户端对象中
if (ret != HC_OK) {
LOGE("Object %u generate session key failed", pake_client_sn(pake_client));
return ret;
}
struct challenge challenge = { 0, {0} };//定义challenge
struct random_value rand = generate_random(CHALLENGE_BUFF_LENGTH);//生成challenge长度的随机数
if (rand.length == CHALLENGE_BUFF_LENGTH) {
DBG_OUT("Generate challenge success");
challenge.length = rand.length;
if (memcpy_s(challenge.challenge, sizeof(challenge.challenge), rand.random_value,
CHALLENGE_BUFF_LENGTH) != EOK) {//将产生的随机数作为challenge值
return memory_copy_error(__func__, __LINE__);
}
} else {
LOGE("Generate challenge failed.");
}
pake_client->self_challenge = challenge;//将产生的challenge值作为本端challenge值
pake_client->kcf_data = generate_proof(pake_client);//根据hmac密钥和双端challenge值生成一个HMAC值proof赋给kcf_data
send->challenge = challenge;//将本段challenge值作为发送数据
send->epk = pake_client->self_epk;//封装本端epk在待发送数据中
send->kcf_data = pake_client->kcf_data;//封装本端kcf_data在待发送数据中
return HC_OK;
}
DD一下: 欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。
erlang
`欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案)
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......
7. generate_proof函数,客户端生成认证证据保存在kcf_data中,发送给服务端。
scss
/*
函数功能:根据hmac密钥和双端challenge值生成一个HMAC值proof(客户端)
函数参数:
pake_client:pake客户端对象
函数返回值:
返回生成的proof值
*/
static struct hmac generate_proof(struct pake_client *pake_client)
{
struct hmac proof = { 0, {0} };//证据/证明
struct uint8_buff challenge = {//定义一个challenge buf存放本端challenge值和对端challenge值
.val = NULL,
.length = 0,
.size = CHALLENGE_BUFF_LENGTH + CHALLENGE_BUFF_LENGTH
};
challenge.val = (uint8_t *)MALLOC(challenge.size);//申请空间
if (challenge.val == NULL) {
LOGE("Object %u MALLOC generate proof buffer failed.", pake_client_sn(pake_client));
return proof;
}
(void)memset_s(challenge.val, challenge.size, 0, challenge.size);//清空该空间
(void)memcpy_s(challenge.val, challenge.size, pake_client->self_challenge.challenge, CHALLENGE_BUFF_LENGTH);//将本端challenge值拷贝到该challenge buf中
challenge.length = CHALLENGE_BUFF_LENGTH;//赋值challenge长度
(void)memcpy_s(challenge.val + challenge.length, challenge.size - challenge.length,
pake_client->peer_challenge.challenge, CHALLENGE_BUFF_LENGTH);//将对端challenge值拷贝到该challenge buf中
challenge.length += CHALLENGE_BUFF_LENGTH;//加上challenge长度
//challenge缓冲区中的顺序为:{客户端challenge,服务端challenge}
int32_t ret = compute_hmac((struct var_buffer *)&pake_client->hmac_key, &challenge, &proof);//根据hmac密钥和双端challenge值计算HMAC值保存在proof中
FREE(challenge.val);
if (ret != HC_OK) {
LOGE("Object %u generate proof hmac failed, error code is %d.", pake_client_sn(pake_client), ret);
proof.length = 0;
}
DBG_OUT("Object %u generate proof success", pake_client_sn(pake_client));
return proof;//返回proof
}
8. 准备完待发送数据之后,调用函数make_pake_client_confirm构造json格式的pake客户端confirm消息,向服务端发起end请求。
scss
/*
函数功能:构造json格式的pake客户端confirm消息
函数参数:
data:待发送数据
函数返回值:
返回封装好的json数据
{
"message":0x0002, //消息码:PAKE_CLIENT_CONFIRM
"payload":
{
"kcfData":"十六进制格式的字符串", //kcfData,用于身份认证
"challenge":"十六进制格式的字符串", //基于"挑战/响应"方式的挑战值,是一个随机值
"epk":"十六进制格式的字符串" //临时公钥,也是一个随机值
}
}
*/
char *make_pake_client_confirm(void *data)
{
struct pake_end_request_data *pake_client_confirm = data;
/* kcfData */
uint8_t *tmp_kcf_data_hex = raw_byte_to_hex_string(pake_client_confirm->kcf_data.hmac,
pake_client_confirm->kcf_data.length);//将kcfData转换为十六进制格式的字符串
if (tmp_kcf_data_hex == NULL) {
return NULL;
}
/* challenge */
uint8_t *tmp_cha_data_hex = raw_byte_to_hex_string(pake_client_confirm->challenge.challenge,
pake_client_confirm->challenge.length);//将challenge转换为十六进制格式的字符串
if (tmp_cha_data_hex == NULL) {
FREE(tmp_kcf_data_hex);
return NULL;
}
/* epk */
uint8_t *tmp_epk_data_hex = raw_byte_to_hex_string(pake_client_confirm->epk.epk,
pake_client_confirm->epk.length);//将epk转换为十六进制格式的字符串
if (tmp_epk_data_hex == NULL) {
FREE(tmp_kcf_data_hex);
FREE(tmp_cha_data_hex);
return NULL;
}
char *ret_str = (char *)MALLOC(RET_STR_LENGTH);//申请回复字符串存储空间
if (ret_str == NULL) {
FREE(tmp_kcf_data_hex);
FREE(tmp_cha_data_hex);
FREE(tmp_epk_data_hex);
return NULL;
}
(void)memset_s(ret_str, RET_STR_LENGTH, 0, RET_STR_LENGTH);//清空该空间
if (snprintf_s(ret_str, RET_STR_LENGTH, RET_STR_LENGTH - 1,
"{\"%s\":%d,\"%s\":{\"%s\":\"%s\", \"%s\":\"%s\", \"%s\":\"%s\"}}", FIELD_MESSAGE,
PAKE_CLIENT_CONFIRM, FIELD_PAYLOAD, FIELD_KCF_DATA, tmp_kcf_data_hex, FIELD_CHALLENGE,
tmp_cha_data_hex, FIELD_EPK, (char *)tmp_epk_data_hex) < 0) {
LOGE("String generate failed");
FREE(ret_str);
ret_str = NULL;
}//构造构造json格式的pake客户端confirm消息
FREE(tmp_kcf_data_hex);
FREE(tmp_cha_data_hex);
FREE(tmp_epk_data_hex);
return ret_str;
}
三、小结
至此,客户端发起end请求过程结束,经过分析,客户端发送的confirm消息格式如下:
json
{
"message":0x0002, //消息码:PAKE_CLIENT_CONFIRM
"payload":
{
"kcfData":"十六进制格式的字符串", //kcfData,用于身份认证,根据hmac密钥和双端challenge值生成的HMAC值
"challenge":"十六进制格式的字符串", //基于"挑战/响应"方式的挑战值,是一个随机值
"epk":"十六进制格式的字符串" //临时公钥,也是一个随机值
}
}
在这一个过程中,客户端根据服务端的epk、客户端esk、salt、双端challenge值利用HKDF、HMAC等算法生成一个认证证据proof存在kcfData字段中,然后连同客户端生成的challenge和epk封装成confirm消息发送给服务端。