【MFC】WinHTTPのHTTPS接続失敗をTLS 1.2で切り分ける

【MFC】WinHTTPのHTTPS接続失敗をTLS 1.2で切り分ける

MFCアプリからHTTPSへ接続できないとき、エラーコードだけでTLSバージョン不一致とは断定できません。この記事では、WinHTTPでTLS 1.2を明示して接続を試し、DNS、プロキシ、証明書、Schannelのどこで失敗したかを切り分けます。

目次

最初に見るエラーと確認先

表示された結果最初に確認する場所
ERROR_INTERNET_CANNOT_CONNECT(12029)接続先、ポート、DNS、プロキシを確認します。WinInetの一般的な接続失敗であり、TLS不一致を特定する値ではありません。
ERROR_WINHTTP_NAME_NOT_RESOLVEDホスト名とDNSを確認します。
ERROR_WINHTTP_SECURE_FAILURE(12175)証明書の期限、名前、発行元、失効確認に加え、Schannel設定と共通して許可するTLSバージョンを調べます。
ERROR_WINHTTP_SECURE_CHANNEL_ERROR(12157)Schannel設定と、クライアント・サーバーが共通して許可するTLSバージョンを確認します。
HTTPステータスを取得できたTLSハンドシェイクとHTTP応答受信までは完了しています。4xxや5xxはアプリケーション側の応答として別に扱います。

GetLastErrorは、WinHTTP関数が失敗を返した直後に取得します。成功後に取得した値は判定に使えません。

WinInetのセキュリティフラグではTLS 1.2を選べない

INTERNET_OPTION_SECURITY_FLAGSSECURITY_FLAG_STRENGTH_STRONGは、TLS 1.2を有効化する設定ではありません。MicrosoftのWinINet仕様では、SECURITY_FLAG_STRENGTH_STRONGは128bit暗号が使われていることを示す照会結果で、InternetQueryOptionから返される値です。

そのため、次のような処理を追加してもTLS 1.2への切り替えにはなりません。

// TLS 1.2 の指定にはならない
DWORD flags = SECURITY_FLAG_STRENGTH_STRONG;
InternetSetOption(
    hRequest,
    INTERNET_OPTION_SECURITY_FLAGS,
    &flags,
    sizeof(flags));

WinInetは、デスクトップユーザーのInternet Options、プロキシ、資格情報などを利用する用途に向いています。TLSの許可状態はWindowsとSchannelの設定にも左右されるため、アプリ単位で許可プロトコルを比較したい場合はWinHTTPで切り分けます。

WinHTTPでTLS 1.2を指定して実通信する

WinHTTPでは、セッションハンドルへWINHTTP_OPTION_SECURE_PROTOCOLSを設定できます。次の部分でTLS 1.2だけを許可します。

#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")

HINTERNET session = WinHttpOpen(
    L"MyApp/1.0",
    WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
    WINHTTP_NO_PROXY_NAME,
    WINHTTP_NO_PROXY_BYPASS,
    0);
if (session == nullptr)
{
    const DWORD error = GetLastError();
    // error を記録して終了する
}

DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
if (!WinHttpSetOption(
        session,
        WINHTTP_OPTION_SECURE_PROTOCOLS,
        &protocols,
        sizeof(protocols)))
{
    const DWORD error = GetLastError();
    // error を記録し、session を閉じて終了する
}

設定後は、WinHttpConnectWinHttpOpenRequestWinHttpSendRequestWinHttpReceiveResponseの順に呼びます。各戻り値を確認し、失敗直後のGetLastErrorと失敗したAPI名を一緒に記録してください。

応答を受信できたら、WinHttpQueryHeadersWINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBERを渡してHTTPステータスを取得します。最後にrequest、connection、sessionの各ハンドルをWinHttpCloseHandleで確実に閉じます。

TLS 1.1のみの接続が失敗し、同じURLへのTLS 1.2のみの接続が成功するなら、接続先またはOS設定が旧TLSを許可していないことを確認できます。TLS 1.2も失敗する場合は、表示されたAPI名とエラーコードからDNS、プロキシ、証明書、Schannelへ確認範囲を広げます。

WinHTTPで同じURLへ接続し、TLS 1.1のみではWinHttpSendRequestが12175で失敗し、TLS 1.2のみではHTTP 200を取得した比較結果

明示指定とOS既定値を使い分ける

WINHTTP_OPTION_SECURE_PROTOCOLSの既定値はWindowsのバージョンと更新状態で異なります。Microsoftの仕様では、Windows 11はTLS 1.2とTLS 1.3が既定です。Windows 10とWindows 8.1では既定値が異なるため、同じバイナリでも端末によって結果が変わる可能性があります。

TLS 1.2の明示指定は、古いアプリの互換性確認や障害の切り分けには有効です。一方、TLS 1.2だけへ固定すると、OSが将来利用できる新しいプロトコルへ自動で移行できません。対象OSの既定値で要件を満たせる新規実装では、OSの安全な既定値を使う設計も検討します。

アプリ側でTLS 1.2を指定しても、SchannelでTLS 1.2が無効なら上書きできません。Windows 8.1 / Windows Server 2012 R2以降ではTLS 1.2が既定で有効とされているため、レジストリ追加を最初の対処にはしません。端末のOS、更新状態、グループポリシー、現在のSchannel設定を確認してから、管理者の変更手順として扱います。

HTTPS接続失敗の確認順

  1. 失敗したAPI名と、その直後のGetLastErrorを記録します。
  2. 同じURLへTLS 1.1のみ、TLS 1.2のみで接続し、HTTP応答の有無を比較します。
  3. TLS 1.2も失敗する場合は、DNS、プロキシ、証明書、Schannelの順に確認します。

WinInetの暗号強度フラグをTLS設定として使わず、WinHTTPの実通信結果とエラーコードを起点に切り分けることが重要です。

仕様の確認には、Microsoft LearnのWinINet Option FlagsWinHTTP Option FlagsWinHttpSendRequestを参照してください。

目次