EternalWindows
証明書検証 / CRLの取得

CRL配布ポイントからURLを取得すれば、後はそのURLにアクセスすることでCRLを取得することができます。 これは一種のネットワークアクセスですから、ネットワーク系の関数(Winsockなど)を 呼び出す必要があるようにも思えますが、そのようなことはありません。 cryptnet.dllに実装されているCryptRetrieveObjectByUrlを呼び出すだけで、 指定したURLにアクセスすることができます。

BOOL WINAPI CryptRetrieveObjectByUrl(
  LPCTSTR pszUrl,
  LPCSTR pszObjectOid,
  DWORD dwRetrievalFlags,
  DWORD dwTimeout,
  LPVOID *ppvObject,
  HCRYPTASYNC hAsyncRetrieve,
  PCRYPT_CREDENTIALS pCredentials,
  LPVOID pvVerify,
  PCRYPT_RETRIEVE_AUX_INFO pAuxInfo 
);

pszUrlは、オブジェクトが存在するURLを指定します。 pszObjectOidは、取得するオブジェクトの種類を表すOIDを指定します。 CRLを取得する場合は、CONTEXT_OID_CRLを指定します。 dwRetrievalFlagsは、オブジェクトをどのように取得するかを示す定数を示す。 dwTimeoutは、オブジェクトの取得に要する時間をミリ秒単位で指定します。 この時間を超えてもオブジェクトを取得できない場合は、関数が失敗することになります。 ppvObjectは、取得したオブジェクトが返ります。 hAsyncRetrieveは、NULLを指定します。 pCredentialsは、CRYPT_CREDENTIALS構造体のアドレスを指定します。 URLアクセスに認証が伴う場合は、この構造体が必要となります。 pvVerifyは、検証オブジェクトのアドレスを指定します。 これは基本的にNULLを指定します。 pAuxInfoは、CRYPT_RETRIEVE_AUX_INFO構造体のアドレスを指定します。 不要な場合は、NULLを指定して問題ありません。

CRLを取得すれば、CertVerifyCRLRevocationを呼び出して証明書の失効を検証することができます。

BOOL WINAPI CertVerifyCRLRevocation(
  DWORD dwCertEncodingType,
  PCERT_INFO pCertId,
  DWORD cCrlInfo,
  PCRL_INFO rgpCrlInfo[ ]
);

dwCertEncodingTypeは、エンコーディングタイプを指定します。 pCertIdは、検証対象となる証明書のPCERT_INFOを指定します。 cCrlInfoは、rgpCrlInfoの要素数を指定します。 rgpCrlInfoは、CRLを表すPCRL_INFOの配列を指定します。 個々のCRLの失効エントリに、証明書のシリアルナンバーが含まれていないかが確認されます。 証明書が失効されていない場合は、戻り値がTRUEになります。

今回のプログラムは、CryptRetrieveObjectByUrlで取得したCRLで証明書の失効を検証します。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCERTSTORE       hStore;
	PCCERT_CONTEXT   pContext;
	DWORD            i;
	DWORD            dwFlags;
	DWORD            dwUrlSize;
	PCRYPT_URL_ARRAY pUrlArray;
	PCCRL_CONTEXT    pCrlContext;

	hStore = CertOpenSystemStore(0, TEXT("CA"));
	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;
	}
	
	dwFlags = CRYPT_GET_URL_FROM_EXTENSION | CRYPT_GET_URL_FROM_PROPERTY;
	if (!CryptGetObjectUrl(URL_OID_CERTIFICATE_CRL_DIST_POINT, (LPVOID)pContext, dwFlags, NULL, &dwUrlSize, NULL, NULL, NULL)) {
		MessageBox(NULL, TEXT("URLの取得に失敗しました。"), NULL, MB_ICONWARNING);
		CertFreeCertificateContext(pContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}
	pUrlArray = (PCRYPT_URL_ARRAY)HeapAlloc(GetProcessHeap(), 0, dwUrlSize);
	CryptGetObjectUrl(URL_OID_CERTIFICATE_CRL_DIST_POINT, (LPVOID)pContext, dwFlags, pUrlArray, &dwUrlSize, NULL, NULL, NULL);
	
	dwFlags = CRYPT_WIRE_ONLY_RETRIEVAL;
	
	for (i = 0; i < pUrlArray->cUrl; i++) {
		if (CryptRetrieveObjectByUrl(pUrlArray->rgwszUrl[i], CONTEXT_OID_CRL, dwFlags, 5000, (LPVOID *)&pCrlContext, NULL, NULL, NULL, NULL)) {
			MessageBoxW(NULL, pUrlArray->rgwszUrl[i], L"取得成功", MB_OK);
			if (!CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pContext->pCertInfo, 1, (PCRL_INFO *)&pCrlContext->pCrlInfo))
				MessageBox(NULL, TEXT("証明書が失効されています。"), NULL, MB_ICONWARNING);
		}
		else
			MessageBoxW(NULL, pUrlArray->rgwszUrl[i], L"取得失敗", MB_ICONWARNING);
	}

	HeapFree(GetProcessHeap(), 0, pUrlArray);
	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	
	return 0;
}

