EternalWindows
CSP / CSPのパラメータ

CryptoAPIを利用するアプリケーションは、大まかに2種類に別けることができると思われます。 1つは、使用するCSPや暗号化アルゴリズムなどは意識せず、 とにかく暗号化を行いたいという場合で、 もう1つは特定のCSPや暗号化アルゴリズムを使用したいという場合です。 後者の場合では、暗号化の際に使用する鍵のサイズ等を事前に確認しておきたいこともあるため、 CSPのパラメータを取得する方法を覚えておくと便利です。

BOOL WINAPI CryptGetProvParam(
  HCRYPTPROV hProv, 
  DWORD dwParam, 
  BYTE *pbData, 
  DWORD *pdwDataLen, 
  DWORD dwFlags 
);

hProvは、CSPのハンドルを指定します。 dwParamは、取得したいパラメータを表す定数を指定します。 pbDataは、パラメータを受け取るバッファを指定します。 pdwDataLenは、pbDataで指定したバッファのサイズを格納した変数のアドレスを指定します。 pbDataがNULLのときは、取得するパラメータのサイズが返ります。 dwFlagsは、一部のパラメータを利用する際に指定します。 dwParamに指定できる定数の一部を次に示します。

定数 意味
PP_CONTAINER CSPによって開かれている鍵コンテナの名前を取得する。 pbDataは、CHAR型文字列を格納するためのバッファ。 CryptAcquireContextの鍵コンテナを受け取る引数にNULLを指定した場合、 デフォルトの鍵コンテナが開かれることになるため、 その名前を確認するために利用できる。
PP_ENUMALGS_EX アルゴリズム情報を列挙する。 pbDataは、PROV_ENUMALGS_EX構造体のアドレス。 最初の情報を取得する場合はdwFlagsにCRYPT_FIRSTを指定し、 それ以降の情報を取得する場合はCRYPT_NEXTを指定する。
PP_ENUMCONTAINERS 鍵コンテナの名前を列挙する。 pbDataは、CHAR型文字列を格納するためのバッファ。 最初の鍵コンテナの名前を取得する場合はCRYPT_FIRSTを指定し、 それ以降の鍵コンテナを取得する場合はCRYPT_NEXTを指定する。
PP_KEYSET_SEC_DESCR 鍵コンテナに設定されている自己相対形式のセキュリティ記述子を取得する。 pbDataは、PSECURITY_DESCRIPTOR型の変数。 セキュリティ記述子に含める情報を指定するために、 dwFlagsにOWNER_SECURITY_INFORMATIONなどのSECURITY_INFORMATIONフラグを指定する。
PP_NAME CSPの名前を取得する。 pbDataは、CHAR型文字列を格納するためのバッファ。 CryptAcquireContextのCSP名にNULLを指定した場合は、 どのCSPのハンドルを取得したのか分からないため、 これを確認したいときに利用できる。
PP_PROVTYPE CSPのプロバイダタイプを取得する。 pbDataは、DWORD型変数のアドレス。

PP_ENUMALGS_EXやPP_ENUMCONTAINERSのような列挙に関する定数を除いて、 CryptGetProvParamの呼び出し方は基本的に同一となります。 次にPP_NAMEを指定して、CSPが開いている鍵コンテナの名前を取得する例を示します。

CHAR szBuf[256];
dwSize = sizeof(szBuf);
CryptGetProvParam(hProv, PP_NAME, (LPBYTE)szBuf, &dwSize, 0);

PP_NAMEを指定する際は、第3引数にCHAR型のバッファを指定します。 第4引数に指定する変数は第3引数のサイズを格納していなければならないので、 sizeof(szBuf)の値を設定しておきます。 サイズだけ取得したい場合は、第3引数にNULLを指定します。

今回のプログラムは、CryptGetProvParamにPP_ENUMALGS_EXを指定してアルゴリズム情報を取得します。 各アルゴリズムの名前を左側のリストボックスに表示し、 選択したアルゴリズムの情報を右側のリストボックスに表示します。

