TR069 Http Digest 认证流程
一 流程及流程图
1.1盒端主动发起Http Digest认证流程
盒端CPE ACS终端管理系统
1.------------------inform(http不带auth头)----------->
2.<------------------401(http不带auth头)--------------
3.------------------inform(http带auth头)------------->
4.<------------------200 OK---------------------------
5.------------------ Content-Length: 0--------------->
机顶盒(CPE)通过HTTP Digest Authentication发起与终端管理系统(ACS)的认证连接,连接方式遵循RFC 2617的规定。
机顶盒连接终端管理系统的地址由Device.ManagementServer.URL参数提供。
机顶盒主动想终端管理系统发起一个HTTP 连接请求,终端管理系统会要求进行HTTP Digest Authentication认证。并按照RFC2671规范,盒端和盒端管理系统在之后的请求和应答时信息均带有认证头信息。
认证中的用户名、密码为Device.ManagementServer.Username 及Device.ManagementServer.Password
1.2 ACS主动发起Http Digest认证流程
盒端CPE ACS终端管理系统
1.<------------------http(不带auth头信息)-------------
2.------------------ 401 Unauthorized---------------->
3.<------------------http get(带auth头信息)-----------
4.------------------200 OK--------------------------->
5.<-----------------100 continue----------------------
6.------------------6 connect request---------------->
7.<------------------200 OK---------------------------
ACS终端管理系统主动发起一个HTTP 请求,CPE终端会要求进行HTTP Digest Authentication认证。并按照RFC2671规范,盒端和盒端管理系统在之后的请求和应答时信息均带有认证头信息。
认证中的用户名、密码为Device.ManagementServer.Connection.RequestUsername及Device.ManagementServer.ConnectionRequestPassword
二 详细交互流程:
2.1 盒端主动发起Http Digest认证报文
CPE IP地址: 192.168.20.11
ACS IP地址: 192.168.20.36
1)盒端(CPE)首先发起不带认证头的Inform请求报文,内容如下:
POST /acs HTTP/1.1
Host: 192.168.20.36
Accept: */*
Connection: TE, Keep-Alive
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 3814
Expect: 100-continue
HTTP/1.1 100 Continue
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
<SOAP-ENV:Header>
<cwmp:ID SOAP-ENV:mustUnderstand="1">1</cwmp:ID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<cwmp:Inform>
<DeviceId xsi:type="cwmp:DeviceIdStruct">
<Manufacturer>Test</Manufacturer>
<OUI>A1B2C4</OUI>
<ProductClass>Test_PC</ProductClass>
<SerialNumber>821281000054321</SerialNumber>
</DeviceId>
<Event SOAP-ENC:arrayType="cwmp:EventStruct[2]">
<EventStruct>
<EventCode>1 BOOT</EventCode>
<CommandKey></CommandKey>
</EventStruct>
<EventStruct>
……..
2) 盒端管理系统(ACS)收到上述报文后,发现没有认证消息(带有Authorization:标识的报文),然后发送401错误报文:
HTTP/1.1 401 Unauthorized
Date: Fri, 06 Jan 2017 02:47:12 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: JSESSIONID=12rxzt10p2rtb;Path=/
Content-Type: text/xml; charset=utf-8
WWW-Authenticate: Digest realm="XACS",qop="auth",nonce="fd171d5efcc65e79bfd8150af7f9cb21"
Content-Length: 0
Server: Jetty(6.1.20)
3 ) 盒端(cpe)收到报文后,经过分析得到报文错误为401,代码中通过分析报文中是否有WWW-Authenticate: Digest 字段,如果具有那么通过设置函数
//设置鉴权参数
code=curl_easy_setopt(curl,CURLOPT_HTTPAUTH,CURLAUTH_BASIC|CURLAUTH_DIGEST);
(本地配置的realm必须与收到的realm一致否则验证不能通过)将本地文件配置的realm和从盒端管理系统(ACS)收到的nonce,opaque,qop等值通过函数http_da_calc_HA1,生成一个唯一的字符串并存入response字段,并将这些信息组合到报文的头部,最后发送给ACS的报文为:
POST /acs HTTP/1.1
Authorization: Digest username="cpe", realm="XACS", nonce="fd171d5efcc65e79bfd8150af7f9cb21", uri="/acs", cnonce="MDQ1NzA0", nc=00000001, qop="auth", response="5f6059675ea5da97e45be615c2466ff7"
Host: 192.168.20.36
Accept: */*
Cookie: JSESSIONID=12rxzt10p2rtb
Connection: TE, Keep-Alive
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 3814
Expect: 100-continue
HTTP/1.1 100 Continue
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
<SOAP-ENV:Header>
<cwmp:ID SOAP-ENV:mustUnderstand="1">1</cwmp:ID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<cwmp:Inform>
<DeviceId xsi:type="cwmp:DeviceIdStruct">
<Manufacturer>Test</Manufacturer>
<OUI>A1B2C4</OUI>
<ProductClass>Test_PC</ProductClass>
<SerialNumber>821281000054321</SerialNumber>
</DeviceId>
<Event SOAP-ENC:arrayType="cwmp:EventStruct[2]">
<EventStruct>
<EventCode>1 BOOT</EventCode>
<CommandKey></CommandKey>
</EventStruct>……….
…….
4)盒端管理系统(ACS)收到上述报文后,确认其含Authorization:字段,并且Authorization:字段中的response的值正确,那么认证通过,并发送回复报文:
HTTP/1.1 200 OK
Date: Fri, 06 Jan 2017 02:47:12 GMT
Content-Type: text/xml; charset=utf-8
Content-Length: 526
Server: Jetty(6.1.20)
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><SOAP-ENV:Header><cwmp:ID SOAP-ENV:mustUnderstand="1">1</cwmp:ID><cwmp:NoMoreRequests>0</cwmp:NoMoreRequests></SOAP-ENV:Header><SOAP-ENV:Body><cwmp:InformResponse><MaxEnvelopes>1</MaxEnvelopes></cwmp:InformResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
5) 盒端(CPE)收到回复报文后,分析为认证通过报文后,发送一个inform内容为空的确认报文:——其实就是个空报文
POST /acs HTTP/1.1
Authorization: Digest username="cpe", realm="XACS", nonce="fd171d5efcc65e79bfd8150af7f9cb21", uri="/acs", cnonce="MDQ1NzA0", nc=00000002, qop="auth", response="1b58ed5321c916998e4af9de375177fc"
Host: 192.168.20.36
Accept: */*
Cookie: JSESSIONID=12rxzt10p2rtb
Connection: TE, Keep-Alive
Content-Length: 0
Content-Type: application/x-www-form-urlencoded
2.2 ACS主动发起Http Digest认证报文
略,和2.1流程类似,具体可以下载报文,自行分析。
2.3 同时开启双向认证
即CPE终端认证ACS管理系统,和ACS管理系统认证CPE终端同时认证。成功后盒端和盒端管理系统在之后的请求和应答时信息均带有认证头信息,认证中的用户名、密码为Device.ManagementServer.Username 及Device.ManagementServer.Password
报文下载路径:
http://download.csdn.net/detail/eryunyong/9730525
三 代码片段:
1) CPE 认证ACS管理系统的代码
//初始化curl库,设置参数用于http 传输
1 int http_init_curl(cwmp_context_t *cwmp_ctx, t_MemStruct *pmem, CURL **pcurl) 2 3 { 4 5 CURLcode code; 6 7 CURL *curl = NULL; 8 9 char *acs_usr = NULL; 10 11 char *acs_passwd = NULL; 12 13 char error_buf[CURL_ERROR_SIZE]; 14 15 CURLcode ret = FALSE; 16 17 18 19 20 21 if(!cwmp_ctx || !pmem || !pcurl) 22 23 { 24 25 CWMP_LOG_ERROR(EVENT_MODULE, "some param is NULL "); 26 27 return FALSE; 28 29 } 30 31 32 33 curl = curl_easy_init(); 34 35 if (!curl) 36 37 { 38 39 CWMP_LOG_ERROR(EVENT_MODULE, "curl_easy_init fail "); 40 41 return FALSE; 42 43 } 44 45 46 47 memset(error_buf, 0, sizeof(error_buf)); 48 49 code = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf); 50 51 if (code != CURLE_OK) 52 53 { 54 55 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set error buffer [%d] ", code); 56 57 return FALSE; 58 59 } 60 61 62 63 //设置回写函数 64 65 code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cwmp_write_func_callback); 66 67 if (code != CURLE_OK) 68 69 { 70 71 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set writer [%s] ", curl_easy_strerror(code)); 72 73 goto finish; 74 75 } 76 77 78 79 code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)pmem); 80 81 if (code != CURLE_OK) 82 83 { 84 85 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set write data [%s] ", curl_easy_strerror(code)); 86 87 goto finish; 88 89 } 90 91 92 93 code = curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); 94 95 if (code != CURLE_OK) 96 97 { 98 99 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set cookie file [%s] ", curl_easy_strerror(code)); 100 101 goto finish; 102 103 } 104 105 106 107 //设置鉴权参数 108 109 code = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST); 110 111 if (code != CURLE_OK) 112 113 { 114 115 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set http auth [%s] ", curl_easy_strerror(code)); 116 117 goto finish; 118 119 } 120 121 get_param_value_by_fullname(IGD_ManagementServer_Username, &acs_usr); 122 123 get_param_value_by_fullname(IGD_ManagementServer_Password, &acs_passwd); 124 125 if (acs_usr && acs_passwd) 126 127 { 128 129 //curl_easy_setopt(curl, CURLOPT_USERNAME, acs_usr); 130 131 //curl_easy_setopt(curl, CURLOPT_PASSWORD, acs_passwd); 132 133 code = curl_easy_setopt(curl, CURLOPT_USERNAME, acs_usr); 134 135 if (code != CURLE_OK) 136 137 { 138 139 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set username [%s] ", curl_easy_strerror(code)); 140 141 goto finish; 142 143 } 144 145 code = curl_easy_setopt(curl, CURLOPT_PASSWORD, acs_passwd); 146 147 if (code != CURLE_OK) 148 149 { 150 151 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set password [%s] ", curl_easy_strerror(code)); 152 153 goto finish; 154 155 } 156 157 158 159 CWMP_LOG_DEBUG(EVENT_MODULE, "acs usrname=%s, passwd=%s ", acs_usr, acs_passwd); 160 161 } 162 163 else 164 165 { 166 167 CWMP_LOG_ERROR(EVENT_MODULE, "get acs usrname or passwd fail "); 168 169 goto finish; 170 171 } 172 173 174 175 code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); 176 177 if (code != CURLE_OK) 178 179 { 180 181 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set follow location [%s] ", curl_easy_strerror(code)); 182 183 goto finish; 184 185 } 186 187 188 189 code = curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5); 190 191 if (code != CURLE_OK) 192 193 { 194 195 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set max redirs [%s] ", curl_easy_strerror(code)); 196 197 goto finish; 198 199 } 200 201 202 203 code = curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); 204 205 if (code != CURLE_OK) 206 207 { 208 209 CWMP_LOG_ERROR(EVENT_MODULE, "Failed to set POSTREDIR [%s] ", curl_easy_strerror(code)); 210 211 goto finish; 212 213 } 214 215 216 217 // http timeout 30 seconds 218 219 220 221 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30); 222 223 224 225 // not support SSL 226 227 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); 228 229 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); 230 231 232 233 (*pcurl) = curl; 234 235 ret = TRUE; 236 237 238 239 finish: 240 241 if(ret == FALSE) 242 243 { 244 245 if(curl) 246 247 { 248 249 curl_easy_cleanup(curl); 250 251 } 252 253 } 254 255 256 257 if (acs_usr) 258 259 { 260 261 free_check(acs_usr); 262 263 } 264 265 266 267 if (acs_passwd) 268 269 { 270 271 free_check(acs_passwd); 272 273 } 274 275 276 277 return ret; 278 279 } 280 281
2) CPE处理来自ACS的socket连接报文
1 //处理socket连接 2 3 static void *handle_sock(void *data) 4 5 { 6 7 Http_request *request = NULL; 8 9 int sock = (int)(long)data; 10 11 int len = 0; 12 13 const char *auth_str = NULL; 14 15 int auth = 0; //是否需要校验 16 17 int status = 0; 18 19 char *usrname = NULL; 20 21 char *passwd = NULL; 22 23 char resp[MAX_BUF_LEN+1] = {0}; 24 25 char auth_opaque[33] = {0}; 26 27 28 29 CWMP_LOG_ERROR(ACS_CONN_MODULE, "handle_sock begin, sock=%d ", sock); 30 31 len = http_parse_request(sock, &request); 32 33 if(len == 0) 34 35 { 36 37 CWMP_LOG_ERROR(ACS_CONN_MODULE, "socket:%d is closed ", sock); 38 39 return NULL; 40 41 } 42 43 44 45 if(len < 0) 46 47 { 48 49 CWMP_LOG_INFO(ACS_CONN_MODULE, "read data finish "); 50 51 goto finish; 52 53 } 54 55 56 57 if(!request) 58 59 { 60 61 CWMP_LOG_ERROR(ACS_CONN_MODULE, "http_parse_request fail "); 62 63 goto finish; 64 65 } 66 67 68 69 //判断是否需要验证 70 71 if(g_pcwmp_ctx->dev_info.func_get_auth) 72 73 { 74 75 auth = g_pcwmp_ctx->dev_info.func_get_auth(); 76 77 } 78 79 CWMP_LOG_ERROR(ACS_CONN_MODULE, "auth=%d ", auth); 80 81 if(auth <= 0) //不需要校验 82 83 { 84 85 status = 200; 86 87 goto response; 88 89 } 90 91 92 93 auth_str = http_header_get(request->header, "Authorization"); 94 95 if(!auth_str) 96 97 { 98 99 status = 401; 100 101 CWMP_LOG_ERROR(ACS_CONN_MODULE, "have not Authorization "); 102 103 goto response; 104 105 } 106 107 108 109 //校验 110 111 get_param_value_by_fullname(IGD_ManagementServer_ConnectionRequestUsername, &usrname); 112 113 get_param_value_by_fullname(IGD_ManagementServer_ConnectionRequestPassword, &passwd); 114 115 if(!usrname || !passwd) 116 117 { 118 119 status = 200; 120 121 } 122 123 if (check_digest_auth(auth_str, usrname, passwd) == FALSE) 124 125 { 126 127 status = 401; 128 129 } 130 131 else 132 133 { 134 135 status = 200; 136 137 CWMP_LOG_INFO(ACS_CONN_MODULE, "auth pass "); 138 139 } 140 141 response: 142 143 if(status == 200) 144 145 { 146 147 strcpy(resp, RESPONSE_200); 148 149 } 150 151 else if(status == 400) 152 153 { 154 155 strcpy(resp, RESPONSE_400); 156 157 } 158 159 else if(status == 401) 160 161 { 162 163 char buffer[256] = {0}; 164 165 char nonce[33]; 166 167 168 169 g_auth_nonce++; 170 171 snprintf(buffer, 256, "%d", g_auth_nonce); 172 173 MD5(nonce, buffer, NULL); 174 175 176 177 nonce[32] = 0; 178 179 MD5(auth_opaque, g_auth_realm, NULL); 180 181 snprintf(resp, MAX_BUF_LEN+1, RESPONSE_401, g_auth_realm, "auth", nonce, auth_opaque); 182 183 } 184 185 186 187 //发送回应 188 189 if(status != 0) 190 191 { 192 193 write_all(sock, resp, strlen(resp)); 194 195 CWMP_LOG_DEBUG(ACS_CONN_MODULE, "response to acs ok, status=%d ", status); 196 197 } 198 199 200 201 finish: 202 203 close(sock); 204 205 if(request) 206 207 { 208 209 http_destroy_request(request); 210 211 } 212 213 214 215 if(usrname) 216 217 { 218 219 free_check(usrname); 220 221 } 222 223 if(passwd) 224 225 { 226 227 free_check(passwd); 228 229 } 230 231 if(status == 200) 232 233 { 234 235 //6 Connected Request加入事件队列 236 237 increase_event_set(EVENT_CONNECTIONREQUEST, 1); 238 239 sem_post(&g_pcwmp_ctx->sem_send_acs); 240 241 } 242 243 CWMP_LOG_DEBUG(ACS_CONN_MODULE, "handle_sock end "); 244 245 return NULL; 246 247 }
四 总结
TR069 协议采用SSL/TLS、HTTP basic或者HTTP digest等加密认证方式可以保证数据的安全性;采用较多Web中成熟的技术,实现简单,降低了开发难度;采用HTTP协议,可以有效地穿越复杂的网络环境。因此,TR069协议比较适合对广域网内的设备进行管理。
五 参考
1)
2)TR069规范