HTTPS request process development example (ArkTS) based on HarmonyOS

Introduction

This codelab implements an HTTPS request based on the network module and Webview, and performs packet capture analysis on the process. The effect is as shown in the figure:

Related concepts

● Webview: Provides Web control capabilities, and Web components provide web page display capabilities.

● HTTP data request: The network management module provides HTTP data request capabilities and supports GET, POST, OPTIONS, HEAD, PUT, DELETE, TRACE, and CONNECT request methods.

● HTTPS: Application layer protocol, supports encrypted transmission and identity authentication, ensuring safe data transmission.

● SSL: SSL (Secure Socket Layer) is a security protocol implemented on top of the transport communication protocol (TCP/IP).

● TLS: TLS (Transport Layer Security) is a security protocol designed to achieve encrypted data transmission.

Complete example

gitee source code address

Source code download

HTTPS request process (ArkTS).zip

Environment setup

We first need to complete the establishment of the HarmonyOS development environment. Please refer to the following steps.

Software requirements

● DevEco Studio version: DevEco Studio 3.1 Release.

● HarmonyOS SDK version: API version 9.

Hardware requirements

● Device type: Huawei mobile phone or Huawei mobile phone device simulator running on DevEco Studio.

● HarmonyOS system: 3.1.0 Developer Release.

Environment setup

1. Install DevEco Studio. For details, please refer to Download and Install the Software.

2. Set up the DevEco Studio development environment. The DevEco Studio development environment depends on the network environment. You need to be connected to the network to ensure the normal use of the tool. You can configure the development environment according to the following two situations:

● If you have direct access to the Internet, you only need to download the HarmonyOS SDK.

● If the network cannot directly access the Internet and needs to be accessed through a proxy server, please refer to Configuring the Development Environment.

3. Developers can refer to the following link to complete the relevant configuration for device debugging:

● Use a real machine for debugging

● Use the simulator for debugging

Interpretation of code structure

This codelab only explains the core code. For the complete code, we will provide it in the source code download or gitee.

├──entry/src/main/ets // Code area</code><code>│ ├──common</code><code>│ │ ├──constants</code><code>│ │ │ ├──StyleConstants.ets // Style constant class</code><code>│ │ │ └──CommonConstants.ets // Constant class</code><code>│ │ └──utils</code> <code>│ │ ├──HttpUtil.ets // Network request method</code><code>│ │ └──Logger.ets // Log printing tool class</code><code>│ ├──entryability</code><code>│ │ └──EntryAbility.ts // Program entry class</code><code>│ └──pages</code><code>│ └──WebPage.ets // Page entry</code><code>└──entry/src/main/resources // Resource file directory</code>

Create HTTPS request

The HTTPS protocol is a secure transmission protocol located at the application layer. The biggest difference from HTTP is that data transmission between the server and the client will be encrypted by TLS/SSL. This example requests the HarmonyOS official website and displays the requested content through the web container. The effect is as shown in the figure:

First, call the createHttp method in HttpUtil.ets to create a request task, and then initiate a network request through the request method. This method supports three parameters: url, options, and callback. Options can set the request method, request headers, timeout, etc.

// HttpUtil.ets</code><code>import http from '@ohos.net.http';</code><code>export default async function httpGet(url: string) {<!- - --></code><code> if (!url) {<!-- --></code><code> return undefined;</code><code> }</code><code> let request = http.createHttp();</code><code> let options = {<!-- --></code><code> method: http.RequestMethod.GET,</code><code> header: { 'Content-Type': 'application/json' },</code><code> readTimeout: CommonConstant.READ_TIMEOUT,</code><code> connectTimeout: CommonConstant.CONNECT_TIMEOUT</code><code> } as http.HttpRequestOptions;</code><code> let result = await request.request(url, options);</code><code> return result;</code><code>}</code>

Then call the above encapsulated httpGet method in the entry page to request the specified URL, and embed the requested content into the web component.

