openssl的功能十分强大,比如可以实现无缝的io过滤,可是实现安全套接字,可以实现数据加密解 密等等,其实openssl的证书认证功能可以实现C/S模式程序的版本控制功能,最重要的就是使用在ssl握手的时候会传递证书,而证书中很多字段是可 以自定义的,服务器可以通过查看客户端的证书得到客户端的信息,反之客户端也可以通过服务器的证书得到服务器的信息,很多人会认为这有点复杂了,实现信息 互通大可不必使用ssl握手,标准套接字就可以,然而使用ssl的目的就是使得通信更加保密,或者就是使用ssl的清晰的过程,openssl的握手过程 十分清晰,为我们考虑了很多我们可能考虑不到的事情,而且openssl的api非常丰富,我们可以在很安全很稳定的环境下轻松得到很充足的信息,何乐而 不为!
最简单的版本控制就是在控制端使用一个全局的变量,这个变量可以是int类型,它的每一个位代表一个功能,在程序初始化的时候服务器和作为控制端的客户端 进行ssl握手,期间服务器会将证书传递给控制端,控制端可以根据服务器证书的内容得到这个全局的版本控制变量,然后解析这个变量得到该程序可以使用的功 能,或者过程反过来也可以,就是客户端将证书传递给服务器,服务器通过验证客户端的证书来确定提供哪些服务,亦或者服务器和客户端进行双向验证。以下就是 一个实际的例子,首先看一下服务器的粗略代码:
#include "StdAfx.h"
#include "Server_Verify.h"
DWORD WINAPI VerifyProc( LPVOID lpParam ) ;
int IsValidCert( char * strCertPath ) //这个函数作为回调函数出现,实现内部认证策略,这里仅仅给出一个简单的实例
{
X509 * server_cert = NULL;
char* str;
BIO *pbio = BIO_new_file((const char*)strCertPath, "r");
if( pbio == NULL )
{
return 0;
}
server_cert = PEM_read_bio_X509(pbio,NULL,NULL,NULL);
if( server_cert == NULL )
{
return 0;
}
str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
if ( str == NULL )
{
X509_free (server_cert);
return 0;
}
char *sub = strstr(str,"CN=");
int len = 0;
for ( len = 0;(char)*(sub+len) != '/';len++);
sub[len] = 0;
sub = sub+3;
int sublen = strlen(sub);
for ( ;(char)*(sub+sublen) != ')';sublen--);
if(sublen <= 0||')' != *(sub+sublen))
{
X509_free (server_cert);
return 0;
}
for ( len = sublen - 1;'(' != *(sub+len)&&len != 0;len--);
if ( len <= 0 )
{
X509_free (server_cert);
return 0;
}
sub[sublen-1] = 0;
sub = sub+len+1;
//获取SSL证书中的版本信息
int nVersion = atoi(sub);
OPENSSL_free (str);
X509_free (server_cert);
return 1;
}
CServerVerify::CServerVerify(void)
{
g_hListenThread = NULL;
m_soServer = INVALID_SOCKET;
m_serverip=_T("") ;
m_bExit = false;
}
int CServerVerify::StartListen(const char* lpszServerIP, int nPort, HWND hMsgRevWnd)
{
m_serverip = (CString)lpszServerIP;
m_port = nPort;
m_meth = SSLv23_server_method();
m_ctx = SSL_CTX_new (m_meth);
char szFilePath[_MAX_PATH];
::GetModuleFileNameA(NULL, szFilePath, sizeof(szFilePath));
...//在szFilePath中得到证书文件的全路径
if (SSL_CTX_use_certificate_file(m_ctx, szFilePath, SSL_FILETYPE_PEM) != 1) {
...//版本信息的出错处理
}
int server_ID; //这个server_ID代表了服务器端自己验证的逻辑,要验证客户端证书,首先自己的证书要通过验证
if( !IsValidCert_server[ID]( szFilePath ) ) //验证是否是合法证书,合法证书必须符合本公司的认证策略规定
{
...//证书不合法的出错处理
}
...//在szFilePath中的到key文件的全路径
if (SSL_CTX_use_PrivateKey_file(m_ctx, szFilePath, SSL_FILETYPE_PEM) != 1) {
...//key文件和证书不匹配的出错处理
}
m_soServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
...//套接字创建的出错处理
...//套接字参数设置,使用ioctl
...//套接字绑定,使用bind
...//套接字侦听,使用listen
DWORD dwThreadID = 0;
g_hListenThread = CreateThread(NULL, 0, VerifyProc, (LPVOID)this, 0,&dwThreadID);
return (int)m_soServer;
}
int CServerVerify::StopListen()
{
...//停止认证服务线程,重置套接字
return 0;
}
SSL* InitNewSslSession( SOCKET sd, CServerVerify * pServer )
{
SSL* ssl = NULL;
int err;
ssl = SSL_new ( pServer->m_ctx );
...//出错后将ssl设置为NULL并返回ssl
SSL_set_fd (ssl, sd);
err = SSL_accept (ssl);
...//出错后将ssl设置为NULL
return ssl;
}
DWORD WINAPI VerifyProc( LPVOID lpParam )
{
CServerVerify * pServer = (CServerVerify *)lpParam ;
SOCKET soServer = pServer->m_soServer ;
FD_SET fsRead;
FD_ZERO (&fsRead);
FD_SET (soServer, &fsRead);
while ( !pServer->m_bExit )
{
int nSelectRes = select (0, &fsRead, NULL, NULL, NULL);
...//select出错处理
if (FD_ISSET(soServer, &fsRead))
{
struct sockaddr_in saddrClient;
size_t szAddress = sizeof(saddrClient);
SOCKET clientsd = accept( soServer, (sockaddr*)&saddrClient, (int*)&szAddress );
...//accept出错处理
int mode = 0;
ioctlsocket(clientsd, FIONBIO, (u_long FAR*) &mode);
SSL *ssl = InitNewSslSession( clientsd, pServer );
int client_ID; //这个ID代表了认证的策略ID。通过标准套接字recv得到,马上要进行客户端验证
//以下实际上应该调用实现认证策略的回调函数,每一个客户端在连接进来的时候会告诉服务器使用哪一个认证策略,所有的认证策略在服务器端被注册
if( !IsValidCert_server[client_ID](...) )
{
...//证书不通过时的处理,比如发送消息给客户端,告知客户端即将不可用
}
if( ssl ) //由于只需要这一个ssl进行认识服务,因此认证完成后就没有必要存在了,于是释放
{
...//ssl释放,客户端套接字的关闭
}
}
}
return 2009;
}
以下为客户端代码:
#include "StdAfx.h"
#include "Client_Verify.h"
#pragma comment(lib, "libeay32")
#pragma comment(lib, "ssleay32")
unsigned int g_iCertcode = 0; //全局的认证信息,版本控制参考字段
int GetCertCode(SSL *ssl)
{
X509 * server_cert = NULL;
char* str;
server_cert = SSL_get_peer_certificate (ssl);
if ( server_cert == NULL )
{
return 0;
}
str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
...//空指针出错处理
char *sub = strstr(str,"CN=");
int len = 0;
for ( len = 0;(char)*(sub+len) != '/';len++);
sub[len] = 0;
sub = sub+3;
int sublen = strlen(sub);
for ( ;(char)*(sub+sublen) != ')';sublen--);
if(sublen <= 0||')' != *(sub+sublen))
{
X509_free (server_cert);
return 0;
}
for ( len = sublen - 1;'(' != *(sub+len)&&len != 0;len--);
if ( len <= 0 )
{
X509_free (server_cert);
return 0;
}
sub[sublen-1] = 0;
sub = sub+len+1;
g_iCertcode = atoi(sub); //这里得到全局的版本控制信息,这也是最终目的
OPENSSL_free (str);
X509_free (server_cert);
return 1;
}
CClientVerify::CClientVerify(void)
{
SSLeay_add_ssl_algorithms();
SSL_load_error_strings();
m_soClient = INVALID_SOCKET ;
m_clientssl = NULL;
m_clientmeth = SSLv23_client_method();
}
SOCKET CClientVerify::ConnectToServer(sockaddr_in sdServer)
{
SOCKET soClient = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
...//套接字创建出错处理
if (connect(soClient, (SOCKADDR*)&sdServer, sizeof(sdServer)) == SOCKET_ERROR)
...//标准套接字连接出错处理
m_clientmeth = SSLv23_client_method();
m_clientctx = SSL_CTX_new (m_clientmeth);
...//得到根证书的完全路径
int re = SSL_CTX_load_verify_locations(m_clientctx, szFilePath , ".");
...//得到根证书的出错处理,比如根证书不存在
SSL * ssl = SSL_new(m_clientctx);
SSL_set_fd(ssl, soClient);
int err = SSL_connect(ssl);
...//ssl连接的出错处理
int vres = SSL_get_verify_result(ssl);
if ( vres != X509_V_OK )
{
...//ssl验证不通过的出错处理,比如退出进程
}
if(!GetCertCode(ssl))//这个函数本来也应该是一个回调函数,通过服务器和客户端协商确定
{
...//自定义验证不通过的出错处理,比如退出进程
}
...//保存ssl等等用户自定义操作
return soClient;
}