#### 步骤一:修改CONTEXT结构体(httpd.h)**
在CONTEXT结构体中增加Digest认证相关的字段:
```c
#define LEN_USERNAME 256
#define LEN_REALM 256
#define LEN_DIGEST_NONCE 256
#define LEN_URI 256
#define LEN_RESPONSE 256
#define LEN_ALGORITHM 64
#define LEN_QOP 64
#define LEN_NC 32
#define LEN_CNONCE 64
#define LEN_OPAQUE 256
typedef struct _CONTEXT
{
/* 现有字段... */
/* 新增Digest认证相关字段 */
BOOL digest_authenticated; /* Digest认证是否通过 */
BOOL wss_authenticated; /* Digest认证是否通过 */
char generate_realm[LEN_REALM]; /* 生成的realm */
char generate_opaque[LEN_OPAQUE]; /* 生成的opaque */
char digest_username[LEN_USERNAME]; /* Digest认证用户名 */
char digest_realm[LEN_REALM]; /* Digest认证域 */
char digest_nonce[LEN_DIGEST_NONCE]; /* 服务器生成的nonce */
char digest_uri[LEN_URI]; /* 请求的URI */
char digest_response[LEN_RESPONSE]; /* 客户端计算的响应值 */
char digest_algorithm[LEN_ALGORITHM];/* 算法类型 */
char digest_qop[LEN_QOP]; /* 保护质量 */
char digest_nc[LEN_NC]; /* 随机数计数器 */
char digest_cnonce[LEN_CNONCE]; /* 客户端随机数 */
char digest_opaque[LEN_OPAQUE]; /* 服务器不透明数据 */
/* 其他现有字段... */
} CONTEXT;
```
#### 步骤二:解析Authorization头(`http_parser.c`)
**添加Authorization头解析器**
在`http_parser_init()`函数中添加:
```c
void http_parser_init()
{
/* 现有初始化代码... */
/* 添加Authorization头解析器 */
http_key_parser_add("Authorization", http_parse_authorization);
/* ... */
}
```
**实现Authorization头解析函数**
参考`http`头格式:
```c
GET/cgi-bin/checkout?a=b HTTP/1.1
Authorization: Digest
username="Mufasa",
realm="realm",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c0",
uri="/xxxx/System/Register",
response="6629fae49393a05397450978507c4ef1",
algorithm=MD5,
qop=auth,
nc=00000001,
cnonce="0a4f113b",
/* opaque="5ccc069c403ebaf9f0171e9517f40e41" */
```
辅助函数:`trim_whitespace`
- 去除键前后的空格,避免因空格导致匹配失败。
- 十六进制字符转数值
```c
/* 移除字符串两端的空格 */
void trim_whitespace(char *str) {
char *end;
/* 去除前导空格 */
while (isspace((unsigned char)*str)) str++;
/* 全空格字符串处理 */
if (*str == 0) return;
/* 去除尾部空格 */
end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) end--;
*(end + 1) = '\0';
}
/* 十六进制字符转数值 */
int hex_val(char c) {
return isdigit(c) ? c - '0' : tolower(c) - 'a' + 10;
}
```
**`http_parse_authorization`:**
该函数假设各参数之间使用逗号分隔,且参数值中不包含逗号。
```c
LOCAL S32 http_parse_authorization(CONTEXT *context, char *value)
{
char *parse_ptr = value;
char *token;
int error_count = 0;
/* 检查Digest认证类型 */
if (strncasecmp(parse_ptr, "Digest ", 7) != 0) {
return OK;
}
parse_ptr += 7; /* 跳过前缀 */
char *saveptr; /* strtok_r上下文指针 */
token = strtok_r(parse_ptr, ",", &saveptr);
while (token != NULL) {
/* 分割键值对 */
char *eq = strchr(token, '=');
if (!eq) {
HTTPD_DEBUG("Invalid param format: %s", token);
error_count++;
token = strtok_r(NULL, ",", &saveptr);
continue;
}
/* 分割键值 */
*eq = '\0';
char *key = token;
char *val = eq + 1;
/* 移除键值两端的空格 */
trim_whitespace(key);
trim_whitespace(val);
/* 处理引号包裹的值 */
size_t vlen = strlen(val);
if (vlen >= 2 && val[0] == '"' && val[vlen-1] == '"') {
val[vlen-1] = '\0'; /* 移除尾部引号 */
memmove(val, val+1, vlen-1); /* 移除头部引号 */
vlen -= 2; /* 更新长度 */
}
/* 存储到CONTEXT字段 */
if (strcasecmp(key, "username") == 0) {
strncpy(context->digest_username, val, LEN_USERNAME-1);
}
else if (strcasecmp(key, "realm") == 0) {
strncpy(context->digest_realm, val, LEN_REALM-1);
}
else if (strcasecmp(key, "nonce") == 0) {
strncpy(context->digest_nonce, val, LEN_DIGEST_NONCE-1);
}
else if (strcasecmp(key, "uri") == 0) {
/* 规范化URI路径(移除查询参数)*/
char *query_start = strchr(val, '?');
if (query_start) *query_start = '\0';
strncpy(context->digest_uri, val, LEN_URI-1);
}
else if (strcasecmp(key, "response") == 0) {
strncpy(context->digest_response, val, LEN_RESPONSE-1);
}
else if (strcasecmp(key, "algorithm") == 0) {
strncpy(context->digest_algorithm, val, LEN_ALGORITHM-1);
}
else if (strcasecmp(key, "qop") == 0) {
strncpy(context->digest_qop, val, LEN_QOP-1);
}
else if (strcasecmp(key, "nc") == 0) {
strncpy(context->digest_nc, val, LEN_NC-1);
}
else if (strcasecmp(key, "cnonce") == 0) {
strncpy(context->digest_cnonce, val, LEN_CNONCE-1);
}
else if (strcasecmp(key, "opaque") == 0) {
strncpy(context->digest_opaque, val, LEN_OPAQUE-1);
}
else {
HTTPD_DEBUG("Unknown Digest param: %s=%s", key, val);
}
token = strtok_r(NULL, ",", &saveptr);
}
/* 检查必需参数是否齐全 */
if (strlen(context->digest_response) == 0) {
HTTPD_WARNING("Missing required response field");
return ERROR;
}
return (error_count > 3) ? ERROR : OK;
}
```
#### 步骤三:生成响应Authorization头
`onvif_srv.c`文件,`onvif_make_response(CONTEXT *context)`函数会在状态码非200时调用`http_make_response()`函数发送HTTP错误响应。
```c
#define HTTP_UNAUTHORIZED 401
LOCAL S32 onvif_make_response(CONTEXT *context)
{
/* 其他字段... */
if (context->code == HTTP_REQ_OK)
{
if (soap->use_udp == FALSE &&
soap->xml_buf.start != NULL &&
soap->xml_buf.start != soap->xml_buf.last)
{
ONVIF_TRACE("Onvif_make_response return onvif_send_http_rsp_packet.");
ret = onvif_send_http_rsp_packet(soap);
goto out;
}
else
{
ONVIF_TRACE("Onvif_make_response return OK.");
ret = OK;
goto out;
}
}
ONVIF_TRACE("Onvif_make_response return http_make_response.");
ret = http_make_response(context);
out:
/* 清除content内容 */
http_reset_context(context);
return ret;
}
```
在`http_make_response()`函数中,通过`http_send_rsp_header()`设置HTTP响应报文头部。因此,需要修改`http_send_rsp_header()`函数,使其能够在HTTP状态码为401时添加`WWW-Authenticate`头部。
```c
/* Location */
if (HTTP_MOVE_TEMPORARILY == context->code) {
// ...原有Location代码...
}
/* ========== 新增Digest认证头 ========== */
if (HTTP_UNAUTHORIZED == context->code) {
/* 必需字段: realm, nonce, qop */
context->digest_realm = “Restricted Area”;
/* 生成带时效的nonce */
if (generate_digest_nonce(context) != OK) {
return ERROR;
}
length = snprintf(context->head_end, (context->head_buf_end - context->head_end),
"WWW-Authenticate: Digest realm=\"Restricted Area\", nonce=\"%s\", qop=\"auth\"", context->digest_nonce);
/* 结束头部 */
length += snprintf(context->head_end + length, (context->head_buf_end - context->head_end - length), "\r\n");
context->head_end += length;
context->header_len += length;
}
/* Access-Control-Allow-Origin */
if (context->origin == TRUE) {
/* ...原有代码... */
}
```
#### 步骤四:nonce生成及过期管理:
Rand_Byte实现:
在tpssl中定义了RandBytes,但是return 0;
```c
int tpssl_RandBytes(unsigned char* buf, int num)
{
return 0;// 接口已无效,待处理
}
```
在`tpssl_rsa_encrypt`和其他函数中,使用了`mbedtls_ctr_drbg_random`作为随机数生成器,但这是通过`mbedtls`的上下文来生成随机数的,并不是直接可以调用的函数。
在`mbedtls`中,生成随机数的常用函数是`mbedtls_ctr_drbg_random`。但是,这个函数需要一个`mbedtls_ctr_drbg_context`上下文,而在代码中并没有一个直接封装好的函数来生成随机字节。
但是,在多个函数中(例如`tpssl_rsa_encrypt`)都使用了`mbedtls_ctr_drbg_random`,因此,可以自己封装一个类似于`RandBytes`的函数,但需要确保已经初始化了随机数生成器。
在`tpssl_private_decrypt`函数中,有一个使用随机数生成器的例子:
```c
mbedtls_ctr_drbg_init( &ctr_drbg );
mbedtls_entropy_init( &entropy );
ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen( pers ) );
...
/* 然后使用 mbedtls_ctr_drbg_random 生成随机数 */
```
所以,如果需要生成随机数,可以按照上述步骤初始化并使用`mbedtls_ctr_drbg_random`。
```c
int tpssl_SecureRandBytes(unsigned char* buf, int len) {
static mbedtls_ctr_drbg_context drbg;
static mbedtls_entropy_context entropy;
static int initialized = 0;
const char pers[] = "tpssl_rand_seed";
if (!initialized) {
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&drbg);
if (mbedtls_ctr_drbg_seed(&drbg, mbedtls_entropy_func, &entropy,
(unsigned char*)pers, sizeof(pers)-1) != 0) {
return 0; /* 失败返回0 */
}
initialized = 1;
}
if (mbedtls_ctr_drbg_random(&drbg, buf, len) != 0) {
return 0; /* 生成随机数失败 */
}
return 1; /* 成功返回1 */
}
```
可实现:
```c
#include <time.h>
#include <string.h>
#define ERROR_CRYPTO_FAIL -1
#define ERROR_BUFFER_OVERFLOW -2
#define OK 0
#define NONCE_LIFETIME 30
S32 generate_digest_nonce(CONTEXT *context) {
/* 数据源:时间戳+随机数 */
struct {
time_t timestamp; /* 精确到秒的时间戳 */
time_t expiration_time; /* 过期时间 */
unsigned char rand_bytes[16]; /* 强随机数 */
} nonce_seed;
/* 取当前时间戳 */
nonce_seed.timestamp = time(NULL);
nonce_seed.expiration_time = nonce_seed.timestamp + NONCE_LIFETIME; /* 过期时间 */
/* 生成密码学强随机数 (使用OpenSSL RAND_bytes) */
if (RAND_bytes(nonce_seed.rand_bytes, sizeof(nonce_seed.rand_bytes)) != 1) {
return ERROR_CRYPTO_FAIL; /* 随机数生成失败 */
}
/* 转为十六进制字符串格式 */
char hex_buffer[2 * sizeof(nonce_seed) + 1];
const unsigned char *ptr = (const unsigned char *)&nonce_seed;
for (size_t i = 0; i < sizeof(nonce_seed); i++) {
snprintf(hex_buffer + 2*i, 3, "%02x", ptr[i]);
}
hex_buffer[2 * sizeof(nonce_seed)] = '\0';
/* 组合realm和hex_buffer生成nonce:realm:hex_buffer" */
int len = snprintf(context->digest_nonce, LEN_DIGEST_NONCE, "%s:%s", context->generate_realm, hex_buffer);
if (len < 0 || len >= LEN_DIGEST_NONCE) {
return ERROR_BUFFER_OVERFLOW;
}
return OK;
}
/* NONCE验证函数(应在认证流程中调用) */
int validate_nonce(const char *nonce_str) {
/* 提取时间结构体部分(跳过realm前缀) */
const char *hex_seed = strchr(nonce_str, ':') + 1;
size_t seed_len = strlen(hex_seed)/2;
/* 将HEX转回二进制结构 */
struct {
time_t timestamp;
time_t expiration_time;
unsigned char rand_bytes[16];
} seed;
for (size_t i = 0; i < seed_len; i++) {
sscanf(hex_seed + 2*i, "%2hhx", (unsigned char*)&seed + i);
}
/* 过期判断 */
time_t current_time = time(NULL);
if (current_time > seed.expiration_time) {
return 0; /* 过期 */
}
return 1; /* 有效 */
}
```
#### 步骤五:修改`onvif_proc_data_srv`函数(`onvif_srv.c`)
在`onvif_proc_data_srv`函数中,在解析SOAP之前,先检查是否有Digest认证头,并进行验证。
```c
S32 onvif_proc_data_srv(CONTEXT *context)
{
/* 现有代码(初始化和HTTP检查)... */
/* ==== 新增Digest认证检查 ==== */
/* 初始化认证状态 */
context->digest_authenticated = FALSE;
context->wss_authenticated = FALSE;
/* 检查是否有Digest认证头以及对realm、nonce进行校验 */
if (context->digest_username[0] != '\0'&& strcmp(context->digest_realm,context->generate_realm) == 0 && validate_nonce(context->digest_nonce)==1)
{
/* 执行Digest认证 */
if (check_digest_auth(context) == OK)
{
context->digest_authenticated = TRUE;
ONVIF_TRACE("Digest authentication successful");
}
else
{
ONVIF_TRACE("Digest authentication failed");
/* 认证失败,返回401 */
context->code = HTTP_UNAUTHORIZED;
return ERROR;
}
}
/* 继续原有处理逻辑(端口处理和XML解析)... */
/* ==== 新增WSS认证检查 ==== */
if (soap->has_header) {
if (soap_usernametoken_auth(soap, USER_TYPE_ADMIN) != OK) {
ONVIF_ERROR("WSS authentication failed");
soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL,
"Authentication failed");
soap->error = SOAP_FAULT;
context->wss_authenticated = FALSE;
} else {
context->wss_authenticated = TRUE;
ONVIF_TRACE("WSS authentication successful");
}
}
/* ==== 最终认证检查 ==== */
if (!context->digest_authenticated && !context->wss_authenticated) {
ONVIF_TRACE("No valid authentication provided");
context->code = HTTP_UNAUTHORIZED;
return ERROR;
}
/* ==== 请求处理(原有逻辑)... */
return OK;
}
```
#### 步骤六:实现Digest认证验证函数
实现一个函数,用于验证Digest认证的响应是否正确。
```c
extern char g_ciphertext[];
S32 check_digest_auth(CONTEXT *context) {
if (!context) return -1;
/* 验证必要参数存在 */
if (strlen(context->digest_username) == 0 ||
strlen(context->digest_realm) == 0 ||
strlen(context->digest_nonce) == 0 ||
strlen(context->digest_uri) == 0 ||
strlen(context->digest_response) == 0) {
ONVIF_WARN("Missing required Digest auth parameters");
return -1;
}
/* 获取用户对应的密码 */
char stored_pwd[USER_MANAGEMENT_PASSWD_MAX_STR_LEN + 1] = {0};
if (strcmp(context->digest_username, TP_ROOT_USER) == 0) {
/* 管理员账户 */
char *plain_pwd = tpssl_rsa_decrypt(PRI_KEY_FILE_PATH,
(U8 *)g_ciphertext,,
(U32)strlen(g_ciphertext));
if (!plain_pwd) return -1;
strncpy(stored_pwd, plain_pwd, sizeof(stored_pwd)-1);
ONVIF_FREE(plain_pwd);
} else {
/* 第三方账户 */
THIRD_ACCOUNT_USER_MANAGEMENT third_acct;
if (0 == ds_read(THIRD_ACCOUNT_PATH, &third_acct, sizeof(third_acct))) {
char *plain_pwd = tpssl_rsa_decrypt(PRI_KEY_FILE_PATH,
(U8 *)third_acct.ciphertext,
(U32)strlen(third_acct.ciphertext));
if (!plain_pwd) return -1;
strncpy(stored_pwd, plain_pwd, sizeof(stored_pwd)-1);
ONVIF_FREE(plain_pwd);
} else {
return -1;
}
}
/* 计算HA1 = MD5(username:realm:password) */
char ha1_input[256];
snprintf(ha1_input, sizeof(ha1_input), "%s:%s:%s",
context->digest_username, context->digest_realm, stored_pwd);
char ha1[33];
tpssl_md5_hex(ha1_input, ha1);
memset(stored_pwd, 0, sizeof(stored_pwd)); /* 立即清除密码 */
/* 计算HA2 = MD5(method:uri) */
char ha2_input[256];
snprintf(ha2_input, sizeof(ha2_input), "%s:%s",
http_method_str(context->method), context->digest_uri);
char ha2[33];
tpssl_md5_hex(ha2_input, ha2);
/* 计算预期响应 = MD5(HA1:nonce:nc:cnonce:qop:HA2) */
char resp_input[512];
snprintf(resp_input, sizeof(resp_input), "%s:%s:%s:%s:%s:%s",
ha1, context->digest_nonce, context->digest_nc,
context->digest_cnonce, context->digest_qop, ha2);
char expected_resp[33];
tpssl_md5_hex(resp_input, expected_resp);
/* 验证响应值 */
if (strcasecmp(expected_resp, context->digest_response) == 0) {
ONVIF_TRACE("Digest auth success for user: %s", context->digest_username);
return 0; /* 认证成功 */
}
ONVIF_WARN("Digest auth failed. Expected: %s, Received: %s",
expected_resp, context->digest_response);
return -1; /* 认证失败 */
}
/* 使用tpssl实现的MD5计算(十六进制输出) */
void tpssl_md5_hex(const char *input, char *output) {
if (!input || !output) return;
Md5 ctx;
unsigned char digest[16]; // MD5_DIGEST_LENGTH=16
tpssl_InitMd5(&ctx);
tpssl_Md5Update(&ctx, (const byte*)input, strlen(input));
tpssl_Md5Final(&ctx, digest);
for (int i = 0; i < 16; i++) {
sprintf(output + (i * 2), "%02x", digest[i]);
}
output[32] = '\0';
}
/* 将HTTP方法枚举转换为字符串 */
const char *http_method_str(HTTP_METHOD method) {
switch(method) {
case HTTP_METHOD_GET: return "GET";
case HTTP_METHOD_POST: return "POST";
/* 添加PUT和DELETE(如果枚举中有定义)*/
#if defined(INCLUDE_UPNP_SERVER)
case HTTP_METHOD_MGET: return "MGET";
case HTTP_METHOD_MPOST: return "MPOST";
case HTTP_METHOD_SUBSCRIBE: return "SUBSCRIBE";
case HTTP_METHOD_UNSUBSCRIBE:return "UNSUBSCRIBE";
#endif
case HTTP_METHOD_UNKOWN: return "UNKNOWN";
default: return "INVALID";
}
}
```
——是我添加了上述代码才出错的,有没有可能是哪里引起的?
最新发布