连接到 Web 服务
重要
这是 Azure Sphere(旧版)文档。 Azure Sphere(旧版)将于 2027 年 9 月 27 日停用,用户此时必须迁移到 Azure Sphere(集成)。 使用位于 TOC 上方的版本选择器查看 Azure Sphere(集成)文档。
Azure Sphere SDK 包括 libcurl 库,高级应用程序可使用该库连接到 HTTP 和 HTTPS Web 服务并对这些服务进行身份验证。 支持服务器身份验证和客户端身份验证,因此应用程序可以验证它们是否与预期的服务器通信,并可以向服务器证明其设备和 Azure Sphere 租户是合法的。 相互身份验证会结合这两者。
GitHub 上的 Azure Sphere 示例存储库包含以下 curl 示例:
- HTTPS_Curl_Easy 使用同步(阻止)API 进行服务器身份验证。
- HTTPS_Curl_Multi 示例使用异步(非阻止)API 进行服务器身份验证。
尽管使用 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 库的应用程序必须包括适当的头文件,并在应用程序清单中提供租户和 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 主页的应用程序需要 microsoft.com
和 www.microsoft.com
。
如果应用程序使用相互身份验证,清单的 DeviceAuthentication 字段必须包含 Azure Sphere 租户 ID。 仅当设备的租户 ID 与应用程序清单中的租户 ID 匹配时,才会颁发设备身份验证证书。 此限制提供深层防御:在不同租户的设备(例如,不同客户或恶意实体的设备)上运行的应用程序无法向服务器进行身份验证。
如果应用程序使用代理, ReadNetworkProxyConfig 字段指示应用程序是否有权检索代理配置。
在开发过程中,可以使用 azsphere tenant show-selected 命令查找当前 Azure Sphere 租户的 ID。
在以下示例中, AllowedConnections 字段指定应用程序仅 www.example.com
连接到, DeviceAuthentication 字段指定 Azure Sphere 租户 ID,使应用程序能够使用设备证书进行相互身份验证, 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,并已停用 TLS 1.0 和 TLS 1.1,符合更广泛的Microsoft TLS 安全策略。
下面是受支持的密码套件:
- 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 文件扩展名。 若要添加证书,请执行以下操作:
- 在应用程序的项目文件夹中创建一个证书文件夹。 该项目文件夹包含应用程序的 CMakeLists 文件。
- 在证书文件夹中,创建扩展名为 .pem 的文本文件,将所有证书复制到其中,然后保存文件。
- 在CMakeLists.txt文件中,将证书文件作为资源文件添加到映像包。 例如:
azsphere_target_add_image_package(${PROJECT_NAME} RESOURCE_FILES "certs/DigiCertGlobalRootCA.pem")
证书文件现在应显示在映像包的证书文件夹中。
设置证书位置
在应用程序中,使用 CURLOPT_CAPATH 和 CURLOPT_CAINFO 选项来设置证书的位置。 调用 Storage_GetAbsolutePathInImagePackage 检索映像包中证书的绝对路径,然后调用 curl_easy_setopt。
CURLOPT_CAPATH 设置证书的默认文件夹。 例如,以下代码告知 curl 查找映像中证书文件夹内的证书:
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 除了信任在 CURLOPT_CAPATH 中设置的目录中定义的 CA 之外,还要信任 mycertificates.pem 文件中定义的所有 CA。
相互身份验证
相互身份验证可验证服务器和客户端设备是否合法。 这是一个多步骤过程:
- 应用程序使用 CA 证书对服务器进行身份验证,如服务器身份验证中所述。
- 应用程序向服务器提供一个 x509 客户端身份验证证书,以便服务器可以对设备进行身份验证。
- 服务器使用 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 文件,如下例所示:
azsphere 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 将 0 毫秒视为 1 毫秒(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; }