EternalWindows
証明書管理 / 証明書の列挙

アプリケーションにとって、証明書ストアから証明書を取り出す必要性が生じるのは、 どのような場合が挙げられるでしょうか。 たとえば、PKI基盤によるデータの送受信を行う場合、 データの送信側は自分の身元を表すために証明書を添付しますし、 受信側はその証明書に署名を行ったCAが信頼できるかどうかを確認する必要があります。 このような、自分(個人)の証明書やCAの証明書は証明書ストアに格納されているため、 まずはCertOpenSystemStoreで証明書ストアをオープンしなければなりません。

HCERTSTORE WINAPI CertOpenSystemStore(
  HCRYPTPROV hProv, 
  LPCTSTR szSubsystemProtocol 
);

hProvは、CSPのハンドルを指定します。 NULLを指定すると、デフォルトのCSPを指定したことになります。 szSubsystemProtocolは、オープンしたいシステムストアの名前を指定します。 戻り値のHCERTSTOREが証明書ストアを表すハンドルとなります。

システムストアは証明書ストアの一種であり、 証明書ダイアログで表示されている証明書ストアは、全てシステムストアです。 どのような文字列を指定したら、どのシステムストアをオープンできることになるのかを 次に示します。

szSubsystemProtocol 意味
MY 個人
ADDRESSBOOK ほかの人
CA 中間証明機関
Root 信頼されたルート証明機関
Disallowed 信頼されない発行元

証明書ストアのハンドルは、不要になったらCertCloseStoreで閉じることになります。

BOOL WINAPI CertCloseStore(
  HCERTSTORE hCertStore, 
  DWORD dwFlags 
);

hCertStoreは、証明書ストアのハンドルを指定します。 dwFlagsは、次のいずれかの値を指定します。

意味
0 指定された証明書ストアから取得された証明書コンテキストが開放されているどうかを考慮しない。 開放されていない証明書コンテキストは、依然として利用可能かもしれない。
CERT_CLOSE_STORE_CHECK_FLAG 指定された証明書ストアから取得された証明書コンテキストが、 開放されているかどうかをチェックする。 開放されている場合は戻り値がTRUEとなり、開放されていない場合は戻り値がFALSEとなる。 このときGetLastErrorが返す値は、CRYPT_E_PENDING_CLOSEとなる。
CERT_CLOSE_STORE_FORCE_FLAG 指定された証明書ストアから取得された証明書コンテキストが開放されていない場合、 それらを強制的に開放する。

証明書ストアのハンドルから証明書を取得するには、 CertEnumCertificatesInStoreかCertFindCertificateInStoreのどちらかを呼び出すことになります。 今回は、証明書ストアに格納されている全ての 証明書を列挙するCertEnumCertificatesInStoreについて説明します。

PCCERT_CONTEXT WINAPI CertEnumCertificatesInStore(
  HCERTSTORE hCertStore, 
  PCCERT_CONTEXT pPrevCertContext 
);

hCertStoreは、証明書ストアのハンドルを指定します。 pPrevCertContextは、1つ前に列挙された証明書コンテキストを指定します。 証明書コンテキストを指定することで、関数は次にどの証明書を返せばよいのかを把握できます。 戻り値は、新しく列挙された証明書コンテキストです。

証明書コンテキストは証明書を表していますから、 証明書コンテキストがあれば証明書の情報を取得することができます。 たとえば、証明書の発行先名(所有者名)を取得したい場合は、CertGetNameStringを呼び出します。

DWORD WINAPI CertGetNameString(
  PCCERT_CONTEXT pCertContext,
  DWORD dwType,
  DWORD dwFlags,
  void *pvTypePara,
  LPTSTR pszNameString,
  DWORD cchNameString
);

pCertContextは、証明書コンテキストを指定します。 dwTypeは、CERT_NAME_SIMPLE_DISPLAY_TYPEを指定することになるでしょう。 dwFlagsは基本的に0を指定しますが、証明書の発行者名を取得したい場合は、 CERT_NAME_ISSUER_FLAGを指定することができます。 pvTypeParaは、dwTypeにCERT_NAME_SIMPLE_DISPLAY_TYPEを指定している場合はNULLで構いません。 pszNameStringは、発行先名を受け取るバッファを指定します。 cchNameStringは、バッファのサイズを指定します。 動的にバッファを確保する場合は、pszNameStringにNULLを指定し、 戻り値のバッファサイズを受け取ります。

