EternalWindows
証明書管理 / 物理ストア

システムストアに位置というものがあることは分かりましたが、 それではシステムストアをオープンするCertOpenSystemStoreは、 一体どの位置を対象としているのかという疑問が生じます。 結論から述べると、対話ログオンユーザーとしてコードを実行している場合は、 CERT_SYSTEM_STORE_CURRENT_USERが対象とされることになるのですが、 実際にCertOpenSystemStoreで取得したシステムストアに対して証明書の列挙を行うと、 CERT_SYSTEM_STORE_LOCAL_MACHINEという別のシステムストアも対象にしていることが分かります。 この動作を把握し、他のシステムストアを参照しないようにするためには、 システムストアを構成する物理ストアについての知識が必要です。

物理ストアの最大の要点は、それ自体が実際にレジストリキーに存在するということです。 システムストアも確かにMYやCAといった名前でレジストリキーに実際に存在していますが、 正確にはこれはシステムストアを表しているのではなく、 システムストアの1つの構成要素(物理ストア)を表しているに過ぎません。 システムストアというのは、あくまでシステム内部での概念的な存在であり、 それは複数の物理ストアへのリンクを持っているだけです。 システムストアを構成する物理ストアは、CertEnumPhysicalStoreで列挙することができます。

BOOL WINAPI CertEnumPhysicalStore(
  const void *pvSystemStore, 
  DWORD dwFlags, 
  void *pvArg, 
  PFN_CERT_ENUM_PHYSICAL_STORE pfnEnum 
);

pvSystemStoreは、システムストアの名前を指定します。 dwFlagsは、システムストアの位置を指定します。 pvArgは、コールバック関数へ渡したい引数を指定します。 pfnEnumは、コールバック関数のアドレスを指定します。 PFN_CERT_ENUM_PHYSICAL_STORE型は、次のように定義されています。

BOOL WINAPI CertEnumPhysicalStoreCallback(
  const void *pvSystemStore, 
  DWORD dwFlags, 
  LPCWSTR pwszStoreName, 
  PCERT_SYSTEM_STORE_INFO pStoreInfo, 
  void *pvReserved, 
  void *pvArg 
);

pvSystemStoreは、CertEnumPhysicalStoreの第1引数が格納されます。 dwFlagsは、CertEnumPhysicalStoreの第2引数が格納されます。 pwszStoreNameは、列挙された物理ストアの名前が格納されます。 物理ストアの名前は常にUNICODE文字列で表されます。 pStoreInfoは、CERT_PHYSICAL_STORE_INFO構造体へのアドレスが格納されます。 この構造体については、後述します。 pvReservedは予約されており、NULLが格納されます。 pvArgは、CertEnumPhysicalStoreの第3引数が格納されます。 次に、CertEnumPhysicalStoreを呼び出す例を示します。

#include <windows.h>

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

BOOL WINAPI CertEnumPhysicalStoreCallback(const void *pvSystemStore, DWORD dwFlags, LPCWSTR pwszStoreName, PCERT_PHYSICAL_STORE_INFO pStoreInfo, void *pvReserved, void *pvArg);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	CertEnumPhysicalStore(TEXT("CA"), CERT_SYSTEM_STORE_CURRENT_USER, NULL, CertEnumPhysicalStoreCallback);

	return 0;
}

BOOL WINAPI CertEnumPhysicalStoreCallback(const void *pvSystemStore, DWORD dwFlags, LPCWSTR pwszStoreName, PCERT_PHYSICAL_STORE_INFO pStoreInfo, void *pvReserved, void *pvArg)
{
	MessageBoxW(NULL, pwszStoreName, L"OK", MB_OK);

	return TRUE;
}

このコードは、CERT_SYSTEM_STORE_CURRENT_USERに存在するCAのシステムストアを対象としています。 コールバック関数のpwszStoreNameから確認できるように、 列挙される物理ストアは、.Default、.GroupPolicy、.LocalMachineの3つとなるはずです。 イメージとしては、この3つの物理ストアに格納されている証明書が、 CAというシステムストアに格納されている証明書ということになるのですが、 実際にはもう少し複雑です。 いくつかの角度から見ていきましょう。