#include <windows.h>

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             hwndListBoxLeft = NULL;
	static HWND             hwndListBoxRight = NULL;
	static PROV_ENUMALGS_EX algs[100] = {0};

	switch (uMsg) {

	case WM_CREATE: {
		int              i;
		HCRYPTPROV       hProv;
		BOOL             bResult;
		DWORD            dwSize;
		PROV_ENUMALGS_EX alg;

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
	
		if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
			return -1;
		
		dwSize = sizeof(PROV_ENUMALGS_EX);
		bResult = CryptGetProvParam(hProv, PP_ENUMALGS_EX, (LPBYTE)&alg, &dwSize, CRYPT_FIRST);
		for (i = 0; bResult; i++) {
			algs[i] = alg;
			SendMessageA(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)alg.szName);
			bResult = CryptGetProvParam(hProv, PP_ENUMALGS_EX, (LPBYTE)&alg, &dwSize, CRYPT_NEXT);
		}

		CryptReleaseContext(hProv, 0);

		return 0;
	}

	case WM_COMMAND: {
		int              nIndex;
		DWORD            dwAlgType;
		CHAR             szLongName[256];
		TCHAR            szBuf[256];
		PROV_ENUMALGS_EX alg;
	
		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

		nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);
		alg = algs[nIndex];

		lstrcpyA(szLongName, "LongName : ");
		lstrcatA(szLongName, alg.szLongName);
		SendMessageA(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szLongName);
		
		dwAlgType = GET_ALG_CLASS(alg.aiAlgid);
		if (dwAlgType == ALG_CLASS_DATA_ENCRYPT)
			lstrcpy(szBuf, TEXT("AlgType : Encrypt"));
		else if (dwAlgType == ALG_CLASS_HASH)
			lstrcpy(szBuf, TEXT("AlgType : Hash"));
		else if (dwAlgType == ALG_CLASS_SIGNATURE)
			lstrcpy(szBuf, TEXT("AlgType : Signature"));
		else if (dwAlgType == ALG_CLASS_KEY_EXCHANGE)
			lstrcpy(szBuf, TEXT("AlgType : KeyExchange"));
		else
			lstrcpy(szBuf, TEXT("AlgType : Unknown"));

		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		if (dwAlgType != ALG_CLASS_HASH) {
			wsprintf(szBuf, TEXT("最小の鍵の長さ : %d"), alg.dwMinLen);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
			
			wsprintf(szBuf, TEXT("最大の鍵の長さ : %d"), alg.dwMaxLen);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

			wsprintf(szBuf, TEXT("デフォルトの鍵の長さ : %d"), alg.dwDefaultLen);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 3, 0, LOWORD(lParam) - LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

PP_ENUMALGS_EXを指定したCryptGetProvParamの最初の呼び出しでは、 第5引数にCRYPT_FIRSTを指定し、2回目以降の呼び出しではCRYPT_NEXTを指定することになります。 このため、最初のCRYPT_FIRSTの呼び出しは、ループ文の中に入らないようにしています。 algsはPROV_ENUMALGS_EX構造体の配列であり、これはリストボックスの項目が選択されたときに インデックスから情報を参照するために必要となります。 PP_ENUMALGS_EXの呼び出しで次の要素がなくなったときには戻り値がFALSEになるので、 これをループ文の条件式に利用できます。

PROV_ENUMALGS_EX構造体のaiAlgidは、アルゴリズムを表すIDとなっています。 どのようなアルゴリズムを表しているのかはGET_ALG_CLASSマクロで取得できるため、 その値に応じてアルゴリズム名をリストボックスに追加します。 また、ハッシュ以外のアルゴリズムは鍵を使用することになるため、 鍵に関してのメンバも表示することにしています。

鍵コンテナとセキュリティ記述子

鍵コンテナに格納される鍵ペアを使用できるユーザーを制限するべく、 鍵コンテナにはセキュリティ記述子を設定することが可能となっています。 CryptAcquireContextが呼び出されたとき、CSPは指定された鍵コンテナに設定されているセキュリティ記述子の情報と 呼び出し側スレッドのセキュリティコンテキストを比較し、 アクセスが許可された場合はCSPのハンドルが返ることになります。 鍵コンテナに設定されているセキュリティ記述子がどのアカウントにアクセスを許可しているのかを調べるべく、 次のコードを検討します。

#include <windows.h>

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCRYPTPROV           hProv;
	DWORD                i;
	DWORD                dwSize;
	BOOL                 bDefaulted;
	BOOL                 bDaclPresent;
	TCHAR                szBuf[256];
	TCHAR                szAccountName[256];
	ACL_SIZE_INFORMATION aclInformation;
	PSID                 pSidOwner;
	PACL                 pDacl;
	PACCESS_ALLOWED_ACE  pAce;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	
	if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
		return 0;

	CryptGetProvParam(hProv, PP_KEYSET_SEC_DESCR, NULL, &dwSize, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION);
	pSecurityDescriptor = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptGetProvParam(hProv, PP_KEYSET_SEC_DESCR, (LPBYTE)pSecurityDescriptor, &dwSize, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION);
	
	GetSecurityDescriptorOwner(pSecurityDescriptor, &pSidOwner, &bDefaulted);
	ConvertSidToName(pSidOwner, szAccountName, sizeof(szAccountName));
	wsprintf(szBuf, TEXT("セキュリティ記述子の所有者 : %s"), szAccountName);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

	GetSecurityDescriptorDacl(pSecurityDescriptor, &bDaclPresent, &pDacl, &bDefaulted);
	GetAclInformation(pDacl, &aclInformation, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
	for (i = 0; i < aclInformation.AceCount; i++) {
		GetAce(pDacl, i, (LPVOID *)&pAce);
		ConvertSidToName((PSID)&pAce->SidStart, szAccountName, sizeof(szAccountName));
		wsprintf(szBuf, TEXT("アクセスアカウント : %s \nアクセスマスク : %08x"), szAccountName, pAce->Mask);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}
		
	HeapFree(GetProcessHeap(), 0, pSecurityDescriptor);
	CryptReleaseContext(hProv, 0);

	return 0;
}

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName);
	SID_NAME_USE sidName;

	return LookupAccountSid(NULL, pSid, lpszName, &dwSizeName, szDomainName, &dwSizeDomain, &sidName);
}

