EternalWindows
CNG / KSP

CNGには、暗号化や署名を実装するプロバイダとして、 アルゴリズムプロバイダとKSPの2種類が存在します。 アルゴリズムプロバイダには、乱数の発生やハッシュ値の生成、 そしてカーネルモードでの実行など、KSPが備えていない優れた機能が数多くありますが、 実際にはある1つの欠点があります。 それは、アルゴリズムプロバイダが鍵ペアを保存する機能を持っていないという点です。 前々節では、アルゴリズムプロバイダにおける署名の例を示しましたが、 その際にはファイルから秘密鍵をインポートするような設計になっており、 決して秘密鍵が安全に管理されているとはいえません。 作成した鍵ペアをアプリケーションが存在している間だけ利用するのであれば、 アルゴリズムプロバイダを利用しても問題はありませんが、 鍵ペアを永続的に保存したい場合は、KSPの利用を検討するべきといえます。

KSPにおける鍵ペアの保存は、アプリケーションから鍵ペアの実体を抽象化します。 鍵ペアはシステムのみアクセスできる安全なディレクトリに保存されていますが、 KSPを通じて鍵ペアを取得する場合は、具体的なファイルパスを指定することはありません。 鍵ペアの作成時には名前を設定することができるため、 その名前を指定するだけで鍵ペアを取得することができます。 この要領は、CryptoAPIにおけるCSPの鍵コンテナと同じですが、 KSPにはCSPにない鍵分離サービスを利用することができます。 正確にはこの機能は、Microsoft Key Storage ProviderというKSPのみ扱えるのですが、 基本的にアプリケーションが使用するKSPは、Microsoft Key Storage Providerであるため、 鍵分離サービスの恩恵にあずかることができます。 次に、鍵分離サービスのモデル図を示します。

アプリケーションがKSPの機能を利用するためには、ncrypt.dllの関数を呼び出すことになります。 ncrypt.dllは、アプリケーションが選択しているKSPにこの呼び出しを伝える必要がありますが、 Microsoft Key Storage Providerを選択している場合は、 LSAの中で実行されているCNG Key Isolationという名前のサービスとLRPC通信することになります。 このサービスが鍵分離サービスであり、その最大の目的は、 Microsoft Key Storage Providerのコードをアプリケーションプロセス内ではなく、 LSAという安全なプロセスで実行するという点にあります。 このようにすることで、鍵の作成や暗号化操作などを他のプロセスから妨害されることはなくなり、 鍵をサービス内で管理することができるようになります。 また、鍵分離サービスが監査機能を備えている点も重要です。 この機能により、鍵の作成や暗号化操作などが発生した際に、 その旨がセキュリティイベントログに書き込まれることになります。 鍵の作成と削除に関しては、NCryptNotifyChangeKeyを呼び出すことで、 アプリケーション内で検出することも可能です。 なお、Microsoft Key Storage Providerの正体はncrypt.dllです。 つまり、ncrypt.dllは第三者KSPに関数呼び出しを中継する以外に、 Microsoft Key Storage Providerとしても機能します。

鍵ペアの列挙

KSPによって作成された鍵ペアは、NCryptEnumKeysで列挙することができます。 次節のプログラムを実行してから次のプログラムを実行してみると、 作成した鍵ペアの名前が表示されることが分かります。

#include <windows.h>
#include <ncrypt.h>

#pragma comment (lib, "ncrypt.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               hwndListBoxLeft = NULL;
	static HWND               hwndListBoxRight = NULL;
	static NCRYPT_PROV_HANDLE hProv = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		NCryptKeyName *pKeyName;
		PVOID         pEnumState = NULL; 

		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 (NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0) != ERROR_SUCCESS)
			return -1;

		for (;;) {
			if (NCryptEnumKeys(hProv, NULL, &pKeyName, &pEnumState, 0) != ERROR_SUCCESS)
				break;
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)pKeyName->pszName);
			NCryptFreeBuffer(pKeyName);
		}
		
		NCryptFreeBuffer(pEnumState);

		return 0;  
	}
	
	case WM_COMMAND: {
		NCRYPT_KEY_HANDLE hKey;
		SECURITY_STATUS   status;
		WCHAR             szBuf[256];
		WCHAR             szKeyName[256];
		LPBYTE            lpData;
		DWORD             dwDataSize;
		DWORD             dwFlags;
		LPWSTR            lpszProperty;

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);
		SendMessage(hwndListBoxLeft, LB_GETTEXT, SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0), (LPARAM)szKeyName);

		status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0);
		if (status != ERROR_SUCCESS)
			return 0;
		
		lpszProperty = NCRYPT_ALGORITHM_PROPERTY;
		NCryptGetProperty(hKey, lpszProperty, NULL, 0, &dwDataSize, 0);
		lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
		NCryptGetProperty(hKey, lpszProperty, lpData, dwDataSize, &dwDataSize, 0);
		wsprintfW(szBuf, L"Algorithm Name : %s", lpData);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		HeapFree(GetProcessHeap(), 0, lpData);

		lpszProperty = NCRYPT_EXPORT_POLICY_PROPERTY;
		dwDataSize = sizeof(DWORD);
		NCryptGetProperty(hKey, lpszProperty, (LPBYTE)&dwFlags, dwDataSize, &dwDataSize, 0);
		wsprintfW(szBuf, L"Export Policy : %x", dwFlags);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		lpszProperty = NCRYPT_KEY_TYPE_PROPERTY;
		dwDataSize = sizeof(DWORD);
		NCryptGetProperty(hKey, lpszProperty, (LPBYTE)&dwFlags, dwDataSize, &dwDataSize, 0);
		wsprintfW(szBuf, L"Key Type : %x", dwFlags);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		return 0;
	}

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

	case WM_DESTROY:
		if (hProv != NULL)
			NCryptFreeObject(hProv);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

NCryptEnumKeysは、第3引数に鍵ペアの名前を返すため、 関数が失敗するまでこれを表示することになります。 第4引数は、どこまで鍵ペアを列挙したかを関数が確認するための値が入っており、 この中身をアプリケーションが確認する必要はありません。 予めNULLで初期化されたPVOID型のアドレスを指定するだけで問題ありません。

鍵ペアの名前が選択された場合、右側のリストボックスにその詳細が表示されます。 Algorithm Nameは、NCryptCreatePersistedKeyの第3引数に指定した値であり、 Export Policyは鍵ペアの秘密鍵をエクスポートできるかどうかなどです。 1が含まれる場合は、エクスポートが可能であることを意味します。 Key Typeは、NCryptCreatePersistedKeyの第6引数にNCRYPT_MACHINE_KEY_FLAGを指定した場合に1となります。



戻る