EternalWindows
証明書検証 / 証明書チェーン

証明書を署名に使用したり、第三者に公開したりするアプリケーションは、 その証明書が無効になっていないかを正しく検証しておく必要があります。 証明書が既に有効期間を経過していたり、CAによって失効されていたり、 あるいはCAが信頼されているルートCAとして存在ない場合などは、 証明書が無効として扱われる代表的な原因であり、 このような証明書を安易に利用することがあってはいけません。 証明書が有効であるかどうかは、証明書を表示するダイアログの「証明のパス」から 確認することができます。

表示されているチェーンの最下位に存在する証明書が、 現在表示対象となっている証明書であり、 その上の証明書が下位の証明書を発行したCAの証明書となります。 そして、一番上に存在する証明書がルートCAの証明書となります。 さて、この図では最下位の証明書とそれを発行したCAの証明書が無効となっていますが、 CAの証明書が無効になっている時点で、最下位の証明書が無効になるという点に注意してください。 証明書を実際に信頼するためには、その上位のCAが信頼できなければなりませんから、 たとえ最下位の証明書が有効期間を経過していたり、失効されたりしていなくても、 証明書は無効として扱わなければならないのです。 このことから分かるように、証明書を検証するというのは、その証明書単一を対象にすればよいというものではなく、 上位の証明書を含めた一連の証明書、即ち証明書チェーンを検証する必要があるのです。

アプリケーションから証明書チェーンを取得するには、 CertGetCertificateChainを呼び出します。

BOOL WINAPI CertGetCertificateChain(
  HCERTCHAINENGINE hChainEngine, 
  PCCERT_CONTEXT pCertContext, 
  LPFILETIME pTime,
  HCERTSTORE hAdditionalStore,
  PCERT_CHAIN_PARA pChainPara,
  DWORD dwFlags,
  LPVOID pvReserved,
  PCCERT_CHAIN_CONTEXT *ppChainContext
);

hChainEngineは、証明書チェーンを作成するためのチェーンエンジンのハンドルを指定します。 NULLを指定した場合は、デフォルトのチェーンエンジンが利用されます。 pCertContextは、証明書コンテキストを指定します。 この証明書とこれより上位に存在する証明書が、証明書チェーンに含まれることになります。 pTimeは、NULLで問題ありません。 hAdditionalStoreは、明示的にチェーンエンジンに検索させたい証明書ストアを指定します。 不要な場合は、NULLを指定することができます。 pChainParaは、証明書チェーンを作成するための情報を含むCERT_CHAIN_PARA構造体のアドレスを指定します。 dwFlagsは、ネットワークを通じて証明書の失効確認を行う場合に指定します。 この作業が不要な場合は、0で問題ありません。 pvReservedは、予約されているためNULLを指定します。 ppChainContextは、作成された証明書チェーンを受け取るPCCERT_CHAIN_CONTEXTのアドレスを指定します。

不要になった証明書チェーンは、CertFreeCertificateChainで開放することになります。

void WINAPI CertFreeCertificateChain(
  PCCERT_CHAIN_CONTEXT pChainContext
);

pChainContextは、開放したい証明書チェーンを指定します。

今回のプログラムは、特定の証明書から証明書チェーンを作成し、 そこに含まれる証明書が有効であるかどうかを確認します。

#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_SIMPLE_CHAIN   pSimpleChain;
	PCERT_CHAIN_ELEMENT  pChainElement;
	DWORD                i, j;
	TCHAR                szBuf[256];
	TCHAR                szCaption[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;
	}

	for (i = 0; i < pChainContext->cChain; i++) {
		pSimpleChain = pChainContext->rgpChain[i];
		for (j = 0; j < pSimpleChain->cElement; j++) {
			pChainElement = pSimpleChain->rgpElement[j];
			CertGetNameString(pChainElement->pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szBuf, sizeof(szBuf));
			if (pChainElement->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
				lstrcpy(szCaption, TEXT("有効"));
			else
				lstrcpy(szCaption, TEXT("無効"));
			MessageBox(NULL, szBuf, szCaption, MB_OK);
		}
	}

	if (pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
		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;
}

証明書チェーンを作成する証明書を選択するために、CryptUIDlgSelectCertificateFromStoreを呼び出しています。 この関数は、指定した証明書ストア(今回の場合CA)に存在する証明書を列挙し、 そこで選択した証明書を戻り値として返すことができます。 また、列挙された証明書の詳細を表示することもできるため、 そこで「証明のパス」タブを表示して、どの証明書を有効であるかを確認しておくとよいでしょう。 その内容と今回のプログラムの結果が同じであれば、正しく証明書チェーンを扱えていることになります。 次に、CertGetCertificateChainの呼び出しを確認します。

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;
}