管理者グループに属するユーザーのセキュリティコンテキストで作成された鍵コンテナは、 そのユーザーとSYSTEMアカウントにアクセスを許可することになります。 一方、一般ユーザーで作成された鍵コンテナは 自身とSYSTEMアカウント、そして管理者グループにアクセスを許可しています。 CRYPT_MACHINE_KEYSETを指定したCryptAcquireContextで作成された鍵コンテナは、 いずれの場合も、作成者のアカウントとSYSTEMアカウントにアクセスを許可します。 また、セキュリティ記述子の所有者も作成者のアカウントとなります。 ただし、CSPによってはこれらに該当しないこともありえます。

一般ユーザーの鍵コンテナが管理者グループにアクセスを許可しているという事実は、 一般ユーザーが作成した鍵ペアを管理者グループのユーザーを扱えることを意味しているように思えますが、 実際にはそうとも限りません。 たとえば、鍵コンテナが一般ユーザーのユーザープロファイルに格納されている場合、 管理者グループのユーザーはその鍵コンテナにアクセスすることはできません。 これは、アクセスが拒否されるということではなく、 コードを実行するにあたって参照されるプロファイルが自身のものとなるため、 別ユーザーのプロファイルに格納されているデータは考慮されないからです。 ただし、スレッドが特定のユーザーに偽装すれば、 参照されるプロファイルは偽装したユーザーのものになりますから、 これを利用すれば一般ユーザーの鍵コンテナにアクセスすることができます。 具体的な流れとしては、LogonUserで一般ユーザーのトークンを取得し、 次にLoadUserProfileを呼び出してプロファイルを明示的にロード、 そして最後にImpersonateLoggedOnUserで偽装を行います。

鍵コンテナに設定したセキュリティ記述子が重要な意味を持つのは、 CRYPT_MACHINE_KEYSETを指定して作成された鍵コンテナです。 この場合、鍵コンテナは特定のユーザーのプロファイルに存在するわけではないため、 どのユーザーからもアクセスの対象となり、アクセスチェックが必要です。 CRYPT_MACHINE_KEYSETで作成された鍵コンテナのセキュリティ記述子は、 自身とSYSTEMアカウントにアクセスを許可するだけですから、 たとえば、一般ユーザーが作成した鍵コンテナは、 管理者グループのユーザーからはアクセスすることはできません。 特定のユーザーに鍵コンテナへのアクセスを許可したい場合は、 まず適切なセキュリティ記述子を作成し、 それと共にPP_KEYSET_SEC_DESCRを指定したCryptSetProvParamを呼び出します。



戻る