EternalWindows
SSPI / 暗号化と複合化
対応するサーバーはこちら

SSPIの強力な点は、ネットワークを通じた認証をサポートするだけでなく、 認証後に行う通信の安全も確保している点です。 認証によって作成されたコンテキストには、暗号化に使用するための鍵などが格納されていますから、 コンテキストハンドルをEncryptMessageに指定することで、 データを暗号化することができます。

SECURITY_STATUS SEC_Entry EncryptMessage(
  PCtxtHandle phContext,
  ULONG fQOP,
  PSecBufferDesc pMessage,
  ULONG MessageSeqNo
);

phContextは、コンテキストハンドルを指定します。 fQOPは、保護の品質を表す定数を指定します。 0を指定しても問題ありません。 pMessageは、SecBufferDesc構造体のアドレスを指定します。 この構造体には、暗号化したいデータを格納したSecBuffer構造体を関連付けておきます。 MessageSeqNoは、0を指定します。

EncryptMessageはデータを暗号化するための関数ですが、 この関数に指定するのは暗号化したいデータだけではありません。 この他に、署名バッファとパディングバッファを指定しなければならず、 これら合わせた3つのデータがメッセージと呼ばれています。 署名バッファとパディングバッファのサイズは、QueryContextAttributesで取得できます。

SECURITY_STATUS SEC_Entry QueryContextAttributes(
  PCtxtHandle phContext,
  ULONG ulAttribute,
  PVOID pBuffer
);

phContextは、コンテキストハンドルを指定します。 ulAttributeは、取得したい情報を表す定数を指定します。 pBufferは、情報を受け取るバッファを指定します。

今回のクライアントプログラムは、データを暗号化してサーバーに送信します。 事前に対応するサーバープログラムを起動しておいてください。

#define  SECURITY_WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <security.h>

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

SOCKET g_soc = INVALID_SOCKET;

