SSL 上の TCP 通信を行うサーバとクライアントの実装方法

この資料では SSP を使って、SSL 上の TCP 通信を行う方法を示します。

SSL のコードを書くことがやや複雑なことに加えて、SSL を利用するにはサーバー証明書などが必要になります。 ネットワークやサーバーの管理をしている人はわかるでしょうが、普通はあまり馴染みがないものだと思います。

とはいえ、「SSL とは何か?」 とか 「サーバー証明書とは何か?」 とか、そういうテーマについてはまた別の機会に書くことにして、 ここではプログラミングの詳細のみにとどめておきます。めちゃくちゃ長くなってしまいますからね。

SSL 固有のトピックとしては、「サーバー証明書が必要」 ということだけ覚えておいてください。 そしてサーバー証明書はコモンネーム (Common Name, CN) というもので識別されます。 この資料のテストを行うためのサーバー証明書は次のリンクからダウンロードして、インストールして使ってください。

サーバー証明書 (キーペア) と CA 証明書

コードの多くの部分は NTLM を用いたサーバーとクライアントの実装と同様です。異なるセキュリティパッケージに対して、 同様のコードが使用できることがわかると思います。

 ちなみに、この SSPI を用いた SSL の実装については、ちょっと苦い思い出があります。

私がまだこうした実装方法の知識が無かった頃、仕事で急にその知識が必要になりました。

そこであちこち資料を探したのですがなかなか良い資料が見つかりません。やっと Programming Server-Side Applications for Microsoft Windows 2000 に SSL の実装の章があることがわかりました。この本はなんとか手に入れたのですが、まだ当時は英語の書籍を読むのが今ほどは得意ではなかったので、 急いでいたこともあり分厚い洋書を読むのに恐れをなして翻訳書を探しました。

翻訳書を買って読み進めていたら、なんと、あろうことか和書には大切な SSL の実装方法だけがポカッと抜けているではありませんか...

原書に解説があるのに翻訳書には抜けている...

せっかく翻訳書を買ったのに勝手に説明を抜かされては面白くないので、その出版社に問い合わせてみました。

すると結局、 「何も言わずに割愛したのは悪かったけど、大事な部分かどうか判断する権利はこちらにある」 と一蹴されてしまいました。

こちらが仕事で必要に迫られてそれを読もうとしていたのに、大事な部分ではないとは何事だ! と少々頭にきてしまいました。

きっと翻訳書だけを読んだ人には、そこが割愛されていたことには気付かなかったでしょうけど...

それ以来、私はなるべく翻訳書ではなく原著で読むようにしています。(ぶっちゃけ、たいていの翻訳書は訳語がバラバラで何のことを言っているのかわからないことが多いですし)

では、さっそくコードを書いてみましょう。

ヘルパー関数

クライアントとサーバー両方で使用するヘルパー関数はこちらです。

以下のコードを helpers.h として保存してください。

#pragma once

#include <winsock2.h>


void PrintHexDump(DWORD length, PBYTE buffer);
BOOL ReceiveData(SOCKET hSocket, PBYTE pbBuff, ULONG ulSize);
BOOL SendData(SOCKET hSocket, PBYTE pbBuff, ULONG ulSize);

以下のコードを helpers.cpp として保存してください。

#include "helpers.h"
#include <stdio.h>


///////////////////////////////////////////////////////////////////////////////


void PrintHexDump(DWORD length, PBYTE buffer) {
     
     DWORD i,count,index;
     CHAR rgbDigits[]="0123456789abcdef";
     CHAR rgbLine[100];
     char cbLine;

     for(index = 0; length; length -= count, buffer += count, index += count) {
          count = (length > 16) ? 16:length;

          sprintf(rgbLine, "%4.4x  ",index);
          cbLine = 6;

          for(i=0;i<count;i++) {
               rgbLine[cbLine++] = rgbDigits[buffer[i] >> 4];
               rgbLine[cbLine++] = rgbDigits[buffer[i] & 0x0f];
               if(i == 7) {
                    rgbLine[cbLine++] = ':';
               } 
               else {
                    rgbLine[cbLine++] = ' ';
               }
          }
          for(; i < 16; i++) {
               rgbLine[cbLine++] = ' ';
               rgbLine[cbLine++] = ' ';
               rgbLine[cbLine++] = ' ';
          }

          rgbLine[cbLine++] = ' ';

          for(i = 0; i < count; i++) {
               if(buffer[i] < 32 || buffer[i] > 126) 
               {
                    rgbLine[cbLine++] = '.';
               } 
               else {
                    rgbLine[cbLine++] = buffer[i];
               }
          }
          
          rgbLine[cbLine++] = 0;
          printf("%s\n", rgbLine);
     }
     

} // end PrintHexDump


///////////////////////////////////////////////////////////////////////////////


BOOL ReceiveData(SOCKET hSocket, PBYTE pbBuff, ULONG ulSize) {

     BYTE bTemp[1024];
     PBYTE pBuffPointer = pbBuff;
     ULONG ByteRead = 0;
     ULONG ulTotalSize = ulSize;

     do {
          ::ZeroMemory(bTemp, 1024);

          ByteRead = recv(hSocket, (char*)bTemp, 
               ulTotalSize > 1024 ? 1024 : ulTotalSize, 0);

          if(SOCKET_ERROR == ByteRead) {
               printf("Error in receiving data.\n");
               return FALSE;
          }

          memcpy(pBuffPointer, bTemp, ByteRead);

          pBuffPointer += ByteRead;
          ulTotalSize -= ByteRead;
     
     } while(ulTotalSize > 0);

     return TRUE;
}