そもそも、システムストアが物理ストアで構成されるといっても、 システムストアはCERT_SYSTEM_STORE_CURRENT_USERのようなレジストリ上での物理的な位置を持っていたはずです。 しかし、この位置は物理ストアである.Defaultの位置であると言い換えることもできます。 つまり、システムストアという概念上の存在を物理的な位置として見せかけているのが、.Defaultなのです。 .GroupPolicyや.LocalMachineも物理ストアであるため、 当然レジストリキーとして物理的に存在します。 たとえば、.LocalMachineの位置はCERT_SYSTEM_STORE_LOCAL_MACHINEであり、 今回の例であれば、そこのCAシステムストアが(CERT_SYSTEM_STORE_LOCAL_MACHINEの視点からすれば、 .Defaultという物理ストアが)、 CERT_SYSTEM_STORE_CURRENT_USERのCAシステムストアの一部になっているということになります。 レジストリ上で確認できるものは全て物理ストアでありながら、 その位置をプログラム上ではシステムストアの位置と表現することに注意してください。

大まかにまとめると、システムストアは.Defaultで自分の物理的な位置を表すことができ、 さらに他の物理ストアを参照することができるということです。 システムストアを構成する物理ストアはCertEnumPhysicalStoreで列挙できますが、 既定のシステムストアは、構成する物理ストアを事前に定義しています。 CERT_SYSTEM_STORE_CURRENT_USERのシステムストアであれば、次のようになっています。

システムストア 物理ストア
MY .Default
Root .Default
.LocalMachine
Trust .Default
.GroupPolicy
.LocalMachine
CA .Default
.GroupPolicy
.LocalMachine

MYシステムストアが、.Defaultだけで構成されている点は重要です。 このシステムストアには個人の証明書だけが含まれるべきですから、 複数の物理ストアから構成されてはならないわけです。 なお、CertOpenStoreやCertRegisterSystemStoreでシステムストアを明示的に作成した場合、 既定で.Default、.GroupPolicy、.LocalMachineへのリンクを含みます。

最後に、CertOpenStoreで物理ストアをオープンする方法について説明します。 システムストアではなく、物理ストアを直接オープンすれば、 .Defaultに格納される証明書のみを列挙するようなことが可能になります。

HCERTSTORE WINAPI CertOpenStore(
  LPCSTR lpszStoreProvider, 
  DWORD dwMsgAndCertEncodingType, 
  HCRYPTPROV hCryptProv, 
  DWORD dwFlags, 
  const void *pvPara 
);

lpszStoreProviderは、ストアプロバイダタイプを指定します。 ストアプロバイダタイプは、証明書ストアの種類を表すものであり、 その種類によってpvParaに指定するデータも変化します。 次に、ストアプロバイダタイプの一部を示します。

ストアプロバイダタイプ 説明
CERT_STORE_PROV_MEMORY 証明書ストアをメモリ上に作成してオープンする。 オープンした時点では、当然ながら証明書は含まれていない。 pvParaは使用されないため、NULLを指定する。
CERT_STORE_PROV_FILENAME ファイルとして保存された証明書ストアをオープンする。 pvParaは、ファイルのフルパス。
CERT_STORE_PROV_SYSTEM 指定されたシステムストアで初期化された証明書ストアをオープンする。 pvParaは、システムストアの名前。
CERT_STORE_PROV_SYSTEM_REGISTRY システムストアの.Default物理ストアをオープンする。 pvParaは、システムストアの名前。
CERT_STORE_PROV_PHYSICAL 物理ストアをオープンする。 pvParaは、[システムストア名]\\[物理ストア名]。
CERT_STORE_PROV_PKCS7 PKCS7形式で署名された証明書ストアをオープンする。 pvParaは、署名データを維持しているCRYPT_DATA_BLOB構造体のアドレス。 CERT_STORE_PROV_PKCS7を指定したCertOpenStoreの呼び出しは、 CryptGetMessageCertificatesで代用できる。

