EternalWindows
証明書検証 / 証明書チェーンとダイアログ

証明書ストアに格納されている証明書やファイルとして保存されている証明書は、 それを開くことで証明のパス、即ち証明書チェーンを確認することができます。 この証明書チェーンが動的に構築される原理は、 証明書の発行先とシリアルナンバーを基に証明書ストアから検索を行うというものですが、 場合によってはこの方法だけでは解決できないことがあります。 それは、証明書が署名の中に格納されている場合です。 この場合、その証明書の発行先である証明書も署名に格納されているはずですから、 署名の中に格納されている証明書も検索する必要性が生じるのです。

ユーザーが証明書チェーンを視覚的に確認するのは、 CryptUIDlgViewContextで表示される次のようなダイアログであると思われます。 しかし、残念ながらこの関数には、先に述べた署名の中の証明書を検索する機能はありません。

見て分かるように、CryptUIDlgViewContextでダイアログを表示した場合は、 証明書チェーンが正しく構築されていません(当然ながら、CAの証明書が既に証明書ストアに存在する場合は別です)。 しかし、CryptUIDlgViewCertificateという関数は、表示するダイアログこそCryptUIDlgViewContextと同一であるものの、 署名の中の証明書を検索する機能を持っているため、 正しく証明書チェーンが構築することができます。 この関数に証明書チェーン構築のための情報を与えるためには、 WinTrustの関数群であるWTHelper関数を呼び出すことになります。 最初に呼び出すのは、WTHelperProvDataFromStateDataです。

CRYPT_PROVIDER_DATA* WINAPI WTHelperProvDataFromStateData(
  HANDLE hStateData
);

hStateDataは、WinVerifyTrustの呼び出しによって取得した状態ハンドルを指定します。 戻り値のCRYPT_PROVIDER_DATA構造体は、様々な情報を維持する複雑な構造体ですが、 簡単に言い表すなら、複数の証明書チェーンを表す構造体です。 単一の署名には複数の証明書が格納されており、これは1つの証明書チェーンと考えることができるため、 ファイルに格納されている署名が複数存在すれば、証明書チェーンも複数存在すると考えることができます。

CRYPT_PROVIDER_DATA構造体のアドレスがあれば、CryptUIDlgViewCertificateを呼び出すことができますが、 肝心の表示すべき証明書はどのように取得すればよいでしょうか。 この証明書は証明書チェーンの一部ですから、 CRYPT_PROVIDER_DATA構造体から辿ることができます。 まずは、WTHelperGetProvSignerFromChainを呼び出して、 複数の証明書チェーンから1つの証明書チェーンを取得します。

CRYPT_PROVIDER_SGNR* WINAPI WTHelperGetProvSignerFromChain(
  CRYPT_PROVIDER_DATA *pProvData,
  WORD idxSigner,
  BOOL fCounterSigner,
  DWORD idxCounterSigner
);

pProvDataは、CRYPT_PROVIDER_DATA構造体のアドレスを指定します。 idxSignerは、取得する証明書チェーンのインデックスを指定します。 fCounterSignerは、副署名の証明書チェーンを取得したい場合にTRUEを指定します。 idxCounterSignerは、取得する副署名の証明書チェーンのインデックスを指定します。 戻り値は、CRYPT_PROVIDER_SGNR構造体は証明書チェーンを表しますが、 名前から想像ができるように、署名者情報も含んでいます。 したがって、正確には署名者情報と証明書チェーンを含む構造体です。

証明書チェーンを取得できれば、後はその中から証明書を取得すればよいことになります。 これには、WTHelperGetProvCertFromChainを呼び出します。

CRYPT_PROVIDER_CERT* WINAPI WTHelperGetProvCertFromChain(
  CRYPT_PROVIDER_SGNR *pSgnr,
  DWORD idxCert
);

pSgnrは、CRYPT_PROVIDER_SGNR構造体のアドレスを指定します。 idxCertは、取得したい証明書のインデックスを指定します。

今回のプログラムは、署名の中に格納されている証明書をダイアログで表示します。 これをCryptUIDlgViewCertificateで行うことにより、 証明書チェーンが正しく構築されることになります。

#include <windows.h>
#include <softpub.h>
#include <cryptuiapi.h>