// WebPage.ets</code><code>import http from '@ohos.net.http';</code><code>...</code><code>@Entry</code><code>@Component</code><code>struct WebPage {<!-- --></code><code> @State webVisibility: Visibility = Visibility.Hidden;</code><code> .. .</code><code> build() {<!-- --></code><code> Column() {<!-- --></code><code> ...</code><code> }</code><code> }</code>
<code> async onRequest() {<!-- --></code><code> if (this.webVisibility === Visibility.Hidden) {<!-- --></code><code> this .webVisibility = Visibility.Visible;</code><code> try {<!-- --></code><code> let result = await httpGet(this.webSrc);</code><code> if ( result & amp; & amp; result.responseCode === http.ResponseCode.OK) {<!-- --></code><code> this.controller.clearHistory();</code><code> this .controller.loadUrl(this.webSrc);</code><code> } </code><code> } catch (error) {<!-- --></code><code> promptAction.showToast({ <!-- --></code><code> message: $r('app.string.http_response_error')</code><code> })</code><code> }</code> <code> } else {<!-- --></code><code> this.webVisibility = Visibility.Hidden;</code><code> }</code><code> }</code><code>}</code>

Analyzing the module source code shows that after establishing a request through the request method, the bottom layer of the module will first call curl_easy_init in the third-party library libcurl to initialize a simple session. After the initialization is complete, then call the curl_easy_setopt method to set the transfer options. Among them, CURLOPT_URL is used to set the URL address of the request, corresponding to the url parameter in the request; CURLOPT_WRITEFUNCTION can set a callback to save the received data; CURLOPT_HEADERDATA supports setting a callback and saving the response header data in the callback.

// http_exec.cpp
bool HttpExec::RequestWithoutCache(RequestContext *context)
{
    if (!staticVariable_.initialized) {
        NETSTACK_LOGE("curl not init");
        return false;
    }
    auto handle = curl_easy_init();
    ...
    if (!SetOption(handle, context, context->GetCurlHeaderList())) {
        NETSTACK_LOGE("set option failed");
        return false;
    }
    ...
    return true;
}
...
bool HttpExec::SetOption(CURL *curl, RequestContext *context, struct curl_slist *requestHeader)
{
    const std::string & amp;method = context->options.GetMethod();
    if (!MethodForGet(method) & amp; & amp; !MethodForPost(method)) {
        NETSTACK_LOGE("method %{public}s not supported", method.c_str());
        return false;
    }
    if (context->options.GetMethod() == HttpConstant::HTTP_METHOD_HEAD) {
        NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOBODY, 1L, context);
    }
    //Set request URL
    NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, context->options.GetUrl().c_str(), context);
    ...
    //Set CURLOPT_WRITEFUNCTION transmission options, OnWritingMemoryBody is the callback function
    NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEFUNCTION, OnWritingMemoryBody, context);
    NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEDATA, context, context);
    //Write response header data in OnWritingMemoryHeader
    NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader, context);
    NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERDATA, context, context);
    ...
    return true;
}
...
#define NETSTACK_CURL_EASY_SET_OPTION(handle, opt, data, asyncContext) \
    do {
        CURLcode result = curl_easy_setopt(handle, opt, data); \
        if (result != CURLE_OK) { \
            const char *err = curl_easy_strerror(result); \
            NETSTACK_LOGE("Failed to set option: %{public}s, %{public}s %{public}d", #opt, err, result); \
            (asyncContext)->SetErrorCode(result); \
            return false; \
        } \

After the transfer option is successfully set, call curl_multi_perform to execute the transfer request, query the processing handle through curl_multi_info_read to see if there is a message returned, and finally enter the HandleCurlData method to process the returned data.

// http_exec.cpp</code><code>void HttpExec::SendRequest()</code><code>{<!-- --></code><code> ...</code><code> do {<!-- --></code><code> ...</code><code> auto ret = curl_multi_perform(staticVariable_.curlMulti, & amp;runningHandle);</code><code> ...</code><code> } while (runningHandle > 0);</code><code>}</code><code>...</code><code>void HttpExec::ReadResponse ()</code><code>{<!-- --></code><code> CURLMsg *msg = nullptr; /* NOLINT */</code><code> do {<!-- -- ></code><code> ...</code><code> msg = curl_multi_info_read(staticVariable_.curlMulti, & amp;leftMsg);</code><code> if (msg) {<!-- -- ></code><code> if (msg->msg == CURLMSG_DONE) {<!-- --></code><code> HandleCurlData(msg);</code><code> }</code> <code> }</code><code> } while (msg);</code><code>}</code>

Call the ParseHeaders function in the HandleCurlData function to parse the response header written by the above callback. The response header will carry the highest network protocol supported by the client and server. If it is HTTP/2, it means HTTPS encrypted transmission is supported.

// http_exec.cpp
bool HttpExec::GetCurlDataFromHandle(CURL *handle, RequestContext *context, CURLMSG curlMsg, CURLcode result)
{
    ...
    context->response.ParseHeaders();
    return true;
}
// http_response.cpp
void HttpResponse::ParseHeaders()
{
    std::vector<std::string> vec = CommonUtils::Split(rawHeader_, HttpConstant::HTTP_LINE_SEPARATOR);
    for (const auto & amp;header : vec) {
        if (CommonUtils::Strip(header).empty()) {
            continue;
        }
        auto index = 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));
    }
}