dwFlagsに指定しているCRYPT_WIRE_ONLY_RETRIEVALは、 ネットワークにアクセスしてオブジェクトを取得することを意味しています。 さらにこの定数は、取得したオブジェクトをローカルにキャッシュすることになっているため、 2回目以降は、CRYPT_CACHE_ONLY_RETRIEVALという定数を指定することもできます。 この定数は、キャッシュされたオブジェクトを取得するため、 ネットワークに接続されていない場合でも利用することができます。 また、ネットワークへアクセスするけれども、オブジェクトをキャッシュしたくない場合は、 CRYPT_DONT_CACHE_RESULTという定数を指定することもできます。 CryptRetrieveObjectByUrlは、次のように呼ばれています。

for (i = 0; i < pUrlArray->cUrl; i++) {
	if (CryptRetrieveObjectByUrl(pUrlArray->rgwszUrl[i], CONTEXT_OID_CRL, dwFlags, 5000, (LPVOID *)&pCrlContext, NULL, NULL, NULL, NULL)) {
		MessageBoxW(NULL, pUrlArray->rgwszUrl[i], L"取得成功", MB_OK);
		if (!CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pContext->pCertInfo, 1, (PCRL_INFO *)&pCrlContext->pCrlInfo))
			MessageBox(NULL, TEXT("証明書が失効されています。"), NULL, MB_ICONWARNING);
	}
	else
		MessageBoxW(NULL, pUrlArray->rgwszUrl[i], L"取得失敗", MB_ICONWARNING);
}

このループでは、CRL配布ポイントで取得したURLの数だけCryptRetrieveObjectByUrlを呼び出します。 タイムアウトに5000を指定しているため、5秒以内にオブジェクト(今回の場合CRL)を取得できなかった場合は、 関数が失敗することになります。 関数が成功した場合は、第5引数にオブジェクトが返ったことを意味するため、 CertVerifyCRLRevocationを呼び出して失効の確認を行っています。

CertVerifyRevocationによる失効検証

今回のプログラムでは、ネットワーク上から取得したCRLだけで証明書の失効検証を行っていますが、 正確にはこれだけでは不十分といえます。 本来ならば、システムストアに存在するCRLも取得し、それと併せて検証するべきでしょう。 特定のCRLだけを参照して失効検証を行うのではなく、 存在し得る全てのCRLを考慮して検証を行いたい場合は、CertVerifyRevocationを呼び出します。

#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;
	DWORD                  dwFlags;
	CERT_REVOCATION_PARA   revPara;
	CERT_REVOCATION_STATUS revStatus;

	hStore = CertOpenSystemStore(0, TEXT("CA"));
	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(&revPara, sizeof(CERT_REVOCATION_PARA));
	revPara.cbSize    = sizeof(CERT_REVOCATION_PARA);
	revPara.hCrlStore = CertOpenSystemStore(0, TEXT("CA"));
	
	revStatus.cbSize = sizeof(CERT_REVOCATION_STATUS);

	dwFlags = 0;
	if (!CertVerifyRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CERT_CONTEXT_REVOCATION_TYPE, 1, (PVOID *)&pContext, dwFlags, &revPara, &revStatus)) {
		if (revStatus.dwError == CRYPT_E_REVOKED)
			MessageBox(NULL, TEXT("証明書は失効されています。"), NULL, MB_ICONWARNING);
		else
			MessageBox(NULL, TEXT("証明書の失効状態を確認できません。"), TEXT("OK"), MB_OK);
	}
	else
		MessageBox(NULL, TEXT("証明書は有効です。"), TEXT("OK"), MB_OK);

	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	CertCloseStore(revPara.hCrlStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return 0;
}

CertVerifyRevocationは、検証に使うデータをCERT_REVOCATION_PARA構造体として受け取り、 検証結果をCERT_REVOCATION_STATUS構造体として返します。 CERT_REVOCATION_PARA構造体のhCrlStoreにシステムストアのハンドルを指定すれば、 そこに格納されているCRLが検証に使用されることになります。 CertVerifyRevocationが失敗した場合、CERT_REVOCATION_STATUS構造体のdwErrorを確認することで原因を把握することができます。 CRYPT_E_REVOKEDの場合は、証明書が失効されていることを意味します。 それ以外の場合は、失効状態を確認できないと表示していますが、 これは証明書にCRL配布ポイントが存在しない場合や、 オフラインであるためネットワーク上からCRLを取得できないことを意味しています。 CertVerifyRevocationは、呼び出し側からCRL_CONTEXTを受け取るのではなく、 内部でそれを取得しようとするため、これが解決できない場合は関数が失敗することになります。

具体的な話をすると、実際の失効検証を行っているのはシステムに登録されているDLLです。 そのようなDLLはCertDllVerifyRevocationという関数を実装しており、 この関数をCertVerifyRevocationが呼び出す仕組みになっています。 登録されているDLLは、次のレジストリキーから確認することができます。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 1\CertDllVerifyRevocation

基本的に証明書の検証というものは、失効確認だけでなく、 有効期間の経過やCAの信頼なども考慮しますから、 検証用関数としては、CertGetCertificateChainの呼び出しが最も適切であると思われます。 この関数は、証明書が失効されているかどうかも調べますから(CertDllVerifyRevocationの呼び出しによって)、 CertVerifyRevocationを呼び出す必要はありません。 特に今回のような、CryptRetrieveObjectByUrlで明示的にCRLを取得するコードは、 独自の失効検証DLLを作成する場合を除いて、利用することは少ないでしょう。



戻る