///////////////////////////////////////////////////////////////////////////////


BOOL SendData(SOCKET hSocket, PBYTE pbBuff, ULONG ulSize) {

     PBYTE pBuffPointer = pbBuff;
     ULONG ByteSent = 0;
     ULONG ulTotalSize = ulSize;

     do {
          ByteSent = send(hSocket, (char*)pBuffPointer, ulTotalSize, 0);

          if(SOCKET_ERROR == ByteSent) {
               printf("Error in sending data.\n");
               return FALSE;
          }

          pBuffPointer += ByteSent;
          ulTotalSize -= ByteSent;
     
     } while(ulTotalSize > 0);

     return TRUE;
}

SSL サーバーの実装

次のコードを sslsrv.cpp として保存してください。

次のコードではクライアントからの接続を 127.0.0.1 の TCP ポート 3000 番で待ちます。 次に証明書ストアから CN が "keisukeo-hp" である証明書を探し、見つかればそれをサーバー証明書として用いて処理を続行します。 証明書がインストールされていないとここで処理が終了しますから気をつけてください。 ご自身の既に持っている証明書を使うのでしたら CN を書き換えてください。

次に SChannel 情報 (SCHANNEL_CRED) を作成し、それと証明書情報を関連付け AcquireCredentialsHandle に渡します。

ここまでくれば NTLM の場合とほぼ同様です。

ちなみに、クライアントとの このプログラムの固有の 「プロトコル」 として、先に送信するデータ量を 4 バイトで送信し、 指定されたデータ量を受け取ったら、送受信が切り替わるという風にしています。

つまり、次のようなやり取りをするということです。

クライアント→サーバー: 10
クライアント→サーバー: (10 バイトのデータ)
クライアント←サーバー: 5
クライアント←サーバー: (5 バイトのデータ)
...


#include <winsock2.h>
#include <security.h>
#include <schannel.h>
#include <sspi.h>
#include <wincrypt.h>
#include <stdio.h>

#include "helpers.h"


#define szMESSAGE_FROM_SERVER "This message was sent from sslsrv.exe."


///////////////////////////////////////////////////////////////////////////////


