连接到 Web 服务

Azure Sphere SDK 包括 libcurl 库,高级应用程序可以使用该库来连接 HTTP 和 HTTPS Web 服务并进行身份验证。 支持服务器和客户端身份验证,以便应用程序可以验证它们是否正在与预期的服务器通信,并可以向服务器证明其设备和 Azure Sphere 目录是合法的。 相互身份验证 将两者组合在一起。

GitHub 上的 Azure Sphere 示例存储库包含以下 curl 示例:

尽管 HTTPS_Curl_Easy 中的同步服务器身份验证方法非常简单,但 Azure Sphere 应用程序通常应使用HTTPS_Curl_Multi示例中所示的更复杂的异步技术,以及基于 epoll 的单线程事件驱动模式。

libcurl 网站提供了 libcurl C API 的完整文档和许多示例。 cURL 库与 Azure Sphere SDK 运行时库之间的差异如下:

常量名称
(定义)
cURL范围限制 Azure Sphere 范围限制
CURLOPT_BUFFERSIZE
(缓冲区大小)
默认值:16 KB 默认值:1536 KB
CURLOPT_UPLOAD_BUFFERSIZE
(上传缓冲区大小)
默认值:64 KB
最大值:2MB
最小值:16 KB
默认值:1536 KB
最大值:64 KB
最小值:1536 KB
CURLOPT_HEADERFUNCTION
(传递给此函数) 的完整 HTTP 标头
最大值:100 KB 最大值:16 KB
CURLOPT_DNS_CACHE_TIMEOUT 默认值:缓存结果 60 秒
最大值:永久缓存结果
最小值:0 (不缓存结果)
所有值都重写为 0,并且不缓存结果。

使用 curl 的应用程序的要求

使用 curl 库的应用程序必须包含相应的头文件,并在 应用程序清单中提供 Azure Sphere (旧版) 租户 UUID 和 Internet 主机信息。

头文件

若要使用 curl,请在应用程序中包含以下头文件:

#include <applibs/storage.h>  // required only if you supply a certificate in the image package
#include <tlsutils/deviceauth_curl.h> // required only for mutual authentication
#include <curl/curl.h>
#include <applibs/networking_curl.h>  // required only if using proxy to connect to the internet

只有在应用程序映像包中提供一个或多个证书时,才需要 storage.h 头文件。 执行相互身份验证需要 deviceauth_curl.h 标头。 如果应用程序使用代理连接到 Internet,则需要 networking_curl.h 标头。

应用程序清单

应用程序清单的 AllowedConnections 字段必须指定应用程序连接到的主机。 它还必须包含连接在重定向时可能会遇到的每个域的名称。 例如, microsoft.com 连接到 Microsoft 主页的应用程序需要 和 www.microsoft.com

如果应用程序使用相互身份验证,则清单的 DeviceAuthentication 字段必须包含 Azure Sphere (旧版) 租户 UUID。 仅当设备的目录链接到与应用程序清单中的租户 UUID 匹配的 Azure Sphere (旧) 租户 UUID 时,才会颁发设备身份验证证书。 此限制提供深层防御:在不同目录的设备上运行的应用程序 (例如,不同客户或恶意实体的应用程序) 无法向服务器进行身份验证。

在开发期间,可以使用 az sphere catalog show 命令并从 tags 对象中提取MigratedCatalogId值来查找旧租户 UUID。

如果应用程序使用代理, ReadNetworkProxyConfig 字段指示应用程序是否有权检索代理配置。

在以下示例中, AllowedConnections 字段指定应用程序仅连接到 www.example.comDeviceAuthentication 字段指定 Azure Sphere (旧版) 租户 UUID,使应用程序能够使用设备证书进行相互身份验证, ReadNetworkProxyConfig 字段指定应用程序可以保留代理配置信息。

  "Capabilities": {
    "AllowedConnections": [ "www.example.com" ],
    "Gpio": [],
    "Uart": [],
    "WifiConfig": false,
    "DeviceAuthentication": "00000000-0000-0000-0000-000000000000",
    "ReadNetworkProxyConfig": true
  }

