四:SSL服务端编程实例
服务端没有什么好讲的,跟客户端不同的地方,在于初始化时需要加载证书,然后Accept后需要再用IdSslAccept关联.关于如何用Delphi生成证书文件,详见下一讲.下面直接贴代码:
var
Form1: TForm1;
g_Wsa: TWSAData;
g_ServerSocketMain: TSocket = INVALID_SOCKET;
g_ctxServer: PSSL_CTX = nil; //SSL上下文
g_methServer: PSSL_METHOD = nil;
g_DebugCritSec: TRTLCriticalSection;
g_hAcceptThread: THandle = 0;
g_Start: BOOL = False;
implementation
{$R *.dfm}
function AcceptThread(lp: Pointer): DWORD; stdcall;
var
nAddrLen: integer;
sdClient: TSocket;
sAddrs: TSockAddr;
nErrorCode: integer;
sslServer: PSSL;
pClient_Cert: PX509;
pStr: Pchar;
nRet: integer;
buf: array[0..4095] of Char;
strBody, strSend: string;
begin
Result := 0;
while g_Start do
begin
nAddrLen := sizeof(TSockAddr);
sdClient := accept(g_ServerSocketMain, @sAddrs, @nAddrLen);
if (sdClient = INVALID_SOCKET) then
begin
nErrorCode := WSAGetLastError;
EnterCriticalSection(g_DebugCritSec);
if g_Start then Form1.Memo1.Lines.Add(Format('发生错误.错误代码:%d', [nErrorCode]));
LeaveCriticalSection(g_DebugCritSec);
end
else
begin
EnterCriticalSection(g_DebugCritSec);
Form1.Memo1.Lines.Add(Format('新连接.客户端IP:%s', [inet_ntoa(sAddrs.sin_addr)]));
LeaveCriticalSection(g_DebugCritSec);
sslServer := IdSslNew(g_ctxServer); //申请SSL会话的环境,参数就是前面我们申请的 SSL通讯方式,返回当前的SSL 连接环境的指针.
if sslServer = nil then
begin
closesocket(sdClient);
Continue;
end;
IdSslSetFd(sslServer, sdClient); //绑定读写套接字
nRet := IdSslAccept(sslServer);
if (nRet = -1) then
begin
closesocket(sdClient);
IdSslShutdown(sslServer);
IdSslFree(sslServer);
Continue;
end;
EnterCriticalSection(g_DebugCritSec);
Form1.Memo1.Lines.Add(Format('SSL连接使用的算法为:%s', [IdSSLCipherGetName(IdSSLGetCurrentCipher(sslServer))]));
LeaveCriticalSection(g_DebugCritSec);
pClient_Cert := IdSslGetPeerCertificate(sslServer);
if (pClient_Cert <> nil) then
begin
EnterCriticalSection(g_DebugCritSec);
Form1.Memo1.Lines.Add('客户端证书:');
LeaveCriticalSection(g_DebugCritSec);
pStr := IdSslX509NameOneline(IdSslX509GetSubjectName(pClient_Cert), nil, 0);
if pStr = nil then Exit;
EnterCriticalSection(g_DebugCritSec);
Form1.Memo1.Lines.Add(Format('主题: %s', [pStr]));
LeaveCriticalSection(g_DebugCritSec);
IdSslFree(pStr);
pStr := IdSslX509NameOneline(IdSslX509GetIssuerName(pClient_Cert), nil, 0);
if pStr = nil then Exit;
IdSslFree(pStr);
EnterCriticalSection(g_DebugCritSec);
Form1.Memo1.Lines.Add(Format('发行者: %s', [pStr]));
LeaveCriticalSection(g_DebugCritSec);
IdSslFree(pStr);
IdSslFree(pClient_Cert);
end
else
begin
EnterCriticalSection(g_DebugCritSec);
Form1.Memo1.Lines.Add('客户端没有证书.');
LeaveCriticalSection(g_DebugCritSec);
end;
FillChar(buf, sizeof(buf), #0);
nRet := IdSslRead(sslServer, buf, sizeof(buf));
if nRet <= 0 then
begin
closesocket(sdClient);
sdClient := INVALID_SOCKET;
IdSslShutdown(sslServer);
IdSslFree(sslServer);
sslServer := nil;
end
else
begin
EnterCriticalSection(g_DebugCritSec);
Form1.Memo1.Lines.Add('客户端发送请求:'#$D#$A + StrPas(buf));
LeaveCriticalSection(g_DebugCritSec);
end;
strBody := 'Your IP is:' + inet_ntoa(sAddrs.sin_addr);
strSend := 'HTTP/1.1 200 OK'#$D#$A +
'Content-Length: ' + IntToStr(Length(strBody)) + #$D#$A +
'Content-Type: text/html'#$D#$A#$D#$A + strBody;
IdSslWrite(sslServer, @strSend[1], Length(strSend));
closesocket(sdClient);
IdSslShutdown(sslServer);
IdSslFree(sslServer);
end;
end;
g_hAcceptThread := 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
sAddr: TSockAddr;
dwThreadID: DWORD;
begin
Button1.Enabled := False;
//Create a socket
g_ServerSocketMain := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if g_ServerSocketMain = INVALID_SOCKET then
begin
MessageBox(0, '创建Socket失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Application.Terminate;
Exit;
end;
//Bind Port
FillChar(sAddr, Sizeof(TSockAddr), #0);
sAddr.sin_family := AF_INET;
sAddr.sin_port := htons(StrToIntDef(Trim(Edit1.Text), 443));
sAddr.sin_addr.S_addr := INADDR_ANY;
if Bind(g_ServerSocketMain, sAddr, Sizeof(TSockAddr)) = SOCKET_ERROR then
begin
MessageBox(0, '绑定端口失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Application.Terminate;
Exit;
end;
//listen
if listen(g_ServerSocketMain, SOMAXCONN) = SOCKET_ERROR then
begin
MessageBox(0, '监听端口失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Application.Terminate;
Exit;
end;
g_Start := TRUE;
g_hAcceptThread := CreateThread(nil, 0, @AcceptThread, nil, 0, dwThreadId);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
g_Start := FALSE;
if g_ServerSocketMain <> INVALID_SOCKET then
begin
closesocket(g_ServerSocketMain);
g_ServerSocketMain := INVALID_SOCKET;
end;
if (g_hAcceptThread <> 0) then
begin
WaitForSingleObject(g_hAcceptThread, INFINITE);
CloseHandle(g_hAcceptThread);
g_hAcceptThread := 0;
end;
end;
initialization
if WSAStartup($101, g_Wsa) <> 0 then //初始化Wsock32.dll,2.2版本可以使用MakeWord(2,2),
begin
MessageBox(0, '初始化Winsock动态库失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Halt;
end;
if not Load then //装载ssl库失败
begin
MessageBox(0, '装载ssl动态库失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Halt;
end;
IdSslLoadErrorStrings;
IdSslAddSslAlgorithms; //load所有的SSL算法.
//========================== 初始化SSL Server ================================
g_methServer := IdSslMethodV23; //建立SSL所用的method.
if g_methServer = nil then
begin
MessageBox(0, '建立SSL Server所用的method失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Halt;
end;
g_ctxServer := IdSslCtxNew(g_methServer); //初始化上下文情景.
if g_ctxServer = nil then //创建SSL_CTX失败
begin
MessageBox(0, '创建服务端SSL_CTX失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Halt;
end;
if IdSslCtxUseCertificateFile(g_ctxServer, Pchar(ExtractFilePath(GetModuleName(HInstance)) + 'UserCert.pem'), OPENSSL_SSL_FILETYPE_PEM) <= 0 then //加载证书失败
begin
MessageBox(0, '加载证书失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Halt;
end;
if IdSslCtxUsePrivateKeyFile(g_ctxServer, Pchar(ExtractFilePath(GetModuleName(HInstance)) + 'UserKey.pem'), OPENSSL_SSL_FILETYPE_PEM) <= 0 then //加载私钥失败
begin
MessageBox(0, '加载私钥失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Halt;
end;
if not IdSslCtxCheckPrivateKeyFile(g_ctxServer) = 0 then //密钥证书不匹配
begin
MessageBox(0, '密钥和证书不匹配!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST);
Halt;
end;
InitializeCriticalSection(g_DebugCritSec);
finalization
if g_ctxServer <> nil then IdSslCtxFree(g_ctxServer);
DeleteCriticalSection(g_DebugCritSec);
WSACleanup; //结束对WSocket32.dll调用
end.
完整代码下载地址:http://www.138soft.com/download/ssldemo_server.rar
实际上,稍微用心的人结合第一讲的客户端例子,已经可以写出中间人欺骗程序了.