介绍
本篇 Codelab 依据网络模块以及 Webview 完成一次 HTTPS 恳求,并对其进程进行抓包剖析。作用如图所示:
相关概念
●Webview:供给 Web 控制才干,Web 组件供给网页显现才干。
●HTTP数据恳求:网络管理模块,供给 HTTP 数据恳求才干,支撑 GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT 恳求办法。
●HTTPS:应用层协议,支撑加密传输以及身份认证,确保数据的安全传输。
●SSL:SSL(SecureSocketLayer)安全套接层是坐落传输通讯协议(TCP/IP)之上完成的一种安全协议。
●TLS:TLS(TransportLayerSecurity)是一种安全协议,旨在完成数据加密传输。
完好示例
源码下载
环境建立
咱们首要需求完成 HarmonyOS 开发环境建立,可参照如下步骤进行。
软件要求
●DevEcoStudio版别:DevEcoStudio3.1Release。
●HarmonyOSSDK版别:APIversion9。
硬件要求
●设备类型:华为手机或运行在 DevEcoStudio 上的华为手机设备模拟器。
●HarmonyOS 体系:3.1.0DeveloperRelease。
环境建立
1. 装置 DevEcoStudio,详情请参阅下载和装置软件。
2. 设置 DevEcoStudio 开发环境,DevEcoStudio 开发环境需求依赖于网络环境,需求衔接上网络才干确保东西的正常运用,能够依据如下两种情况来装备开发环境:
●假如能够直接拜访 Internet,只需进行下载HarmonyOSSDK操作。
●假如网络不能直接拜访 Internet,需求经过署理服务器才能够拜访,请参阅装备开发环境。
3. 开发者能够参阅以下链接,完成设备调试的相关装备:
代码结构解读
本篇 Codelab 只对核心代码进行解说,关于完好代码,咱们会在源码下载或 gitee 中供给。
创立 HTTPS 恳求
HTTPS 协议是坐落应用层的一种安全传输协议,与 HTTP 最大的区别是服务端与客户端之间进行数据传输都会经过 TLS/SSL 加密。该示例恳求HarmonyOS官网,并将恳求得到的内容经过 Web 容器展现出来。作用如图所示:
首要在 HttpUtil.ets 中调用 createHttp 办法创立一个恳求使命,再经过 request 办法建议网络恳求。该办法支撑三个参数:url、options 以及 callback 回调,其间 options 能够设置恳求办法、恳求头以及超时时刻等。
接着在进口页面中调用上述封装的 httpGet 办法恳求指定网址,将恳求得到的内容嵌入到 Web 组件中。
//WebPage.ets
importhttpfrom'@ohos.net.http';
...
@Entry
@Component
structWebPage{
@StatewebVisibility:Visibility=Visibility.Hidden;
...
build() {
Column() {
...
}
}
asynconRequest() {
if (this.webVisibility===Visibility.Hidden) {
this.webVisibility=Visibility.Visible;
try {
letresult=awaithttpGet(this.webSrc);
if (result&&result.responseCode===http.ResponseCode.OK) {
this.controller.clearHistory();
this.controller.loadUrl(this.webSrc);
}
} catch (error) {
promptAction.showToast({
message: $r('app.string.http_response_error')
})
}
} else {
this.webVisibility=Visibility.Hidden;
}
}
}
剖析模块源码可知,经过 request 办法建立恳求后,模块底层首要会调用三方库libcurl中的 curl_easy_init 初始化一个简略会话。初始化完成后,接着调用 curl_easy_setopt 办法设置传输选项。其间 CURLOPT_URL 用于设置恳求的 URL 地址,对应 request 中的 url 参数;CURLOPT_WRITEFUNCTION 能够设置一个回调,保存接纳的数据;CURLOPT_HEADERDATA 支撑设置回调,在回调中保存呼应头数据。
//http_exec.cpp
boolHttpExec::RequestWithoutCache(RequestContext*context)
{
if (!staticVariable_.initialized) {
NETSTACK_LOGE("curlnotinit");
return false;
}
autohandle= curl_easy_init();
...
if (!SetOption(handle,context,context->GetCurlHeaderList())) {
NETSTACK_LOGE("setoptionfailed");
return false;
}
...
return true;
}
...
boolHttpExec::SetOption(CURL*curl,RequestContext*context,structcurl_slist*requestHeader)
{
conststd::string&method=context->options.GetMethod();
if (!MethodForGet(method) && !MethodForPost(method)) {
NETSTACK_LOGE("method%{public}snotsupported",method.c_str());
return false;
}
if (context->options.GetMethod() ==HttpConstant::HTTP_METHOD_HEAD) {
NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_NOBODY, 1L,context);
}
//设置恳求URL
NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_URL,context->options.GetUrl().c_str(),context);
...
//设置CURLOPT_WRITEFUNCTION传输选项,OnWritingMemoryBody为回调函数
NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_WRITEFUNCTION,OnWritingMemoryBody,context);
NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_WRITEDATA,context,context);
//在OnWritingMemoryHeader写入呼应头数据
NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_HEADERFUNCTION,OnWritingMemoryHeader,context);
NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_HEADERDATA,context,context);
...
return true;
}
...
#defineNETSTACK_CURL_EASY_SET_OPTION(handle,opt,data,asyncContext)
do {
CURLcoderesult= curl_easy_setopt(handle,opt,data);
if (result!=CURLE_OK) {
const char *err= curl_easy_strerror(result);
NETSTACK_LOGE("Failedtosetoption:%{public}s,%{public}s%{public}d",#opt,err,result);
(asyncContext)->SetErrorCode(result);
return false;
}
传输选项设置成功后,调用 curl_multi_perform 执行传输恳求,并经过 curl_multi_info_read 查询处理句柄是否有音讯回来,终究进入 HandleCurlData 办法处理回来数据。
//http_exec.cpp
voidHttpExec::SendRequest()
{
...
do {
...
autoret= curl_multi_perform(staticVariable_.curlMulti, &runningHandle);
...
} while (runningHandle> 0);
}
...
voidHttpExec::ReadResponse()
{
CURLMsg*msg=nullptr; /*NOLINT*/
do {
...
msg= curl_multi_info_read(staticVariable_.curlMulti, &leftMsg);
if (msg) {
if (msg->msg==CURLMSG_DONE) {
HandleCurlData(msg);
}
}
} while (msg);
}
在 HandleCurlData 函数中调用 ParseHeaders 函数将上面回调写入的呼应头解析出来,其间呼应头中会携带客户端和服务端支撑的最高网络协议,假如是 HTTP/2 表示支撑 HTTPS 加密传输。
//http_exec.cpp
boolHttpExec::GetCurlDataFromHandle(CURL*handle,RequestContext*context,CURLMSGcurlMsg,CURLcoderesult)
{
...
context->response.ParseHeaders();
return true;
}
//http_response.cpp
voidHttpResponse::ParseHeaders()
{
std::vector<std::string>vec=CommonUtils::Split(rawHeader_,HttpConstant::HTTP_LINE_SEPARATOR);
for (constauto&header:vec) {
if (CommonUtils::Strip(header).empty()) {
continue;
}
autoindex=header.find(HttpConstant::HTTP_HEADER_SEPARATOR);
if (index==std::string::npos) {
header_[CommonUtils::Strip(header)] = "";
NETSTACK_LOGI("HEAD:%{public}s",CommonUtils::Strip(header).c_str());
continue;
}
header_[CommonUtils::ToLower(CommonUtils::Strip(header.substr(0,index)))] =
CommonUtils::Strip(header.substr(index 1));
}
}
将本篇 Codelab 中的网址协议头更改为 http 时,在 DevEcoStudio 的日志中看到服务端会回来 301 状况码永久重定向到 https,因此终究通讯依旧会经历 TLS 加密传输。
模块源码能够在 Gitee 开源库房 communication_netstack 中获取,本篇 Codelab 引证源码部分坐落 http_exec 文件中。
TLS/SSL 握手进程
本章节首要经过抓包数据剖析 TLS 协议的握手进程,其间包括交流参数、证书验证、密钥计算以及验证密钥等,抓包内容如图所示:
握手进程如图所示:
5.1第一次握手
依据上图中能够看到,客户端首要会进行第一次握手衔接,发送“ClientHello”音讯给服务端敞开一个新的会话衔接。剖析数据包得到,客户端在第一次握手时会向服务端传递协议版别号(TLS1.2)、随机数(ClientRandom,用于后续生成“会话密钥”)、SessionID 以及 CipherSuites(客户端支撑的暗码套件)。数据内容如图所示:
5.2第2次握手
服务端接纳到客户端数据后,将呼应数据经过“SeverHello”传递给客户端,包括随机数(SeverRandom,用于后续生成“会话密钥”)、协议版别号(TLS1.2)以及 CipherSuite(恣意选择一个客户端支撑的暗码套件),数据内容如图所示:
服务端传递“SeverHello”后,紧跟着会将 Certificate(证书)、“SeverKeyExchange”音讯以及“ServerHelloDone”音讯传递给客户端。此处侧重剖析“SeverKeyExchange”,数据内容如图所示:
5.3第三次握手
客户端收到“ServerHelloDone”音讯后,会将 ClientParams 数据传递给服务端,其间包括本身生成的椭圆曲线公钥(Pubkey),数据内容如图所示:
经过上述进程,客户端持有 ClientRandom、ServerRandom 以及 ServerParams,将 ServerParams 运用服务端公钥解密后得到“ServerKeyExchange”音讯中的暂时公钥,客户端运用 x25519 算法计算出预主密钥(PremasterSecret),然后再结合客户端随机数、服务端随机数以及预主密钥生成主密钥,终究构建“会话密钥”。“ChangeCipherSpec”音讯表示客户端已经生成密钥,并切换到加密模式。终究将之前所有的握手数据做一个摘要,再利用两边洽谈好的对称密钥进行加密,经过“EncryptedHandshakeMessage”音讯将加密数据传递给服务端做校验。数据内容如图所示:
5.4第四次握手
服务端利用 ClientRandom、ServerRandom 以及 ClientParams 计算得出“会话密钥”,向客户端传递“ChangeCipherSpec”和“EncryptedHandshakeMessage”音讯供客户端校验。当两边校验经往后,真实的数据才开始传输。
总结
您已经完成了本次 Codelab 的学习,并了解到以下知识点:
1. 运用 @ohos.net.http 建立一次 https 恳求。
2. 经过剖析 TLS/SSL 握手进程中的传输数据包来了解数据安全传输。