支持的功能

适用于 Azure Sphere 的 Libcurl 仅支持 HTTP 和 HTTPS 协议。 此外,Azure Sphere OS 不支持某些功能,例如可写文件 (cookie) 或 UNIX 套接字。 将来的 libcurl 版本不支持的功能(如 mprintf () 系列)不可用。

适用于 Azure Sphere 的 Libcurl 支持 TLS 1.2 和 TLS 1.3,并且已根据更广泛的 Microsoft TLS 安全策略停用 TLS 1.0 和 TLS 1.1。

以下是受支持的密码套件:

  • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
  • TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
  • TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
  • TLS_DHE_RSA_WITH_AES_128_CBC_SHA256

尝试使用不受支持的 TLS 版本会返回错误 CyaSSL does not support <version>

服务器身份验证

Azure Sphere 支持通过 libcurl 进行服务器身份验证。 服务器的证书必须由设备信任的证书颁发机构 (CA) 签名。 若要使 libcurl 对服务器进行身份验证,应用程序必须提供 CA 文件的路径。

将 CA 证书添加到映像包

若要使用一个或多个 CA,必须将证书添加到映像包。 每个证书都必须采用 base-64 编码。 最简单的方法是创建包含所有其他证书的单个文件。 该文件必须具有 .pem 文件扩展名。 若要添加证书,请执行以下操作:

  1. 在应用程序的项目文件夹中创建 certs 文件夹。 项目文件夹包含应用程序的 CMakeLists 文件。
  2. 在 certs 文件夹中,创建扩展名为 .pem 的文本文件,将每个证书复制到其中,然后保存该文件。
  3. 在 CMakeLists.txt 文件中,将证书文件作为资源文件添加到映像包。 例如:
azsphere_target_add_image_package(${PROJECT_NAME} RESOURCE_FILES "certs/DigiCertGlobalRootCA.pem")

证书文件现在应显示在映像包的 certs 文件夹中。

设置证书位置

在应用程序中,使用 CURLOPT_CAPATHCURLOPT_CAINFO 选项设置证书的位置。 调用 Storage_GetAbsolutePathInImagePackage 以检索映像包中证书的绝对路径,然后调用 curl_easy_setopt

CURLOPT_CAPATH设置证书的默认文件夹。 例如,以下代码指示 curl 在映像的 certs 文件夹中查找证书:

char *path = Storage_GetAbsolutePathInImagePackage("certs");
curl_easy_setopt(curl_handle, CURLOPT_CAPATH, path);

CURLOPT_CAINFO设置包含一个或多个证书的文件的路径。 除了在 CURLOPT_CAPATH 中设置的默认文件夹外,Curl 还会搜索此文件。例如:

char *path = Storage_GetAbsolutePathInImagePackage("CAs/mycertificates.pem");
curl_easy_setopt(curl_handle, CURLOPT_CAINFO, path);

此代码指示 curl 信任 mycertificates.pem 文件中定义的任何 CA,以及 CURLOPT_CAPATH 目录中定义的 CA。

相互身份验证

相互身份验证验证服务器和客户端设备是否合法。 这是一个多步骤过程:

  1. 应用程序使用 CA 证书对服务器进行身份验证,如 服务器身份验证中所述。
  2. 应用程序向服务器提供 x509 客户端身份验证证书,以便服务器可以对设备进行身份验证。
  3. 服务器使用 Azure Sphere 目录的证书链来验证设备是否属于目录。

应用程序可以通过以下两种方式之一设置相互身份验证的设备身份验证端:

  • 将 Azure Sphere DeviceAuth_CurlSslFunc 函数配置为执行身份验证的 SSL 函数。
  • 创建一个自定义 SSL 函数,用于调用 Azure Sphere DeviceAuth_SslCtxFunc 函数进行身份验证。

注意

Azure Sphere 不支持 SSL/TLS 重新协商。

