EternalWindows
SSPI / SSPIの概要

ネットワーク上に存在するリソースを有効に活用するという点で、 現在ではネットワークログオンというものが盛んに行われています。 たとえば、ドメインにログオンしてファイルを閲覧したりするのは、その代表例と言えるでしょう。 SSPIは、このようなネットワークを介した認証やデータの送受信を安全に行うAPIであり、 認証プロトコルや暗号化アルゴリズムの詳細をアプリケーションから隠蔽します。 SSPIの機能を強調するために、まずは好ましくないネットワークログオンの方法を次に示します。

lpszUserName = ReciveData();
lpszPassword = ReciveData();

bResult = LogonUser(lpszUserName, NULL, lpszPassword, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &hToken);

SendData(bResult);

このコードは、サーバーがクライアントをログオンさせるために、 クライアントから送られてきたユーザー名とパスワードをサーバーが取得している部分です。 これを取得したサーバーはLogonUserにLOGON32_LOGON_NETWORKをしてログオンを実行し、 その結果をクライアントに返します。 クライアントと同様のアカウントがサーバー上に作成されており、 このアカウントにネットワークログオンが許可されていれば、 クライアントはサーバーにネットワークログオンをできたことになります。

上記コードの問題点は、ユーザー名やパスワードを暗号化されていないクリアテキストとして受信している点です。 仮にクライアントがこれらを暗号化して送信したとしても、 重要なデータを明示的に送信しているという点でやはり危険といえます。 また、ログオンに成功したというのは、認証を終えたことを意味するだけであり、 実際にはこの後にクライアントとサーバーのデータの送受信が行われるはずですから、 これらのデータも暗号化しておく必要があります。 アプリケーションがSSPIを呼び出せば、認証や暗号化はSSPIによって行われますから、 安全な通信を実現するために必要な作業は、全て透過的に実行されることになります。

正確に言うと、認証プロトコルや暗号化アルゴリズムを実装しているのはSSPIではなく、SSPです。 アプリケーションはSSPIを通じて使用するSSPを選択し、 SSPIを通じてSSPが実装している関数を呼び出すことになります。 アプリケーションとSSPの間にSSPIという仲介が存在するおかげで、 どのSSPを使用する場合でも統一的なコードを記述できるようになります。 SSPによっては、認証パッケージが実装する関数を関数を呼び出して、 間接的に認証を行う場合もあります。

システムにどのようなSSPが存在するかを確認したい場合は、 EnumerateSecurityPackagesでセキュリティパッケージを列挙することになります。 セキュリティパッケージとは、SSPを実装するDLLです。

SECURITY_STATUS SEC_Entry EnumerateSecurityPackages(
  PULONG pcPackages,
  PSecPkgInfo *ppPackageInfo
);

pcPackagesは、列挙されたセキュリティパッケージの数を受け取る変数のアドレスを指定します。 ppPackageInfoは、PSecPkgInfo構造体を受け取る変数のアドレスを指定します。

SSPIによって確保されたバッファは、FreeContextBufferで開放することになります。

SECURITY_STATUS SEC_Entry FreeContextBuffer(
  PVOID pvContextBuffer
);

pvContextBufferは、開放したいバッファを指定します。

今回のプログラムは、EnumerateSecurityPackagesを呼び出して、 システムに存在するセキュリティパッケージを列挙します。 SSPIを呼び出すには、SECURITY_WIN32の定義とsecur32.libへのリンクが必要になります。

#define  SECURITY_WIN32
#include <windows.h>
#include <security.h>

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

