EternalWindows
証明書検証 / ポリシーによる検証

前節では証明書チェーンを作成し、そこに含まれる証明書に問題がないのかの検証を行いました。 この検証というのは、有効期間や失効状態、ルート証明書が信頼されているかどうかなどを基準に行っていましたが、 ある特定のポリシーに基づいた検証を行うことも可能です。 たとえば、Authenticode証明書として問題がないか、SSL証明書として問題がないかなどです。 このような、ポリシーに基づいた検証を行う場合は、CertVerifyCertificateChainPolicyを呼び出します。

BOOL WINAPI CertVerifyCertificateChainPolicy(
  LPCSTR pszPolicyOID,
  PCCERT_CHAIN_CONTEXT pChainContext,
  PCERT_CHAIN_POLICY_PARA pPolicyPara,
  PCERT_CHAIN_POLICY_STATUS pPolicyStatus
);

pszPolicyOIDは、ポリシーを表すOIDを指定します。 pChainContextは、検証する証明書チェーンを指定します。 pPolicyParaは、検証するための情報を含むCERT_CHAIN_POLICY_PARA構造体のアドレスを指定します。 pPolicyStatusは、検証結果を受け取るCERT_CHAIN_POLICY_STATUS構造体のアドレスを指定します。

pszPolicyOIDに指定できるOIDの一部を次に示します。

ポリシーOID 説明
CERT_CHAIN_POLICY_BASE CERT_CHAIN_POLICY_PARA.dwFlagsを基に検証を行う。
CERT_CHAIN_POLICY_AUTHENTICODE Authenticode証明書として問題がないかの検証を行う。 CERT_CHAIN_POLICY_PARA.pvExtraPolicyParaにCERT_CHAIN_POLICY_AUTHENTICODE構造体を指定できる。
CERT_CHAIN_POLICY_SSL SSL証明書として問題がないかの検証を行う。 CERT_CHAIN_POLICY_PARA.pvExtraPolicyParaにSSL_EXTRA_CERT_CHAIN_POLICY_PARA構造体を指定できる。
CERT_CHAIN_POLICY_MICROSOFT_ROOT ルート証明書がMicrosoftによって発行されたものであるかを検証する。

CERT_CHAIN_POLICY_PARA構造体の定義を次に示します。

typedef struct _CERT_CHAIN_POLICY_PARA {
  DWORD                   cbSize;
  DWORD                   dwFlags;
  void                    *pvExtraPolicyPara;  
} CERT_CHAIN_POLICY_PARA, *PCERT_CHAIN_POLICY_PARA;

cbSizeは、この構造体のサイズを指定します。 dwFlagsは、特定の内容が無効になっているかどうかを考慮しない場合に利用します。 有効期間や信頼されているルート証明書など、全て検証したい場合は0を指定します。 pvExtraPolicyParaは、検証に使うポリシー固有の値を指定します。

CERT_CHAIN_POLICY_STATUS構造体の定義を次に示します。

typedef struct _CERT_CHAIN_POLICY_STATUS {
  DWORD           cbSize;
  DWORD           dwError;
  LONG            lChainIndex;
  LONG            lElementIndex;
  void           *pvExtraPolicyStatus; 
} CERT_CHAIN_POLICY_STATUS, *PCERT_CHAIN_POLICY_STATUS;

cbSizeは、この構造体のサイズを指定します。 dwErrorは、検証時に発生したエラーが格納されます。 lChainIndexは、検証時にエラーが発生したチェーンのインデックスが格納されます。 lElementIndexは、検証時にエラーが発生した要素のインデックスが格納されます。 pvExtraPolicyStatusは、ポリシー固有のデータを受け取る場合に指定します。