今回のプログラムは、CertEnumCertificatesInStoreで証明書を列挙し、 その証明書の発行先(所有者)をリストボックスに追加します。

#include <windows.h>

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

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		HCERTSTORE     hStore;
		PCCERT_CONTEXT pContext = NULL;
		TCHAR          szBuf[256];
	
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		hStore = CertOpenSystemStore(0, TEXT("CA"));
		if (hStore == NULL)
			return -1;

		for (;;) {
			pContext = CertEnumCertificatesInStore(hStore, pContext);
			if (pContext == NULL)
				break;
		
			CertGetNameString(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szBuf, sizeof(szBuf));

			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}
		
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

まず、証明書ストアのハンドルを取得するためにCertOpenSystemStoreを呼び出します。

hStore = CertOpenSystemStore(0, TEXT("CA"));

CertOpenSystemStoreのシステムストア名をCAとしていることから、 CAの証明書ストアをオープンすることになります。 後続のCertEnumCertificatesInStoreでは、 CAの証明書ストアに入っている証明書が列挙対象となります。

for (;;) {
	pContext = CertEnumCertificatesInStore(hStore, pContext);
	if (pContext == NULL)
		break;

	CertGetNameString(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szBuf, sizeof(szBuf));

	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
}

CertEnumCertificatesInStoreの第2引数に戻り値となる証明書コンテキストを指定することにより、 次に列挙すべき証明書を関数側は理解できるようになります。 1回目の呼び出しでは、証明書コンテキストをまだ取得していないので、 事前にNULLで初期化しておきます。 証明書コンテキストのメンバについてはここでは深く取り上げませんが、 証明書にはその所有者を表すsubjectという情報が含まれることを指摘しておきます。 この情報は、暗号化されたバイナリデータとなっているため、 CertNameToStrという関数を呼び出して文字列に変換しています。 最後に、CertCloseStoreの呼び出しを確認します。

CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

第2引数をCERT_CLOSE_STORE_CHECK_FLAGとしていますが、これは0でも問題ありません。 CERT_CLOSE_STORE_CHECK_FLAGは、証明書コンテキストが開放されているかを内部的に確かめるという意味ですが、 この結果がFALSEであったとしても、戻り値でそれを受け取らなければ0を指定しているのと変わりありません。 CertEnumCertificatesInStoreは、新しく列挙した証明書を返すと共に、 第2引数に指定された証明書を内部で開放しているため、 この関数を使う限りでは、証明書コンテキストの開放を忘れるということはないはずです。

証明書コンテキストの参照カウント

CertEnumCertificatesInStoreは、列挙する証明書コンテキストを求める方法として、 内部的に列挙回数をカウントするのではなく、 1つ前に列挙した証明書コンテキストを要求しています。 しかし、証明書コンテキストを指定すると、 それは関数によって内部的に開放されますから、 現在持っている証明書コンテキストを維持することができなくなってしまいます。 この問題を回避する例を次に示します。

for (i = 0;; i++) {
	pContext = CertEnumCertificatesInStore(hStore, pContext);
	if (pContext == NULL)
		break;
	
	pContexts[i] = CertDuplicateCertificateContext(pContext);
}

証明書コンテキストは内部的に参照カウントという値を持っており、 実際に証明書コンテキストのメモリが開放されるかどうかは、 証明書コンテキストの参照カウントが0になっているかどうかで決定します。 CertDuplicateCertificateContextを呼び出した場合、 証明書コンテキストの参照カウントは1つ上昇することになるため、 CertEnumCertificatesInStoreの呼び出しでメモリが開放されることはなくなります。 上記のコードでは、一連の証明書をpContextsという配列で管理しているため、 後は必要に応じてpContextsの要素にアクセスすればよいことになります。



戻る