void main() {

     //
     // ネットワーク接続の準備
     //

     WSADATA wsaData;
     WORD wRet = WSAStartup(WINSOCK_VERSION, &wsaData);

     if (wRet) {
          printf ("WSAStartup failed. %d\n", wRet);
          return;
     }
     
     INT nRet;

     // リスニングソケットを作成
     SOCKET hListenSocket = socket(AF_INET, SOCK_STREAM, 0);
     if(hListenSocket == INVALID_SOCKET) {
          printf("socket Failed.\n");
          return;
     }
     ULONG ulAddress = inet_addr("127.0.0.1");
     SOCKADDR_IN name;
     name.sin_family = AF_INET;
     name.sin_port = htons(3000);
     memmove(&name.sin_addr, &ulAddress, sizeof(ULONG));

     // 名前をつける...
     nRet = bind(hListenSocket, (SOCKADDR*)&name, sizeof(SOCKADDR_IN));
     if(nRet) {
          printf("bind Failed.\n");
          return;
     }

     // リスニング
     nRet = listen(hListenSocket, 2);
     if(nRet) {
          printf("listen Failed.\n");
          return;
     }

     printf("Listening...\n");

     // クライアントの接続を待つ
     SOCKADDR_IN client_addr;
     INT addr_len = sizeof(client_addr);
     SOCKET hClientSocket = accept(hListenSocket, (SOCKADDR*)&client_addr, 
          &addr_len);
     if(hClientSocket == INVALID_SOCKET) {
          printf("Last error = %u\n", WSAGetLastError());
          return;
     }

     printf("Accepting a client.\n");

     closesocket(hListenSocket); // リスニング終わり

     //
     // 接続は確立された
     //

     // 証明書の情報を取得

     HCERTSTORE hCertStore = CertOpenStore(
          CERT_STORE_PROV_SYSTEM_A, 
          X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
          0,
          CERT_SYSTEM_STORE_LOCAL_MACHINE,
          "MY");

     if(NULL==hCertStore) {
          printf("CertOpenSotore Failed.\n");
          return;
     }

     PSTR pszCN = "keisukeo-hp"; // CN は証明書にあわせ変えてください!
     CERT_RDN_ATTR certRDNAttr[1];
     certRDNAttr[0].pszObjId = szOID_COMMON_NAME;
     certRDNAttr[0].dwValueType = CERT_RDN_PRINTABLE_STRING;
     certRDNAttr[0].Value.pbData = (PBYTE) pszCN;
     certRDNAttr[0].Value.cbData = lstrlen(pszCN);
     CERT_RDN certRDN = {1, certRDNAttr};

     PCCERT_CONTEXT pCertContext = CertFindCertificateInStore(
          hCertStore,
          X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
          0,
          CERT_FIND_SUBJECT_ATTR,
          &certRDN,
          NULL);

     if(NULL==pCertContext) {
          printf("CertFindCertificateInStore Failed.\n");
          return;
     }

     // SChannel 情報

     SCHANNEL_CRED sslCred = {0};
     sslCred.dwVersion = SCHANNEL_CRED_VERSION;
     sslCred.cCreds = 1;
     sslCred.paCred = &pCertContext;
     sslCred.grbitEnabledProtocols = SP_PROT_SSL3;

     // SSPI のクレデンシャルハンドル取得

     CredHandle hCredential;
     TimeStamp tsExpiry;
     SECURITY_STATUS ss;

     ss = AcquireCredentialsHandle(
          NULL, 
          UNISP_NAME, 
          SECPKG_CRED_BOTH,
          NULL, 
          &sslCred, 
          NULL, 
          NULL, 
          &hCredential, 
          &tsExpiry);

     if(ss != SEC_E_OK) {
          printf("AcquireCredentialsHandle Failed.\n");
          return;
     }

     //
     // クライアントとのハンドシェイク
     //

     SecBuffer secBufIn[1], secBufOut[1];
     SecBufferDesc secBufDescIn, secBufDescOut;
     BOOL bFirst = TRUE;
     CtxtHandle hContext;

     PBYTE pbBuff = NULL;
     ULONG ulSize = 0;

     ss = SEC_I_CONTINUE_NEEDED;

     while(ss == SEC_I_CONTINUE_NEEDED) {

          // データ転送量
          if (!ReceiveData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize))) {
               return;
          }

          // ulSize 分のサイズのデータを受け取る
          pbBuff = (PBYTE) calloc(ulSize, 1);
          if(NULL == pbBuff) {
               printf("calloc Failed.\n");
               return;
          }

          if (!ReceiveData(hClientSocket, pbBuff, ulSize)) {
               return;
          }
          
          PrintHexDump(ulSize, pbBuff);

          // ブロブの受け入れ

          secBufIn[0].BufferType = SECBUFFER_TOKEN;
          secBufIn[0].cbBuffer = ulSize;
          secBufIn[0].pvBuffer = pbBuff;
          secBufDescIn.cBuffers = 1;
          secBufDescIn.ulVersion = SECBUFFER_VERSION;
          secBufDescIn.pBuffers = secBufIn;

          secBufOut[0].BufferType = SECBUFFER_TOKEN;
          secBufOut[0].cbBuffer = 0;
          secBufOut[0].pvBuffer = NULL;
          secBufDescOut.cBuffers = 1;
          secBufDescOut.ulVersion = SECBUFFER_VERSION;
          secBufDescOut.pBuffers = secBufOut;

          ULONG ulAttr = 0;
          
          ss = AcceptSecurityContext(&hCredential, bFirst ? NULL: &hContext,
               &secBufDescIn, ulAttr | ASC_REQ_ALLOCATE_MEMORY , SECURITY_NETWORK_DREP,
               &hContext, &secBufDescOut, &ulAttr , NULL);

          printf("AcceptSecurityContext returned %x\n", ss);

          bFirst = FALSE;

          if(secBufOut[0].cbBuffer != 0) {
               ULONG ulOutSize = secBufOut[0].cbBuffer;
               // 次に送るサイズのデータを後で送る
               SendData(hClientSocket, (PBYTE) &ulOutSize, sizeof(ulOutSize));
               // データ送信
               SendData(hClientSocket, (PBYTE) secBufOut[0].pvBuffer, 
                    secBufOut[0].cbBuffer);
          
               // ASC_REQ_ALLOCATE_MEMORY フラグを立てていたので...
               FreeContextBuffer(secBufOut[0].pvBuffer);
          }

          if(pbBuff) {
               free(pbBuff);
               pbBuff = NULL;
          }
          
     } // while SEC_I_CONTINUE_NEEDED


     //
     // メッセージを暗号化して送信する
     //

     SecPkgContext_StreamSizes PkgSize;

     ss = QueryContextAttributes(
          &hContext, 
          SECPKG_ATTR_STREAM_SIZES, 
          &PkgSize);

     if(SEC_E_OK != ss) {
          printf("QueryContextAttributes Failed.\n");
          return;
     }

     ULONG ulMsgSize = strlen(szMESSAGE_FROM_SERVER);
     PBYTE pIOBuf = 
          (PBYTE) calloc(
          PkgSize.cbHeader + PkgSize.cbMaximumMessage + PkgSize.cbTrailer,
          1);

     if(!pIOBuf) {
          printf("calloc Failed.\n");
          return;
     }

     ::CopyMemory(pIOBuf + PkgSize.cbHeader, szMESSAGE_FROM_SERVER, ulMsgSize);

     SecBuffer secBufMsg[3];
     SecBufferDesc secBufDescMsg;

     // シグネチャバッファ
     secBufMsg[0].BufferType = SECBUFFER_STREAM_HEADER;
     secBufMsg[0].cbBuffer = PkgSize.cbHeader;
     secBufMsg[0].pvBuffer = pIOBuf;
     
     // メッセージバッファ
     secBufMsg[1].BufferType = SECBUFFER_DATA;
     secBufMsg[1].cbBuffer = ulMsgSize;
     secBufMsg[1].pvBuffer = pIOBuf + PkgSize.cbHeader;

     // パディングバッファ
     secBufMsg[2].BufferType = SECBUFFER_STREAM_TRAILER;
     secBufMsg[2].cbBuffer = PkgSize.cbTrailer;
     secBufMsg[2].pvBuffer = pIOBuf + PkgSize.cbHeader + ulMsgSize;

     // バッファディスクリプション
     secBufDescMsg.cBuffers = 3;
     secBufDescMsg.ulVersion = SECBUFFER_VERSION;
     secBufDescMsg.pBuffers = secBufMsg;

     ss = EncryptMessage(&hContext, 0, &secBufDescMsg, 0);

     if(SEC_E_OK != ss) {
          printf("EncryptMessage Failed.\n");
          return;
     }

     // メッセージストリームの送信
     ulSize = secBufMsg[0].cbBuffer + secBufMsg[1].cbBuffer 
          + secBufMsg[2].cbBuffer;
     SendData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     SendData(hClientSocket, pIOBuf, ulSize);

     printf("Message Stream:\n");
     PrintHexDump(ulSize, pIOBuf);

     // 割り当てメモリの解放
     if(pIOBuf) {
          free(pIOBuf); pIOBuf = NULL;
     }

     //
     // SSP のクリーンアップ
     //

     ss = DeleteSecurityContext(&hContext);

     if(SEC_E_OK != ss) {
          printf("DeleteSecurityContext Failed.\n");
          return;
     }

     ss = FreeCredentialsHandle(&hCredential);

     if(SEC_E_OK != ss) {
          printf("FreeCredentialsHandle Failed.\n");
          return;
     }

     //
     // ソケットを閉じる
     //

     shutdown(hClientSocket, SD_SEND);
     shutdown(hClientSocket ,SD_BOTH);
     closesocket(hClientSocket);

     WSACleanup ();
     
}