When changing the URL protocol header in this codelab to http, you can see in the DevEco Studio log that the server will return a 301 status code and permanently redirect to https, so the final communication will still undergo TLS encrypted transmission.

The module source code can be obtained in the Gitee open source warehouse communication_netstack. The source code referenced by this Codelab is located in the http_exec file.

TLS/SSL handshake process

This chapter mainly analyzes the handshake process of the TLS protocol through packet capture data, including exchange parameters, certificate verification, key calculation, and verification keys. The packet capture content is as shown in the figure:

The handshake process is shown in the figure:

5.1 First handshake

As can be seen in the above figure, the client will first perform the first handshake connection and send a “Client Hello” message to the server to open a new session connection. Analysis of the data packet shows that the client will pass the protocol version number (TLS1.2), random number (Client Random, used for subsequent generation of “session key”), Session ID and Cipher Suites ( Cipher suites supported by the client). The data content is as shown in the figure:

5.2 The second handshake

After the server receives the client data, it passes the response data to the client through “Sever Hello”, including random number (Sever Random, used for subsequent generation of “session key”), protocol version number (TLS1.2) and Cipher Suite (choose any cipher suite supported by the client), the data content is as shown in the figure:

After the server passes “Sever Hello”, it will then pass the Certificate, “Sever Key Exchange” message and “Server Hello Done” message to the client. Here we focus on the analysis of “Sever Key Exchange”. The data content is as shown in the figure:

5.3 The third handshake

After the client receives the “Server Hello Done” message, it will pass the Client Params data to the server, which contains the elliptic curve public key (Pubkey) generated by itself. The data content is as shown in the figure:

After the above process, the client holds Client Random, Server Random and Server Params, decrypts the Server Params using the server public key and obtains the temporary public key in the “Server Key Exchange” message. The client uses the x25519 algorithm to calculate the pre-master key. Key (Premaster Secret), and then combine the client random number, server random number and pre-master key to generate the master key, and finally build the “session key”. The “Change Cipher Spec” message indicates that the client has generated a key and switched to encryption mode. Finally, make a summary of all the previous handshake data, encrypt it using the symmetric key negotiated by both parties, and pass the encrypted data to the server for verification through the “Encrypted Handshake Message” message. The data content is as shown in the figure:

5.4 The fourth handshake

The server uses Client Random, Server Random and Client Params to calculate the “session key” and delivers the “Change Cipher Spec” and “Encrypted Handshake Message” messages to the client for verification by the client. When both parties pass the verification, the real data begins to be transmitted.

Summary

You have completed this Codelab and learned the following knowledge points:

1. Use @ohos.net.http to establish an https request.

2. Understand the secure transmission of data by analyzing the transmitted packets during the TLS/SSL handshake.