dwMsgAndCertEncodingTypeは、エンコーディングタイプを指定します。 CertOpenStoreでは基本的に0を指定することになりますが、 ストアプロバイダタイプがCERT_STORE_PROV_FILENAMEやCERT_STORE_PROV_PKCS7である場合は、 適切なエンコーディングタイプを指定することになります。 hCryptProvは、CSPのハンドルを指定します。 NULLを指定するとデフォルトのCSPが利用されます。 dwFlagsは、関数の動作内容を表す定数を指定します。 これらは、複数を組み合わしても構いません。 定義されている定数の一部を次に示します。

定数 説明
CERT_STORE_CREATE_NEW_FLAG このフラグが設定されている場合、証明書ストアが作成されてオープンされる。 既に証明書ストアが存在する場合は、関数は失敗する。 このフラグかCERT_STORE_OPEN_EXISTING_FLAGのどちらも指定されていない場合、 証明書ストアが存在する場合はそれがオープンされ、 存在しない場合は新しく作成されてオープンされる。
CERT_STORE_DELETE_FLAG 証明書ストアを削除するときに指定する。 削除が成功したかどうかは、GetLastErrorが0を返しているかどうかで判断する。
CERT_STORE_OPEN_EXISTING_FLAG 証明書ストアをオープンする。 証明書ストアが存在しない場合は、関数は失敗する。
CERT_STORE_READONLY_FLAG 証明書ストアに対してのアクセスを読み取り専用とする。 このフラグが指定されている場合は、証明書ストアに証明書を追加することはできない。

フラグの中には、特定のストアプロバイダタイプのみで扱えるものもあります。 たとえば、CERT_STORE_PROV_SYSTEM、CERT_STORE_PROV_SYSTEM_REGISTER、 CERT_STORE_PROV_PHYSICALは、システムストアの位置を表す定数をフラグとして指定することになります。 pvParaは、ストアプロバイダタイプに応じたパラメータを指定します。

今回のプログラムは、CAシステムストアの.Default物理ストアの証明書を列挙します。 物理ストアをオープンするため、CertOpenSystemStoreではなく、 CertOpenStoreを呼び出します。

#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 = CertOpenStore(CERT_STORE_PROV_PHYSICAL, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, TEXT("CA\\.Default"));
		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);
}

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

hStore = CertOpenStore(CERT_STORE_PROV_PHYSICAL, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, TEXT("CA\\.Default"));

今回は物理ストアをオープンしたいので、 CertOpenStoreのストアプロバイダタイプはCERT_STORE_PROV_PHYSICALになります。 このとき、どのシステムストアの物理ストアを対象にするかを指定しなければならないため、 第5引数にシステムストアと物理ストアの名前を組合して指定します。 また、システムストア名だけではどの位置のシステムストアか分からないため、 第4引数にはシステムストアの位置を指定します。 ただし、今回のように.Default物理ストアを参照する場合は、 CERT_STORE_PROV_SYSTEM_REGISTRYを利用したほうが効率的です。

hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, TEXT("CA"));

CERT_STORE_PROV_SYSTEM_REGISTRYは、.Default物理ストアをオープンするため、 第5引数にはシステムストアの名前だけを指定することができます。

CertOpenStoreはシステムストアをオープンする機能を持っていますが、 呼び出し方としてはCertOpenSystemStoreのほうが単純です。 ただし、CertOpenSystemStoreは指定したシステムストアが存在しない場合は、 その指定された名前で証明書ストアを新しく作成することになっているため、 この動作が好ましくない場合は、CertOpenStoreを呼び出すことになります。

hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG, TEXT("CA"));

CertOpenStoreでシステムストアをオープンする場合は、CERT_STORE_PROV_SYSTEMを指定します。 そして、この例では第4引数にCERT_STORE_OPEN_EXISTING_FLAGを指定していることに注意してください。 このフラグを指定すれば、証明書ストアが存在しない場合は関数が失敗することになります。


戻る