makefile は次の通りです。

TARGETNAME=sslsrv
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"

CPPFLAGS=\
	/nologo\
	/MT\
	/W4\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/DWIN32\
	/DSECURITY_WIN32
	
LINK32_FLAGS=\
	Secur32.lib\
	Crypt32.lib\
	Ws2_32.lib\
	/nologo\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG
	
LINK32_OBJS= \
	"$(OUTDIR)\$(TARGETNAME).obj" "$(OUTDIR)\helpers.obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.c{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $< 

.cpp{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

ヘルパー関数、sslsrv.cpp 及び makefile を同じディレクトリに配置して、 nmake すればサブディレクトリ chk に sslsrv.exe が作成されます。

SSL クライアントの実装

以下のコードを sslclt.cpp として保存します。

#include <winsock2.h>
#include <security.h>
#include <schannel.h>
#include <sspi.h>
#include <wincrypt.h>
#include <stdio.h>

#include "helpers.h"


///////////////////////////////////////////////////////////////////////////////


void main() {

     //
     // ネットワーク接続の準備
     //

     WSADATA wsaData;
     WORD wRet = WSAStartup(WINSOCK_VERSION, &wsaData);

     if (wRet) {
          printf ("WSAStartup failed. %d\n", wRet);
          return;
     }
     
     INT nRet;

     // ソケットの作成
     SOCKET hClientSocket = socket(AF_INET, SOCK_STREAM, 0);
     if(hClientSocket == INVALID_SOCKET) {
          printf("socket Failed.\n");
          return;
     }

     printf("Socket has been created.\n");

     // サーバーへ接続
     ULONG ulAddress = inet_addr("127.0.0.1");
     SOCKADDR_IN name;
     name.sin_family = AF_INET;
     name.sin_port = htons(3000);
     memmove(&name.sin_addr, &ulAddress, sizeof(ULONG));

     nRet = connect(hClientSocket, (SOCKADDR*) &name, sizeof(SOCKADDR_IN));

     if(nRet) {
          printf("connect Failed. LastError = %u\n", WSAGetLastError());
          return;
     }

     printf("Successfully connecting to the server.\n");

     //
     // ここまでにネットワーク接続は確立された
     //

     // SChannel 情報

     SCHANNEL_CRED sslCred = {0};
     sslCred.dwVersion = SCHANNEL_CRED_VERSION;
     sslCred.grbitEnabledProtocols = SP_PROT_SSL3;
     sslCred.dwFlags = 
          SCH_CRED_NO_DEFAULT_CREDS | 
          SCH_CRED_MANUAL_CRED_VALIDATION;

     // SSPI クレデンシャルの取得
     
     CredHandle hCredential;
     TimeStamp tsExpiry;
     SECURITY_STATUS ss;

     ss = AcquireCredentialsHandle(
          NULL, 
          UNISP_NAME, 
          SECPKG_CRED_OUTBOUND,
          NULL, 
          &sslCred, 
          NULL, NULL, &hCredential, &tsExpiry);

     if(ss != SEC_E_OK) {
          printf("AcquireCredentialsHandle Failed.\n");
          return;
     }

     // サーバーとのハンドシェイク

     SecBuffer secBufIn[1], secBufOut[1];
     SecBufferDesc secBufDescIn, secBufDescOut;
     BOOL bFirst = TRUE;
     CtxtHandle hContext;

     PBYTE pbBuff = NULL;
     ULONG ulSize = 0;

     ss = SEC_I_CONTINUE_NEEDED;

     while(ss == SEC_I_CONTINUE_NEEDED) {

          ULONG ulAttr = 0;

          if(bFirst) {
               secBufDescIn.cBuffers = 0;
               secBufDescIn.ulVersion = SECBUFFER_VERSION;
               secBufDescIn.pBuffers = NULL;
          }
          else {
               ULONG ulInSize;
               if (!ReceiveData(hClientSocket, (PBYTE)&ulInSize, sizeof(ulInSize))) {
                    return;
               }

               pbBuff = (PBYTE) calloc(ulInSize, 1);
               if(NULL == pbBuff) {
                    printf("calloc Failed.\n");
                    return;
               }

               if (!ReceiveData(hClientSocket, pbBuff, ulInSize)) {
                    return;
               }
               
               PrintHexDump(ulInSize, pbBuff);

               secBufIn[0].BufferType = SECBUFFER_TOKEN;
               secBufIn[0].cbBuffer = ulInSize;
               secBufIn[0].pvBuffer = pbBuff;
               secBufDescIn.cBuffers = 1;
               secBufDescIn.ulVersion = SECBUFFER_VERSION;
               secBufDescIn.pBuffers = secBufIn;
          }

          secBufOut[0].BufferType = SECBUFFER_TOKEN;
          secBufOut[0].cbBuffer = 0;
          secBufOut[0].pvBuffer = NULL;
          secBufDescOut.cBuffers = 1;
          secBufDescOut.ulVersion = SECBUFFER_VERSION;
          secBufDescOut.pBuffers = secBufOut;


          ss = InitializeSecurityContext(&hCredential, bFirst ? NULL: &hContext,
               "localhost", ulAttr | ISC_REQ_ALLOCATE_MEMORY, 0,
               SECURITY_NETWORK_DREP, &secBufDescIn, 0, &hContext, 
               &secBufDescOut, &ulAttr, NULL);

          if ( FAILED(ss) ) {
               printf ("InitializeSecurityContext failed. %x\n", ss);
               return;
          }
          
          printf("InitializeSecurityContext returned %x\n", ss);
          // 0x90312 = SEC_I_CONTINUE_NEEDED 

          bFirst = FALSE;

          if(secBufOut[0].cbBuffer != 0) {
               ULONG ulOutSize = secBufOut[0].cbBuffer;
               // 次のサイズのデータを送信する
               SendData(hClientSocket, (PBYTE) &ulOutSize, sizeof(ulOutSize));
               // データを送信する
               SendData(hClientSocket, (PBYTE) secBufOut[0].pvBuffer, 
                    secBufOut[0].cbBuffer);
          
               // ASC_REQ_ALLOCATE_MEMORY フラグを立てていたので...
               FreeContextBuffer(secBufOut[0].pvBuffer);
          }

          if(pbBuff) {
               free(pbBuff);
               pbBuff = NULL;
          }

     } // while SEC_I_CONTINUE_NEEDED


     //
     // サーバーからメッセージを受け取り復号化する
     //

     PBYTE pIOBuf;
     SecBuffer secBufMsg[4] = {0};
     SecBufferDesc secBufDescMsg;

     // メッセージストリームの受け取り
     ReceiveData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     pIOBuf = (PBYTE) calloc(ulSize, 1);
     if(!pIOBuf) {
          printf("calloc Failed.\n");
          return;
     }
     ReceiveData(hClientSocket, pIOBuf, ulSize);

     printf("Raw Message Stream:\n");
     PrintHexDump(ulSize, pIOBuf);

     secBufMsg[0].BufferType = SECBUFFER_DATA;
     secBufMsg[0].cbBuffer = ulSize;
     secBufMsg[0].pvBuffer = pIOBuf;

     secBufMsg[1].BufferType = SECBUFFER_EMPTY;
     secBufMsg[2].BufferType = SECBUFFER_EMPTY;
     secBufMsg[3].BufferType = SECBUFFER_EMPTY;

     // バッファディスクリプション
     secBufDescMsg.cBuffers = 4;
     secBufDescMsg.ulVersion = SECBUFFER_VERSION;
     secBufDescMsg.pBuffers = secBufMsg;

     ULONG lQual = 0;
     ss = DecryptMessage(&hContext, &secBufDescMsg, 0, &lQual);

     if(SEC_E_OK != ss) {
          if(SEC_E_INCOMPLETE_MESSAGE == ss) {
               printf("DecryptMessage Failed. SEC_E_INCOMPLETE_MESSAGE.\n");
          }
          else if(SEC_E_INVALID_HANDLE == ss) {
               printf("DecryptMessage Failed. SEC_E_INVALID_HANDLE.\n");
          }
          else if(SEC_E_INVALID_TOKEN == ss) {
               printf("DecryptMessage Failed. SEC_E_INVALID_TOKEN.\n");
          }
          else {
               printf("DecryptMessage Failed. (0x%08x)\n", ss);
          }
          return;
     }


     //
     // バッファの出力
     //

     PBYTE pMessageFromServer = NULL;
     ulSize = secBufMsg[1].cbBuffer;
     pMessageFromServer = (PBYTE) calloc(ulSize, 1);
     if(!pMessageFromServer) {
          printf("calloc Failed.\n");
          return;
     }
     ::CopyMemory(pMessageFromServer, secBufMsg[1].pvBuffer, ulSize);

     printf("Message from the server:\n");
     PrintHexDump(ulSize, pMessageFromServer);
     fwrite(pMessageFromServer, ulSize, 1, stdout);
     printf("\n");

     // 割り当てたメモリの解放
     if(pIOBuf) {
          free(pIOBuf); pIOBuf = NULL;
     }


     //
     // SSPI のクリーンアップ
     //

     ss = DeleteSecurityContext(&hContext);

     if(SEC_E_OK != ss) {
          printf("DeleteSecurityContext Failed.\n");
          return;
     }

     ss = FreeCredentialsHandle(&hCredential);

     if(SEC_E_OK != ss) {
          printf("FreeCredentialsHandle Failed.\n");
          return;
     }


     //
     // ソケットを閉じる
     //

     shutdown(hClientSocket, SD_SEND);
     shutdown(hClientSocket, SD_BOTH);
     closesocket(hClientSocket);

     WSACleanup ();
     
}

makefile は次の通りです。

TARGETNAME=sslclt
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"


CPPFLAGS=\
	/nologo\
	/MT\
	/W4\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/DWIN32\
	/DSECURITY_WIN32

		
LINK32_FLAGS=\
	Secur32.lib\
	Crypt32.lib\
	Ws2_32.lib\
	/nologo\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG
	
LINK32_OBJS= \
	"$(OUTDIR)\$(TARGETNAME).obj" "$(OUTDIR)\helpers.obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.c{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

.cpp{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

実行例

先に sslsrv.exe を実行してください。次のように接続待ち状態になります.

> sslsrv.exe
Listening...

この状態でもうひとつコマンドプロンプトを開き、クライアントを実行します。

すると、次のように一気にプログラムが実行されます。

> sslclt.exe
Socket has been created.
Successfully connecting to the server.
InitializeSecurityContext returned 90312
0000  16 03 00 04 bb 02 00 00:46 03 00 49 bc 65 4b 04  ........F..I.eK.
0010  89 ad dc 43 f2 0b 14 a7:5b 88 6c ef 06 97 58 77  ...C....[.l...Xw
0020  0a 49 66 9c 38 92 41 87:30 87 95 20 44 1e 00 00  .If.8.A.0.. D...
0030  0c d6 49 c1 62 ca f4 41:74 34 51 14 43 00 47 0f  ..I.b..At4Q.C.G.
0040  df a1 2a ed 6b d5 b7 fb:51 a1 fc 73 00 05 00 0b  ..*.k...Q..s....
0050  00 04 69 00 04 66 00 04:63 30 82 04 5f 30 82 04  ..i..f..c0.._0..
0060  09 a0 03 02 01 02 02 0a:61 19 01 0a 00 00 00 00  ........a.......
0070  00 02 30 0d 06 09 2a 86:48 86 f7 0d 01 01 05 05  ..0...*.H.......
0080  00 30 81 92 31 22 30 20:06 09 2a 86 48 86 f7 0d  .0..1"0 ..*.H...
0090  01 09 01 16 13 64 61 64:6f 73 61 6e 40 6b 65 69  .....dadosan@kei
00a0  63 6f 64 65 2e 63 6f 6d:31 0b 30 09 06 03 55 04  code.com1.0...U.
00b0  06 13 02 55 53 31 13 30:11 06 03 55 04 08 13 0a  ...US1.0...U....
00c0  43 61 6c 69 66 6f 72 6e:69 61 31 14 30 12 06 03  California1.0...
00d0  55 04 07 13 0b 4c 6f 73:20 41 6e 67 65 6c 65 73  U....Los Angeles
00e0  31 0e 30 0c 06 03 55 04:0a 13 05 6f 79 61 6d 61  1.0...U....oyama
00f0  31 10 30 0e 06 03 55 04:0b 13 07 6b 65 69 63 6f  1.0...U....keico
0100  64 65 31 12 30 10 06 03:55 04 03 13 09 6b 65 69  de1.0...U....kei
0110  73 75 6b 65 63 61 30 1e:17 0d 30 39 30 32 31 38  sukeca0...090218
0120  32 30 30 34 33 38 5a 17:0d 31 30 30 32 31 38 32  200438Z..1002182
0130  30 31 34 33 38 5a 30 67:31 0b 30 09 06 03 55 04  01438Z0g1.0...U.
0140  06 13 02 55 53 31 0b 30:09 06 03 55 04 08 13 02  ...US1.0...U....
0150  43 41 31 14 30 12 06 03:55 04 07 13 0b 4c 6f 73  CA1.0...U....Los
0160  20 41 6e 67 65 6c 65 73:31 0e 30 0c 06 03 55 04   Angeles1.0...U.
0170  0a 13 05 6f 79 61 6d 61:31 0f 30 0d 06 03 55 04  ...oyama1.0...U.
0180  0b 13 06 66 61 6d 69 6c:79 31 14 30 12 06 03 55  ...family1.0...U
0190  04 03 13 0b 6b 65 69 73:75 6b 65 6f 2d 68 70 30  ....keisukeo-hp0
01a0  81 9f 30 0d 06 09 2a 86:48 86 f7 0d 01 01 01 05  ..0...*.H.......
01b0  00 03 81 8d 00 30 81 89:02 81 81 00 a5 1e 8e 07  .....0..........
01c0  93 ef c2 a6 6e 12 f5 2a:b1 ab 3a 87 2d 65 6e 10  ....n..*..:.-en.
01d0  c1 59 29 2d c9 a3 d9 9f:35 6c 63 47 0d ff c1 4a  .Y)-....5lcG...J
01e0  3f bc 1a 04 36 bd 08 cd:79 41 46 65 2e 41 48 37  ?...6...yAFe.AH7
01f0  50 9e 57 55 80 a7 94 69:29 e4 1e 37 71 44 7d c9  P.WU...i)..7qD}.
0200  2d ce 33 8c 46 cd 4f 59:b7 f1 61 d3 d6 2a 1e 72  -.3.F.OY..a..*.r
0210  db 54 86 da a7 e5 ed 5c:64 46 be 0f 77 f6 f2 07  .T.....\dF..w...
0220  3f 6e c9 3f 9a 95 c3 5c:8c 1f 53 49 9d a7 39 a0  ?n.?...\..SI..9.
0230  55 05 05 b0 77 29 4f 91:95 e6 de 7b 02 03 01 00  U...w)O....{....
0240  01 a3 82 02 25 30 82 02:21 30 0e 06 03 55 1d 0f  ....%0..!0...U..
0250  01 01 ff 04 04 03 02 04:f0 30 13 06 03 55 1d 25  .........0...U.%
0260  04 0c 30 0a 06 08 2b 06:01 05 05 07 03 01 30 1d  ..0...+.......0.
0270  06 03 55 1d 0e 04 16 04:14 fd e2 7f 85 c1 5d e9  ..U...........].
0280  71 9a 84 c1 ea 91 32 9f:34 22 f8 8f 50 30 81 ce  q.....2.4"..P0..
0290  06 03 55 1d 23 04 81 c6:30 81 c3 80 14 25 28 ba  ..U.#...0....%(.
02a0  4b 60 1f 4f 45 0a 13 3d:31 f4 fa 4f 33 9a a5 55  K`.OE..=1..O3..U
02b0  6d a1 81 98 a4 81 95 30:81 92 31 22 30 20 06 09  m......0..1"0 ..
02c0  2a 86 48 86 f7 0d 01 09:01 16 13 64 61 64 6f 73  *.H........dados
02d0  61 6e 40 6b 65 69 63 6f:64 65 2e 63 6f 6d 31 0b  an@keicode.com1.
02e0  30 09 06 03 55 04 06 13:02 55 53 31 13 30 11 06  0...U....US1.0..
02f0  03 55 04 08 13 0a 43 61:6c 69 66 6f 72 6e 69 61  .U....California
0300  31 14 30 12 06 03 55 04:07 13 0b 4c 6f 73 20 41  1.0...U....Los A
0310  6e 67 65 6c 65 73 31 0e:30 0c 06 03 55 04 0a 13  ngeles1.0...U...
0320  05 6f 79 61 6d 61 31 10:30 0e 06 03 55 04 0b 13  .oyama1.0...U...
0330  07 6b 65 69 63 6f 64 65:31 12 30 10 06 03 55 04  .keicode1.0...U.
0340  03 13 09 6b 65 69 73 75:6b 65 63 61 82 10 65 07  ...keisukeca..e.
0350  26 be 67 69 be b0 48 6b:3a a4 e6 55 4f 56 30 6f  &.gi..Hk:..UOV0o
0360  06 03 55 1d 1f 04 68 30:66 30 30 a0 2e a0 2c 86  ..U...h0f00...,.
0370  2a 68 74 74 70 3a 2f 2f:6b 65 69 73 75 6b 65 77  *http://keisukew
0380  32 6b 2f 43 65 72 74 45:6e 72 6f 6c 6c 2f 6b 65  2k/CertEnroll/ke
0390  69 73 75 6b 65 63 61 2e:63 72 6c 30 32 a0 30 a0  isukeca.crl02.0.
03a0  2e 86 2c 66 69 6c 65 3a:2f 2f 5c 5c 6b 65 69 73  ..,file://\\keis
03b0  75 6b 65 77 32 6b 5c 43:65 72 74 45 6e 72 6f 6c  ukew2k\CertEnrol
03c0  6c 5c 6b 65 69 73 75 6b:65 63 61 2e 63 72 6c 30  l\keisukeca.crl0
03d0  81 98 06 08 2b 06 01 05:05 07 01 01 04 81 8b 30  ....+..........0
03e0  81 88 30 41 06 08 2b 06:01 05 05 07 30 02 86 35  ..0A..+.....0..5
03f0  68 74 74 70 3a 2f 2f 6b:65 69 73 75 6b 65 77 32  http://keisukew2
0400  6b 2f 43 65 72 74 45 6e:72 6f 6c 6c 2f 6b 65 69  k/CertEnroll/kei
0410  73 75 6b 65 77 32 6b 5f:6b 65 69 73 75 6b 65 63  sukew2k_keisukec
0420  61 2e 63 72 74 30 43 06:08 2b 06 01 05 05 07 30  a.crt0C..+.....0
0430  02 86 37 66 69 6c 65 3a:2f 2f 5c 5c 6b 65 69 73  ..7file://\\keis
0440  75 6b 65 77 32 6b 5c 43:65 72 74 45 6e 72 6f 6c  ukew2k\CertEnrol
0450  6c 5c 6b 65 69 73 75 6b:65 77 32 6b 5f 6b 65 69  l\keisukew2k_kei
0460  73 75 6b 65 63 61 2e 63:72 74 30 0d 06 09 2a 86  sukeca.crt0...*.
0470  48 86 f7 0d 01 01 05 05:00 03 41 00 4f da 32 18  H.........A.O.2.
0480  33 d7 2e b7 c2 2b cb 60:79 36 e3 c2 a6 18 d3 9b  3....+.`y6......
0490  2f dc b7 91 89 01 cb 5a:77 36 df 59 b3 08 72 9f  /......Zw6.Y..r.
04a0  5e 94 c9 4b 3a 8a c6 92:49 b9 0d 6a cc cd b7 ee  ^..K:...I..j....
04b0  48 78 1c 8d 11 03 7f a5:ac 19 f7 ab 0e 00 00 00  Hx..............
InitializeSecurityContext returned 90312
0000  14 03 00 00 01 01 16 03:00 00 3c 49 47 6d da ed  ..........<IGm..
0010  d6 7a ec 41 70 2d 1a 2f:bd 79 77 d5 ee 6e a6 c1  .z.Ap-./.yw..n..
0020  b1 ab 90 1e 3c 8d 33 c0:68 f2 c8 5c 05 2a 17 d4  ....<.3.h..\.*..
0030  b7 60 5b c6 7c 96 98 b0:93 30 0f 22 2d dc 7a 2a  .`[.|....0."-.z*
0040  b7 d5 29 7f 82 ff 8a                             ..)....
InitializeSecurityContext returned 0
Raw Message Stream:
0000  17 03 00 00 3a cb 00 86:a0 38 51 2e b3 7a 55 0d  ....:....8Q..zU.
0010  91 d3 1b 64 56 80 9d b8:f4 be d3 47 b1 34 13 85  ...dV......G.4..
0020  b1 99 14 c4 66 f0 6d da:30 f9 29 f7 49 fb 70 fd  ....f.m.0.).I.p.
0030  89 ab d7 30 46 1e fd fb:29 a0 4f d7 cd 23 28     ...0F...).O..#(
Message from the server:
0000  54 68 69 73 20 6d 65 73:73 61 67 65 20 77 61 73  This message was
0010  20 73 65 6e 74 20 66 72:6f 6d 20 73 73 6c 73 72   sent from sslsr
0020  76 2e 65 78 65 2e                                v.exe.
This message was sent from sslsrv.exe.

>

サーバー側の出力を見ると確かにクライアントの接続をアクセプトし、 通信したことがわかります。

Accepting a client.
0000  16 03 00 00 33 01 00 00:2f 03 00 49 bc 65 4b 69  ....3.../..I.eKi
0010  28 bb dc 83 6f fb fa 15:f8 e8 25 c1 f1 9e 24 5a  (...o.....%...$Z
0020  5b e8 87 cb 92 ab c1 5f:f1 41 7c 00 00 08 00 05  [......_.A|.....
0030  00 0a 00 13 00 04 01 00:                         ........
AcceptSecurityContext returned 90312
0000  16 03 00 00 84 10 00 00:80 33 18 f2 e6 33 9e 33  .........3...3.3
0010  f8 84 54 a8 2b 98 ea f4:27 1d 4f 95 d2 ea e9 d0  ..T.+...'.O.....
0020  07 f1 d3 0e 23 1d 79 b6:a1 54 d5 c6 7b b7 4c a7  ....#.y..T..{.L.
0030  64 75 f0 ee 5b 50 84 11:aa f9 ff 40 32 b4 11 9a  du..[P.....@2...
0040  01 5b 64 c8 55 b9 49 fb:ea d9 4d 7e 0e dc ee 8c  .[d.U.I...M~....
0050  32 26 40 2c bd 8c d7 3b:b7 6f 97 2f 12 f4 23 9b  2&@,...;.o./..#.
0060  c3 3f ea 97 11 a0 8d ed:3f 1b 99 5d 57 48 c7 f6  .?......?..]WH..
0070  10 03 b4 64 c4 db b3 2c:d4 56 9a 27 ae 18 2d c6  ...d...,.V.'..-.
0080  27 1c 8a bf 39 ce 7b 96:0a 14 03 00 00 01 01 16  '...9.{.........
0090  03 00 00 3c c7 da 89 27:f7 38 0b c7 d9 44 bb a3  ...<...'.8...D..
00a0  88 55 92 f9 55 4d 6c ef:a5 0e 57 63 08 f4 d6 c2  .U..UMl...Wc....
00b0  72 8f 5d 4d c5 30 1a c1:09 28 6b e8 1a d3 22 d0  r.]M.0...(k...".
00c0  da 0f 24 14 55 8c fc 60:bd 89 e6 54 eb cf f8 c4  ..$.U..`...T....
AcceptSecurityContext returned 0
Message Stream:
0000  17 03 00 00 3a cb 00 86:a0 38 51 2e b3 7a 55 0d  ....:....8Q..zU.
0010  91 d3 1b 64 56 80 9d b8:f4 be d3 47 b1 34 13 85  ...dV......G.4..
0020  b1 99 14 c4 66 f0 6d da:30 f9 29 f7 49 fb 70 fd  ....f.m.0.).I.p.
0030  89 ab d7 30 46 1e fd fb:29 a0 4f d7 cd 23 28     ...0F...).O..#(

>

実行結果を見れば、確かにハンドシェイクを行い暗号化されたメッセージを送受信していることがわかると思います。

ちなみに冒頭の写真はシーメンスの古い暗号機です。 Photo by octal. Thank you!

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説