CERT_CHAIN_PARA構造体は、証明書チェーンを作成するための情報を指定できますが、 基本的には必要ないため、構造体のサイズを初期化しておくだけで構いません。 関数が失敗する場合というのは、証明書チェーンに無効な証明書が存在したということではなく、 証明書チェーンそのものを作成できなかったことを意味しています。 このため、失敗する理由としては引数が不正であることが考えられます。

取得したCERT_CHAIN_CONTEXTには、rgpChainというチェーンを表すメンバが存在し、 pChainContext->rgpChain[i]とすることで任意のチェーンを参照できるようになります。 ただし、基本的にはチェーンは1つしか存在しません。 rgpChainの型であるPCERT_SIMPLE_CHAINは、メンバとしてrgpElementを持ち、 これが証明書チェーンに含まれる1つの証明書を表します。 この証明書は、PCERT_CHAIN_ELEMENTで表されることになります。 次のコードは、これら各メンバを利用して、証明書チェーンの証明書を走査しています。

for (i = 0; i < pChainContext->cChain; i++) {
	pSimpleChain = pChainContext->rgpChain[i];
	for (j = 0; j < pSimpleChain->cElement; j++) {
		pChainElement = pSimpleChain->rgpElement[j];
		CertGetNameString(pChainElement->pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szBuf, sizeof(szBuf));
		if (pChainElement->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
			lstrcpy(szCaption, TEXT("有効"));
		else
			lstrcpy(szCaption, TEXT("無効"));
		MessageBox(NULL, szBuf, szCaption, MB_OK);
	}
}

2行目の処理により、pSimpleChainが1つの証明書チェーンを表すようになります。 pSimpleChainはPCERT_SIMPLE_CHAINであり、各証明書を表すrgpElementと その数を表すcElementを持っています。 よって、cElementの数だけrgpElementを処理することになります。 まず、rgpElementをpChainElementで表すようにし、pChainElement->pCertContextとすることで、 証明書本体をCertGetNameStringに指定します。 これにより、証明書の発行先名を指定することができます。 次にpChainElementが有効であるかを調べるために、 CERT_TRUST_STATUS構造体のdwErrorStatusメンバを確認し、 これがCERT_TRUST_NO_ERRORである場合は、証明書が有効であると判断できます。 無効である場合というのは、有効期間が切れている場合や、 ルート証明書が信頼できない場合などが考えられ、 それぞれdwErrorStatusの値は、CERT_TRUST_IS_NOT_TIME_VALID、 CERT_TRUST_IS_UNTRUSTED_ROOTになります。

先の処理により、証明書チェーンの中でどの証明書が有効であるかが分かりましたが、 全ての証明書が有効であるかを確認したい場合は、 CERT_CHAIN_CONTEXT構造体に含まれるCERT_TRUST_STATUS構造体を確認したほうが効率的です。

if (pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
	MessageBox(NULL, TEXT("証明書チェーンに問題はありません。"), TEXT("OK"), MB_OK);
else
	MessageBox(NULL, TEXT("証明書チェーンに無効な証明書が存在します。"), NULL, MB_ICONWARNING);

dwErrorStatusがCERT_TRUST_NO_ERRORであるということは、エラーが発生していないことを意味するため、 この例の場合であれば、証明書チェーンに無効な証明書が含まれていないことが分かります。 証明書チェーン自体が有効であるかを確認したい場合、この方法を利用することになるでしょう。


戻る