EternalWindows
証明書検証 / CRL配布ポイント

CRLに記述されている次回更新日は、新しいCRLが配布される日時を表しています。 このため、現在の時間が更新日を超えているような場合は、 新しいCRLを基に証明書を検証することが望まれますが、 このCRLはどこからダウンロードすればよいのでしょうか。 CAは自身が運営するwebサーバーなどでCRLを公開していますが、 既存のCRLには新しいCRLが配布されているURLなどは明記されていません。 実は、このようなCRL配布ポイントは証明書の拡張情報として存在することになっており、 CAより発行される証明書には、基本的にこの拡張情報が含まれています。

図からもわかるように、CRL配布ポイントにはCRLが公開されているURLが記述されています。 この例では、3つのURLにそれぞれ異なるCRLが公開されていますが、 このようにCRLを別けて公開する理由には何が考えられるでしょうか。 たとえば、あるCRLの用途を認証用と見立てた場合、 そのCRLで失効される証明書は、認証目的で発行された証明書のみで構成するようなことが可能になり、 管理がしやすくなります。 CAは様々な用途を持った証明書を発行できますから、 1つのCRLで用途の異なった様々な証明書を失効させた場合は、 そのCRLがどのような証明書を失効させたのかは直ぐに特定できません。 もちろん、CRLを1つのURLで公開する例も数多く存在します。

CryptoAPIのカテゴリに入りながら、ネットワーク機能を備えているcryptnet.dllは、 URL情報の取得やURLへのアクセスを簡易化するための関数群を実装しています。 それらの関数の1つであるCryptGetObjectUrlは、 特定のオブジェクトからURL情報を取得することができます。

BOOL WINAPI CryptGetObjectUrl(
  LPCSTR pszUrlOid,
  LPVOID pvPara,
  DWORD dwFlags,
  PCRYPT_URL_ARRAY pUrlArray,
  DWORD *pcbUrlArray,
  PCRYPT_URL_INFO pUrlInfo,
  DWORD *pcbUrlInfo,
  LPVOID pvReserved 
);

pszUrlOidは、オブジェクトからURLをどのように取得するのかを示すOIDを指定します。 今回は、証明書のCRL配布ポイントから取得するため、URL_OID_CERTIFICATE_CRL_DIST_POINTを指定します。 pvParaは、URLの取得対象となるオブジェクトを指定します。 今回は証明書が対象ですからCERT_CONTEXT型を指定します。 dwFlagsは、オブジェクトのどこにURLが格納されているのかを示すフラグを指定します。 CRL配布ポイントは拡張情報の1つですから、拡張情報を示すCRYPT_GET_URL_FROM_EXTENSIONを市指定します。 pUrlArrayは、URLを表すデータを受け取るバッファを指定します。 pcbUrlArrayは、pUrlArrayのサイズを受け取る変数のアドレスを指定します。 pUrlInfoは、URL情報を表すデータを受け取るバッファを指定します。 不要である場合は、NULLを指定して問題ありません。 pcbUrlInfoは、pUrlInfoのサイズを受け取る変数のアドレスを指定します。 pUrlInfoにNULLを指定した場合は、NULLで問題ありません。 pvReservedは、予約されているためNULLを指定します。

今回のプログラムは、CryptGetObjectUrlを呼び出してCRL配布ポイントを取得し、 そのURLを確認します。

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

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

	for (i = 0; i < pUrlArray->cUrl; i++)
		MessageBoxW(NULL, pUrlArray->rgwszUrl[i], L"OK", MB_OK);

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

まず、証明書を選択するためにCryptUIDlgSelectCertificateFromStoreを呼び出します。 この関数は証明書を列挙するだけでなく、証明書の詳細を表示することもできるので、 そこで証明書がCRL配布ポイントを持っているかを確認してください。 CryptGetObjectUrlの呼び出しは、次のようになっています。

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は、オブジェクト(今回の場合は証明書)のどこからURLを取得するかを表す定数をしています。 CRL配布ポイントは、拡張情報または拡張プロパティに存在することが考えられるため、 CRYPT_GET_URL_FROM_EXTENSIONとCRYPT_GET_URL_FROM_PROPERTYを指定しています。 1回目の呼び出しでは、第4引数にNULLを指定して取得するURLのサイズを取得し、 その分のメモリを確保してから、2回目の呼び出しでURLを実際に取得します。 pUrlArray->rgwszUrl[i]とすることによって、URLを参照できるようになります。

拡張情報の利用

証明書の拡張情報は、pContext->pCertInfo->rgExtensionの配列として存在することから、 いずれかの要素にCRL配布ポイントが存在しているはずです。 この点に注目した場合、CryptGetObjectUrlを呼び出さなくても、 CRL配布ポイントを取得できることが分かります。

#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;
	PCERT_EXTENSION       pCertExtension;
	DWORD                 i;
	DWORD                 dwSize;
	PCRL_DIST_POINTS_INFO pDistPoints;
	
	hStore = CertOpenSystemStore(0, TEXT("CA"));
	if (hStore == NULL)
		return 0;

	pContext = CryptUIDlgSelectCertificateFromStore(hStore, NULL, NULL, NULL, CRYPTUI_SELECT_FRIENDLYNAME_COLUMN, 0, NULL);
	if (pContext == NULL) {
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}
	
	pCertExtension = CertFindExtension(szOID_CRL_DIST_POINTS, pContext->pCertInfo->cExtension, pContext->pCertInfo->rgExtension);
	if (pCertExtension == NULL) {
		MessageBox(NULL, TEXT("CRL配布ポイントが存在しません。"), NULL, MB_ICONWARNING);
		CertFreeCertificateContext(pContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}

	CryptDecodeObject(X509_ASN_ENCODING, szOID_CRL_DIST_POINTS, pCertExtension->Value.pbData, pCertExtension->Value.cbData, 0, NULL, &dwSize);
	pDistPoints = (PCRL_DIST_POINTS_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptDecodeObject(X509_ASN_ENCODING, szOID_CRL_DIST_POINTS, pCertExtension->Value.pbData, pCertExtension->Value.cbData, 0, pDistPoints, &dwSize);

	for (i = 0; i < pDistPoints->rgDistPoint[0].DistPointName.FullName.cAltEntry; i++)
		MessageBoxW(NULL, pDistPoints->rgDistPoint[0].DistPointName.FullName.rgAltEntry[i].pwszURL, L"OK", MB_OK);
	
	HeapFree(GetProcessHeap(), 0, pDistPoints);
	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	
	return 0;
}

CertFindExtensionは、第1引数で示される拡張情報を第3引数の配列から取得します。 szOID_CRL_DIST_POINTSは、CRL配布ポイントを表しているため、 戻り値はCRL配布ポイントを表す拡張情報です。 後は、これをCryptDecodeObjectでデコードすれば、 PCRL_DIST_POINTS_INFO型で表現することができます。



戻る