Web サービスに接続する

Azure Sphere SDK には libcurl ライブラリが含まれています。このライブラリは、高レベルのアプリケーションが HTTP および HTTPS Web サービスに接続して認証するために使用できます。 サーバーとクライアントの両方の認証がサポートされているため、アプリケーションは、想定されるサーバーと通信していることを確認し、デバイスと Azure Sphere カタログが正当であることをサーバーに証明できます。 相互認証 は、2 つを組み合わせたものになります。

GitHub の Azure Sphere サンプル リポジトリには、次の curl サンプルが含まれています。

HTTPS_Curl_Easyでのサーバー認証に対する同期的なアプローチは非常に簡単ですが、Azure Sphere アプリケーションでは一般に、HTTPS_Curl_Multi サンプルに示されているより複雑な非同期手法と、エポールベースのシングルスレッド イベント ドリブン パターンを使用する必要があります。

libcurl Web サイトには、libcurl C API の完全なドキュメントと多くの例が用意されています。 cURL ライブラリと Azure Sphere SDK ランタイム ライブラリの違いは次のとおりです。

定数名
(定義)
cURL範囲の制限 Azure Sphere の範囲の制限
CURLOPT_BUFFERSIZE
(バッファー サイズ)
既定値: 16 KB 既定値: 1536 KB
CURLOPT_UPLOAD_BUFFERSIZE
(アップロード バッファー サイズ)
既定値: 64 KB
最大: 2 MB
最小: 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 とインターネット ホスト情報を提供する必要があります。

ヘッダー ファイル

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 ヘッダー ファイルは、アプリケーション イメージ パッケージに 1 つ以上の証明書を指定する場合にのみ必要です。 相互認証を実行するには、deviceauth_curl.h ヘッダーが必要です。 アプリケーションがプロキシを使用してインターネットに接続する場合は、networking_curl.h ヘッダーが必要です。

アプリケーション マニフェスト

アプリケーション マニフェストの AllowedConnections フィールドでは、アプリケーションが接続するホストを指定する必要があります。 また、リダイレクトされた場合に接続で発生する可能性がある各ドメインの名前も含まれている必要があります。 たとえば、Microsoft ホーム ページに接続するアプリケーションには、 と www.microsoft.com の両方microsoft.comが必要です。

アプリケーションで相互認証を使用する場合、マニフェストの DeviceAuthentication フィールドに Azure Sphere (レガシ) テナント UUID が含まれている必要があります。 デバイス認証証明書は、デバイスのカタログが、アプリケーション マニフェストのテナント UUID と一致する Azure Sphere (レガシ) テナント UUID にリンクされている場合にのみ発行されます。 この制限により、詳細な防御が提供されます。別のカタログ内のデバイスで実行されているアプリケーション (たとえば、別の顧客や不正なエンティティのアプリケーション) は、サーバーに対して認証できません。

開発中に、 az sphere catalog show コマンドを使用し、オブジェクトから値を MigratedCatalogId 抽出することで、レガシ テナント UUID を tags 見つけることができます。

アプリケーションがプロキシを使用する場合、 ReadNetworkProxyConfig フィールドは、アプリケーションがプロキシ構成を取得するアクセス許可を持っているかどうかを示します。

次の例では、 AllowedConnections フィールドは、アプリケーションが にのみ www.example.com接続することを指定します。 DeviceAuthentication フィールドは 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 証明書を追加する

1 つ以上の CA を使用するには、イメージ パッケージに証明書を追加する必要があります。 各証明書は base-64 でエンコードされている必要があります。 最も簡単な方法は、追加のすべての証明書を含む 1 つのファイルを作成することです。 ファイルには、.pem ファイル名拡張子が必要です。 証明書を追加するには:

  1. アプリケーションのプロジェクト フォルダーに certs フォルダーを作成します。 プロジェクト フォルダーには、アプリケーションの CMakeLists ファイルが含まれています。
  2. certs フォルダーで、拡張子が .pem のテキスト ファイルを作成し、各証明書をコピーしてファイルを保存します。
  3. CMakeLists.txt ファイルで、証明書ファイルをリソース ファイルとしてイメージ パッケージに追加します。 例えば:
azsphere_target_add_image_package(${PROJECT_NAME} RESOURCE_FILES "certs/DigiCertGlobalRootCA.pem")

これで、証明書ファイルがイメージ パッケージの certs フォルダーに表示されます。

証明書の場所を設定する

アプリケーションで、 CURLOPT_CAPATH オプションと CURLOPT_CAINFO オプションを使用して、証明書の場所を設定します。 Storage_GetAbsolutePathInImagePackageを呼び出して、イメージ パッケージ内の証明書への絶対パスを取得し、curl_easy_setoptを呼び出します。

CURLOPT_CAPATH証明書の既定のフォルダーを設定します。 たとえば、次のコードは、イメージ内の certs フォルダー内の証明書を検索するように curl に指示します。

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

CURLOPT_CAINFOは、1 つ以上の証明書を含むファイルへのパスを設定します。 Curl は、CURLOPT_CAPATHで設定された既定のフォルダーに加えて、このファイルを検索します。例えば:

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

このコードは、mycertificates.pem ファイルで定義されているすべての CA を、CURLOPT_CAPATHのディレクトリ セットで定義されている CA に加えて信頼するように curl に指示します。

相互認証

相互認証は、サーバーとクライアント デバイスの両方が正当であることを確認します。 これは複数ステップのプロセスです。

  1. 「サーバー認証」で説明されているように、アプリケーションは CA 証明書を使用して サーバーを認証します
  2. アプリケーションは、サーバーがデバイスを認証できるように、x509 クライアント認証証明書をサーバーに提示します。
  3. サーバーは、Azure Sphere カタログの証明書チェーンを使用して、デバイスがカタログに属していることを確認します。

アプリケーションは、次の 2 つの方法のいずれかで、相互認証のデバイス認証側を設定できます。

  • 認証を実行する SSL 関数として Azure Sphere DeviceAuth_CurlSslFunc 関数を構成します。
  • 認証のために Azure Sphere DeviceAuth_SslCtxFunc 関数を呼び出すカスタム SSL 関数を作成します。

メモ

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を使用 する

デバイス認証を実行する最も簡単な方法は、curl SSL 認証のコールバック関数として DeviceAuth_CurlSslFunc を構成することです。

// 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を使用 する

アプリケーションでは、認証のために Azure Sphere DeviceAuth_SslCtxFunc 関数を呼び出すカスタム SSL コールバック関数を使用することもできます。

カスタム 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 を 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;
    }