#pragma comment (lib, "wintrust.lib")
#pragma comment (lib, "cryptui.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{ 
	GUID                           guidAction = WINTRUST_ACTION_GENERIC_VERIFY_V2;
	WINTRUST_DATA                  trustData;
	WINTRUST_FILE_INFO             fileInfo;
	PCRYPT_PROVIDER_DATA           pProviderData;
	PCRYPT_PROVIDER_SGNR           pProviderSgnr;
	PCRYPT_PROVIDER_CERT           pProviderCert;
	CRYPTUI_VIEWCERTIFICATE_STRUCT viewCert;

	fileInfo.cbStruct       = sizeof(WINTRUST_FILE_INFO);
	fileInfo.hFile          = NULL;
	fileInfo.pcwszFilePath  = L"sample.exe";
	fileInfo.pgKnownSubject = NULL;
	
	trustData.cbStruct            = sizeof(WINTRUST_DATA);
	trustData.pPolicyCallbackData = NULL;
	trustData.pSIPClientData      = NULL;
	trustData.dwUIChoice          = WTD_UI_NONE;
	trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
	trustData.dwUnionChoice       = WTD_CHOICE_FILE;
	trustData.pFile               = &fileInfo;
	trustData.dwStateAction       = WTD_STATEACTION_VERIFY;
	trustData.hWVTStateData       = NULL;
	trustData.pwszURLReference    = NULL;
	trustData.dwProvFlags         = 0;

	if (WinVerifyTrust((HWND)INVALID_HANDLE_VALUE, &guidAction, &trustData) != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("検証に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	pProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
	pProviderSgnr = WTHelperGetProvSignerFromChain(pProviderData, 0, FALSE, 0);
	pProviderCert = WTHelperGetProvCertFromChain(pProviderSgnr, 0);

	ZeroMemory(&viewCert, sizeof(CRYPTUI_VIEWCERTIFICATE_STRUCT));
	viewCert.dwSize                          = sizeof(CRYPTUI_VIEWCERTIFICATE_STRUCT);
	viewCert.hwndParent                      = NULL;
	viewCert.dwFlags                         = 0;
	viewCert.szTitle                         = NULL;
	viewCert.pCertContext                    = pProviderCert->pCert;
	viewCert.rgszPurposes                    = 0;
	viewCert.cPurposes                       = 0;
	viewCert.rgszPurposes                    = NULL;
	viewCert.hWVTStateData                   = trustData.hWVTStateData;
	viewCert.pCryptProviderData              = pProviderData;
	viewCert.fpCryptProviderDataTrustedUsage = TRUE;
	viewCert.idxSigner                       = 0;
	viewCert.idxCert                         = 0;
	viewCert.fCounterSigner                  = FALSE;
	viewCert.idxCounterSigner                = 0;
	viewCert.cStores                         = 0;
	viewCert.rghStores                       = NULL;
	viewCert.cPropSheetPages                 = 0;
	viewCert.rgPropSheetPages                = NULL;
	viewCert.nStartPage                      = 0;

	CryptUIDlgViewCertificate(&viewCert, NULL);

	trustData.dwStateAction = WTD_STATEACTION_CLOSE;
	WinVerifyTrust((HWND)INVALID_HANDLE_VALUE, &guidAction, &trustData);

	return 0;
}

まずは、WinVerifyTrustまでのコードを確認します。 今回は、WinVerifyTrustでUIを表示しないということで、dwUIChoiceにWTD_UI_NONEを指定し、 状態ハンドルを取得するため、dwStateActionにWTD_STATEACTION_VERIFYを指定しています。 また、UIを表示しない場合のWinVerifyTrustの第1引数はNULLであるよりも、 INVALID_HANDLE_VALUEの方が適切といえます。 続いて、WTHelper関数の呼び出しを確認します。

pProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
pProviderSgnr = WTHelperGetProvSignerFromChain(pProviderData, 0, FALSE, 0);
pProviderCert = WTHelperGetProvCertFromChain(pProviderSgnr, 0);

WTHelperProvDataFromStateDataで得られるpProviderDataは、 複数の証明書チェーンを表す情報を持っており、 WTHelperGetProvSignerFromChainを呼び出すことで、 任意の証明書チェーンを取得することができます。 今回は、第2引数を0に指定しているため、最初の証明書チェーンを取得したことになります。 そして、WTHelperGetProvCertFromChainでは証明書チェーンの何番目の証明書を取得することができるため、 0を指定して最初の証明書を取得するようにしています。 後は、pProviderCert->pCertとすることで証明書コンテキストを参照できるため、 これをCRYPTUI_VIEWCERTIFICATE_STRUCT構造体のpCertContextに指定すればよいことになります。

CRYPTUI_VIEWCERTIFICATE_STRUCT構造体のメンバのうち、 WinTrustに関係するメンバは次の通りです。

viewCert.hWVTStateData                   = trustData.hWVTStateData;
viewCert.pCryptProviderData              = pProviderData;
viewCert.fpCryptProviderDataTrustedUsage = TRUE;
viewCert.idxSigner                       = 0;
viewCert.idxCert                         = 0;
viewCert.fCounterSigner                  = FALSE;
viewCert.idxCounterSigner                = 0;

hWVTStateData及びpCryptProviderDataは、それぞれ状態ハンドルとpProviderDataを指定することになります。 fpCryptProviderDataTrustedUsageは、既に検証作業を終えているかを示すフラグであり、 今回の場合は既にWinVerifyTrustを呼び出していますから、TRUEとなります。 残りのメンバについては、恐らく初見では不思議に思えることでしょう。 これらの情報は、既にWTHelper関数に指定して目的の証明を取得済みだからです。 結論から言うと、これらのメンバは「証明のパス」タブを操作するために存在します。 たとえば、署名が副署名を含んでおり、fCounterSignerをTRUEに指定した場合、 副署名の証明書チェーンが「証明のパス」タブに表示されることになります。 当然ながらこれは、現在表示されている証明書の情報と矛盾していますから、 通常はWTHelper関数で指定した値と同じものを指定することになります。

状態ハンドルを取得するアプリケーションは、 次のようにして不要になったハンドルを開放することになります。

trustData.dwStateAction = WTD_STATEACTION_CLOSE;
WinVerifyTrust((HWND)INVALID_HANDLE_VALUE, &guidAction, &trustData);

dwStateActionにWTD_STATEACTION_CLOSEを指定して、WinVerifyTrustを呼び出します。 これにより、状態ハンドルは開放されることになります。


戻る