SOCKET InitializeWinsock(LPSTR lpszServerName, LPSTR lpszPort);
BOOL ClientHandshake(PCredHandle phCredential, PCtxtHandle phContext);
void SendEncryptData(PCtxtHandle phContext, LPVOID lpData, ULONG uSize);
void SendData(LPVOID lpData, ULONG uSize);
LPVOID ReceiveData(ULONG *lpuSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szData[] = TEXT("sample");
	CredHandle hCredential;
	CtxtHandle hContext;
	TimeStamp  ts;

	if (AcquireCredentialsHandle(NULL, TEXT("NTLM"), SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, &hCredential, &ts) != SEC_E_OK) {
		MessageBox(NULL, TEXT("クレデンシャルハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	g_soc = InitializeWinsock("localhost", "4000");
	if (g_soc == INVALID_SOCKET) {
		FreeCredentialsHandle(&hCredential);
		WSACleanup();
		return 0;
	}
	
	if (!ClientHandshake(&hCredential, &hContext)) {
		FreeCredentialsHandle(&hCredential);
		closesocket(g_soc);
		WSACleanup();
		MessageBox(NULL, TEXT("認証に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	MessageBox(NULL, TEXT("認証に成功しました。"), TEXT("クライアント"), MB_OK);
	
	SendEncryptData(&hContext, szData, sizeof(szData));

	DeleteSecurityContext(&hContext);
	FreeCredentialsHandle(&hCredential);
	closesocket(g_soc);
	WSACleanup();

	return 0;
}

BOOL ClientHandshake(PCredHandle phCredential, PCtxtHandle phContext)
{
	BOOL            bFirst = TRUE;
	ULONG           uSize;
	ULONG           uAttributes = ISC_REQ_STREAM | ISC_REQ_CONFIDENTIALITY;
	SecBuffer       sbOut[1];
	SecBuffer       sbIn[1];
	SecBufferDesc   sbdOut;
	SecBufferDesc   sbdIn;
	SECURITY_STATUS ss = SEC_I_CONTINUE_NEEDED;
	
	while (ss == SEC_I_CONTINUE_NEEDED) {
		if (!bFirst) {
			sbIn[0].pvBuffer   = ReceiveData(&uSize);
			sbIn[0].cbBuffer   = uSize;
			sbIn[0].BufferType = SECBUFFER_TOKEN;

			sbdIn.ulVersion = SECBUFFER_VERSION;
			sbdIn.cBuffers  = 1;
			sbdIn.pBuffers  = sbIn;
		}
		
		sbOut[0].cbBuffer   = 0;
		sbOut[0].BufferType = SECBUFFER_TOKEN;
		sbOut[0].pvBuffer   = NULL;

		sbdOut.ulVersion = SECBUFFER_VERSION;
		sbdOut.cBuffers  = 1;
		sbdOut.pBuffers  = sbOut;
		
		ss = InitializeSecurityContext(phCredential, bFirst ? NULL : phContext, NULL, uAttributes | ISC_REQ_ALLOCATE_MEMORY,
			0, SECURITY_NETWORK_DREP, bFirst ? NULL : &sbdIn, 0, phContext, &sbdOut, &uAttributes, NULL);
		
		if (sbOut[0].cbBuffer != 0) {
			SendData(sbOut[0].pvBuffer, sbOut[0].cbBuffer);
			FreeContextBuffer(sbOut[0].pvBuffer);
		}
		
		if (!bFirst)
			HeapFree(GetProcessHeap(), 0, sbIn[0].pvBuffer);

		bFirst = FALSE;
	}

	return ss == SEC_E_OK;
}

void SendEncryptData(PCtxtHandle phContext, LPVOID lpData, ULONG uSize)
{
	LPVOID              lpSig;
	LPVOID              lpPad;
	SecBuffer           sb[3];
	SecBufferDesc       sbd;
	SecPkgContext_Sizes size;
	
	QueryContextAttributes(phContext, SECPKG_ATTR_SIZES, &size);

	lpSig = HeapAlloc(GetProcessHeap(), 0, size.cbSecurityTrailer);
	lpPad = HeapAlloc(GetProcessHeap(), 0, size.cbBlockSize);

	sb[0].cbBuffer   = size.cbSecurityTrailer;
	sb[0].BufferType = SECBUFFER_TOKEN;
	sb[0].pvBuffer   = lpSig;

	sb[1].cbBuffer   = uSize;
	sb[1].BufferType = SECBUFFER_DATA;
	sb[1].pvBuffer   = lpData;

	sb[2].cbBuffer   = size.cbBlockSize;
	sb[2].BufferType = SECBUFFER_PADDING;
	sb[2].pvBuffer   = lpPad;

	sbd.ulVersion = SECBUFFER_VERSION;
	sbd.cBuffers  = 3;
	sbd.pBuffers  = sb;

	EncryptMessage(phContext, 0, &sbd, 0);

	SendData(sb[0].pvBuffer, sb[0].cbBuffer);
	SendData(sb[1].pvBuffer, sb[1].cbBuffer);
	SendData(sb[2].pvBuffer, sb[2].cbBuffer);
	
	HeapFree(GetProcessHeap(), 0, lpSig);
	HeapFree(GetProcessHeap(), 0, lpPad);
}

void SendData(LPVOID lpData, ULONG uSize)
{
	send(g_soc, (char *)&uSize, sizeof(ULONG), 0);
	send(g_soc, (char *)lpData, uSize, 0);
}

LPVOID ReceiveData(ULONG *lpuSize)
{
	ULONG  uSize;
	LPVOID lpData;

	recv(g_soc, (char *)&uSize, sizeof(ULONG), 0);
	lpData = HeapAlloc(GetProcessHeap(), 0, uSize);
	recv(g_soc, (char *)lpData, uSize, 0);
	
	*lpuSize = uSize;

	return lpData;
}

SOCKET InitializeWinsock(LPSTR lpszServerName, LPSTR lpszPort)
{
	WSADATA    wsaData;
	ADDRINFO   addrHints;
	LPADDRINFO lpAddrList;
	SOCKET     soc;
	
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	ZeroMemory(&addrHints, sizeof(addrinfo));
	addrHints.ai_family   = AF_INET;
	addrHints.ai_socktype = SOCK_STREAM;
	addrHints.ai_protocol = IPPROTO_TCP;

	if (getaddrinfo(lpszServerName, lpszPort, &addrHints, &lpAddrList) != 0) {
		MessageBox(NULL, TEXT("ホスト情報からアドレスの取得に失敗しました。"), NULL, MB_ICONWARNING);
		WSACleanup();
		return INVALID_SOCKET;
	}

	soc = socket(lpAddrList->ai_family, lpAddrList->ai_socktype, lpAddrList->ai_protocol);

	if (connect(soc, lpAddrList->ai_addr, (int)lpAddrList->ai_addrlen) == SOCKET_ERROR) {
		int nError = WSAGetLastError();
		if (nError == WSAECONNREFUSED)
			MessageBox(NULL, TEXT("サーバーがリッスンしていません。"), NULL, MB_ICONWARNING);
		else if (nError == WSAEHOSTUNREACH)
			MessageBox(NULL, TEXT("サーバーが存在しません。"), NULL, MB_ICONWARNING);
		else
			MessageBox(NULL, TEXT("サーバーへの接続が失敗しました。"), NULL, MB_ICONWARNING);
		freeaddrinfo(lpAddrList);
		closesocket(soc);
		WSACleanup();
		return INVALID_SOCKET;
	}
	
	freeaddrinfo(lpAddrList);

	return soc;
}

今回は暗号化を行うため、ClientHandshakeのuAttributesにISC_REQ_CONFIDENTIALITYを指定しています。 SendEncryptDataという自作関数は、 暗号化したいデータとサイズを受け取り、 これと各種バッファをEncryptMessageに指定します。 そして、暗号化された各種バッファをSendDataでサーバーに送信します。 各種処理を順に見ていきます。

QueryContextAttributes(phContext, SECPKG_ATTR_SIZES, &size);

lpSig = HeapAlloc(GetProcessHeap(), 0, size.cbSecurityTrailer);
lpPad = HeapAlloc(GetProcessHeap(), 0, size.cbBlockSize);

sb[0].cbBuffer   = size.cbSecurityTrailer;
sb[0].BufferType = SECBUFFER_TOKEN;
sb[0].pvBuffer   = lpSig;

sb[1].cbBuffer   = uSize;
sb[1].BufferType = SECBUFFER_DATA;
sb[1].pvBuffer   = lpData;

sb[2].cbBuffer   = size.cbBlockSize;
sb[2].BufferType = SECBUFFER_PADDING;
sb[2].pvBuffer   = lpPad;

sbd.ulVersion = SECBUFFER_VERSION;
sbd.cBuffers  = 3;
sbd.pBuffers  = sb;

署名バッファとパディングバッファに必要なサイズは、 QueryContextAttributesにSECPKG_ATTR_SIZESを指定することで取得できます。 sizeという変数の型はSecPkgContext_Sizes構造体であり、 この構造体のcbSecurityTrailerに署名バッファのサイズが格納され、 cbBlockSizeにパディングバッファのサイズが格納されます。 バッファを確保したら、次にこれらと暗号化したいデータをSecBuffer構造体に指定します。 データの総数は3つですから、SecBuffer構造体の配列であるsbの要素数も3となります。 一通りの初期化が終われば、これをSecBufferDesc構造体であるsbdに指定します。 そして、EncryptMessageを呼び出すことになります。

EncryptMessage(phContext, 0, &sbd, 0);

SendData(sb[0].pvBuffer, sb[0].cbBuffer);
SendData(sb[1].pvBuffer, sb[1].cbBuffer);
SendData(sb[2].pvBuffer, sb[2].cbBuffer);

EncryptMessageの第2引数と第4引数は0で問題ありません。 関数が成功した場合は、各種バッファが適切に初期化されているため、 これらをSendDataで順に送信します。 この順番は、署名バッファ、暗号化されたデータ、パディングバッファにするようにしてください。 ちなみに、署名バッファはデータの送信者を保障するものであり、 パディングバッファはデータのサイズが一定値になるようにダミーのバイトを格納しています。


戻る