今回のプログラムは、ポリシーOIDにCERT_CHAIN_POLICY_BASEを指定して、 基本ポリシーの検証を行います。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCERTSTORE               hStore;
	PCCERT_CONTEXT           pContext;
	CERT_CHAIN_PARA          chainPara;
	PCCERT_CHAIN_CONTEXT     pChainContext;
	PCERT_CHAIN_ELEMENT      pChainElement;
	CERT_CHAIN_POLICY_PARA   policyPara;
	CERT_CHAIN_POLICY_STATUS policyStatus;
	TCHAR                    szBuf[256];

	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return 0;
	
	pContext = CryptUIDlgSelectCertificateFromStore(hStore, NULL, NULL, NULL, 0, 0, NULL);
	if (pContext == NULL) {
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}

	ZeroMemory(&chainPara, sizeof(CERT_CHAIN_PARA));
	chainPara.cbSize = sizeof(CERT_CHAIN_PARA);

	if (!CertGetCertificateChain(NULL, pContext, NULL, NULL, &chainPara, 0, NULL, &pChainContext)) {
		MessageBox(NULL, TEXT("証明書チェーンの取得に失敗しました。"), NULL, MB_ICONWARNING);
		CertFreeCertificateContext(pContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}

	policyPara.cbSize            = sizeof(CERT_CHAIN_POLICY_PARA);
	policyPara.dwFlags           = CERT_CHAIN_POLICY_IGNORE_NOT_TIME_VALID_FLAG;
	policyPara.pvExtraPolicyPara = NULL;

	policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS);

	if (CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_BASE, pChainContext, &policyPara, &policyStatus)) {
		if (policyStatus.dwError != 0) {
			pChainElement = pChainContext->rgpChain[policyStatus.lChainIndex]->rgpElement[policyStatus.lElementIndex];
			CertGetNameString(pChainElement->pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szBuf, sizeof(szBuf));
			MessageBox(NULL, szBuf, TEXT("無効"), MB_ICONWARNING);
		}
		else
			MessageBox(NULL, TEXT("証明書チェーンに問題はありません。"), TEXT("OK"), MB_OK);
	}
	else
		MessageBox(NULL, TEXT("証明書チェーンの検証に失敗しました。"), NULL, MB_ICONWARNING);

	CertFreeCertificateContext(pContext);
	CertFreeCertificateChain(pChainContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return 0;
}

CertVerifyCertificateChainPolicyを呼び出すためには証明書チェーンが必要であるため、 前節と同じ手順でCertGetCertificateChainを呼び出します。 しかし、ここで少し考えておかなければならないのは、 CertGetCertificateChainが成功した時点で、証明書チェーンの検証は終わっているのではないかという点です。 前節では、pChainContext->TrustStatus.dwErrorStatusとすることで、 証明書チェーンに問題があるかを確認できたことから、 さらにCertVerifyCertificateChainPolicyを呼び出す必要はあるのでしょうか。 実は、CERT_CHAIN_POLICY_PARA構造体のdwFlagsによっては意味を持つのです。

policyPara.cbSize            = sizeof(CERT_CHAIN_POLICY_PARA);
policyPara.dwFlags           = CERT_CHAIN_POLICY_IGNORE_NOT_TIME_VALID_FLAG;
policyPara.pvExtraPolicyPara = NULL;

CERT_CHAIN_POLICY_IGNORE_NOT_TIME_VALID_FLAGを指定した場合、 証明書チェーンに有効期間の切れた証明書が含まれていても、 証明書チェーンは無効であると判断されません。 つまり、この定数を指定してCertVerifyCertificateChainPolicyを呼び出す狙いは、 期間切れ以外の理由で証明書チェーンが無効になっているかを特定することです。 言い換えれば、期間切れでも有効な証明書チェーンとして扱いたいわけです。 dwFlagsに指定できる定数には、CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAGというものもあり、 これを指定すると、ルートCAが信頼できない場合でも証明書チェーンは無効になりません。

CertVerifyCertificateChainPolicyの呼び出しは、次のようになっています。

policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS);

if (CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_BASE, pChainContext, &policyPara, &policyStatus)) {
	if (policyStatus.dwError != 0) {
		pChainElement = pChainContext->rgpChain[policyStatus.lChainIndex]->rgpElement[policyStatus.lElementIndex];
		CertGetNameString(pChainElement->pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szBuf, sizeof(szBuf));
		MessageBox(NULL, szBuf, TEXT("無効"), MB_ICONWARNING);
	}
	else
		MessageBox(NULL, TEXT("証明書チェーンに問題はありません。"), TEXT("OK"), MB_OK);
}
else
	MessageBox(NULL, TEXT("証明書チェーンの検証に失敗しました。"), NULL, MB_ICONWARNING);

CertVerifyCertificateChainPolicyの戻り値がTRUEである場合は、 検証が成功したことを意味するだけであり、 実際に証明書チェーンに問題があるかは、CERT_CHAIN_POLICY_STATUS構造体のdwErrorを確認しなければなりません。 dwErrorが0でない場合は、証明書チェーンのいずれかの証明書が無効になっており、 lChainIndexとlElementIndexを指定することで取得することができます。 dwErrorが受け取る定数には、期間切れを示すCERT_E_EXPIREDやルート証明書が信頼できないことを 示すCERT_E_UNTRUSTEDROOTがありますが、今回の場合はCERT_E_EXPIREDが返ることはないでしょう。 何故なら、CERT_CHAIN_POLICY_PARA構造体のdwFlagsに有効期間を考慮しないよう指定したからです。


戻る