struct CAPABILITY {
	DWORD  dwFlag;
	LPTSTR lpszFlag;
};
typedef struct CAPABILITY CAPABILITY;

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 ULONG       uPackageCount = 0;
	static PSecPkgInfo lpPackageInfo = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		ULONG i;
		
		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		EnumerateSecurityPackages(&uPackageCount, &lpPackageInfo);
		
		for (i = 0; i < uPackageCount; i++)
			SendMessage(hwndListBoxLeft, LB_INSERTSTRING, i, (LPARAM)lpPackageInfo[i].Name);

		return 0;
	}
	
	case WM_COMMAND: {
		int        i;
		int        nIndex;
		TCHAR      szBuf[256];
		CAPABILITY capabilities[] = {
			SECPKG_FLAG_INTEGRITY,         TEXT("Supports integrity on messages"),
			SECPKG_FLAG_PRIVACY,           TEXT("Supports privacy (confidentiality)"),
			SECPKG_FLAG_TOKEN_ONLY,        TEXT("Only security token needed"),
			SECPKG_FLAG_DATAGRAM,          TEXT("Datagram RPC support"),
			SECPKG_FLAG_CONNECTION,        TEXT("Connection oriented RPC support"),
			SECPKG_FLAG_MULTI_REQUIRED,    TEXT("Full 3-leg required for re-auth"),
			SECPKG_FLAG_CLIENT_ONLY,       TEXT("Server side functionality not available"),
			SECPKG_FLAG_EXTENDED_ERROR,    TEXT("Supports extended error msgs"),
			SECPKG_FLAG_IMPERSONATION,     TEXT("Supports impersonation"),
			SECPKG_FLAG_ACCEPT_WIN32_NAME, TEXT("Accepts Win32 names"),
			SECPKG_FLAG_STREAM,            TEXT("Supports stream semantics"),
			SECPKG_FLAG_NEGOTIABLE,        TEXT("Can be used by the negotiate package"),
			SECPKG_FLAG_GSS_COMPATIBLE,    TEXT("GSS Compatibility Available"),
			SECPKG_FLAG_LOGON,             TEXT("Supports common LsaLogonUser"),
			SECPKG_FLAG_ASCII_BUFFERS,     TEXT("Token Buffers are in ASCII"),
			SECPKG_FLAG_FRAGMENT,          TEXT("Package can fragment to fit"),
			SECPKG_FLAG_MUTUAL_AUTH,       TEXT("Package can perform mutual authentication"),
			SECPKG_FLAG_DELEGATION,        TEXT("Package can delegate")
		};
		int nCapabilities = sizeof(capabilities) / sizeof(capabilities[0]);

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

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

		nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);

		wsprintf(szBuf, TEXT("Comment : %s"), lpPackageInfo[nIndex].Comment);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)TEXT("Capabilities:"));

		for (i = 0; i < nCapabilities; i++) {
			if (lpPackageInfo[nIndex].fCapabilities & capabilities[i].dwFlag)
				SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)capabilities[i].lpszFlag);
		}

		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:
		FreeContextBuffer(lpPackageInfo);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_CREATEでEnumerateSecurityPackagesを呼び出し、 SecPkgInfo構造体のポインタを取得しています。 各パッケージが実装しているSSPの名前を左のリストボックスに追加し、 これが選択された場合は、右のリストボックスにそのSSPの詳細が表示されます。 SecPkgInfo.fCapabilitiesには、そのSSPの能力を示すフラグが格納されているため、 定義されている一連の定数と論理積を取ることで、 fCapabilitiesに格納されている定数を特定できます。

複数個存在するSSPのうち、アプリケーションはどのSSPを使用すればよいのでしょうか。 まず、NTLMですが、これはWindows 95から使用可能であり、 Windows Client OS間の認証によく使用されます。 Kerberosは、Windows 2000から使用可能であり、 Windows Client OSがWindows Server OSのドメインにログオンする場合によく利用されます。 KerberosはNTLMと比べて、相互認証や委任をサポートする強力なプロトコルですが、 Client OS上ではKerberosを利用するサーバーを開発することはできません。 Negotiateは、サーバーが指定するプロトコルで、 クライアントが指定するプロトコルと交渉を行います。 クライアントは、サーバーに合わせず自身の好きなプロトコルを指定し、 サーバーはそのプロトコルに合した認証を行うことになります。 一般に、サーバーがNegotiateを指定した場合、 クライアントが指定するプロトコルはNTLMかKerberosです。 SSLは、HTTP通信を暗号化して行うためのもので、 webクライアントやwebサーバーの開発時に利用することになります。


戻る