在使用任一函数之前,必须更新应用程序的 CMakeLists.txt 文件,以将 curl 和 tlsutils 添加到TARGET_LINK_LIBRARIES:

TARGET_LINK_LIBRARIES(${PROJECT_NAME} applibs pthread gcc_s c curl tlsutils)

使用 DeviceAuth_CurlSslFunc

执行设备身份验证的最简单方法是将 DeviceAuth_CurlSslFunc 配置为 curl SSL 身份验证的回调函数:

// Set DeviceAuth_CurlSslFunc to perform authentication
CURLcode err = curl_easy_setopt(_curl, CURLOPT_SSL_CTX_FUNCTION, DeviceAuth_CurlSslFunc);
if (err) {
	// Set ssl function failed
	return err;
}

DeviceAuth_CurlSslFunc 函数检索当前 Azure Sphere 目录的证书链,并设置 curl 连接以执行相互身份验证。 如果身份验证失败,函数将返回CURLE_SSL_CERTPROBLEM。

使用 DeviceAuth_SslCtxFunc

应用程序还可以使用自定义 SSL 回调函数,该函数调用 Azure Sphere DeviceAuth_SslCtxFunc 函数进行身份验证。

自定义 SSL 函数必须调用 DeviceAuth_SslCtxFunc 来执行身份验证,但也可以执行与身份验证相关的其他任务。 DeviceAuth_SslCtxFunc 返回枚举的值 DeviceAuthSslResult ,该值提供有关失败的详细信息。 例如:

static CURLcode MyCallback(CURL *curl, void *sslctx, void *userCtx)
{
    int err = DeviceAuth_SslCtxFunc(sslctx);
    Log_Debug("ssl func callback error %d\n", err);
    if (err) {
        // detailed error handling code goes here
    }
    return CURLE_OK;
}
...

err = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, MyCallback);
    if (err) {
        goto cleanupLabel;
    }

在服务器上使用目录证书链

若要执行相互身份验证,服务器必须能够验证设备是否属于 Azure Sphere 目录以及目录本身是否合法。 若要执行此身份验证,服务器需要 Azure Sphere 目录的证书链,该证书链对所有 Azure Sphere 设备进行签名:

若要获取目录的证书链,请将其下载到 .p7b 文件,如以下示例所示:

az sphere ca-certificate download-chain --destination CA-cert-chain.p7b

然后,可以在服务器上使用 .p7b 文件。

有关使用 curl 的其他提示

下面是在 Azure Sphere 应用程序中使用 curl 的一些其他提示。

  • 如果计划将页面内容存储在 RAM 或闪存中,请记住,Azure Sphere 设备上的存储 有限

  • 若要确保 curl 遵循重定向,请将以下内容添加到代码中:

    curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
    
  • 若要添加有关在调试期间可能有用的 curl 操作的详细信息,请执行以下操作:

    curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
    
  • 如果请求不包含用户代理,则某些服务器返回错误。 设置用户代理:

    curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
    
  • 处理curl_multi计时器回调时,请避免在报告的超时为 0 毫秒时进行递归调用,因为这可能会造成不可预知的行为。 相反,通过触发 EventLoopTimer 将 0ms 视为 1ms, (0ms EventLoopTimers 也是递归的,应避免) 。

    static int CurlTimerCallback(CURLM *multi, long timeoutMillis, void *unused)
    {
         // A value of -1 means the timer does not need to be started.
         if (timeoutMillis != -1) {
    
             if (timeoutMillis == 0) {
                 // We cannot queue an event for 0ms in the future (the timer never fires)
                 // So defer it very slightly (see https://curl.se/libcurl/c/multi-event.html)
                 timeoutMillis = 1;
             }
    
             // Start a single shot timer with the period as provided by cURL.
             // The timer handler will invoke cURL to process the web transfers.
             const struct timespec timeout = {.tv_sec = timeoutMillis / 1000,
                                              .tv_nsec = (timeoutMillis % 1000) * 1000000};
             SetEventLoopTimerOneShot(curlTimer, &timeout);
         }
    
         return 0;
    }