高性能网络库-HP-Socket 解析(3) SSLServer
HPSocket的SSLServer是继承与TcpServer 所实现的 算法部分由OpenSSL来实现 所以使用SSL必须确保OpenSSL支持库存在
首先要调用SetupSSLContext来记性SSL初始化
virtual BOOL SetupSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPasswod = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr)
{return m_sslCtx.Initialize(SSL_SM_SERVER, iVerifyMode, lpszPemCertFile, lpszPemKeyFile, lpszKeyPasswod, lpszCAPemCertFileOrPath, fnServerNameCallback);}
//会调用CCSSLContext类
BOOL CSSLContext::Initialize(EnSSLSessionMode enSessionMode, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPasswod, LPCTSTR lpszCAPemCertFileOrPath, HP_Fn_SNI_ServerNameCallback fnServerNameCallback)
{
ASSERT(!IsValid());
//如果SSL已被初始化过了
if(IsValid())
{
::SetLastError(ERROR_INVALID_STATE);
return FALSE;
}
//设置SSL模式
m_enSessionMode = enSessionMode;
//增加上下文
if(AddContext(iVerifyMode, lpszPemCertFile, lpszPemKeyFile, lpszKeyPasswod, lpszCAPemCertFileOrPath) == 0)
//取第0号位(第一个)
m_sslCtx = GetContext(0);
else
{
//清空
Cleanup();
return FALSE;
}
SetServerNameCallback(fnServerNameCallback);
//设置服务器名称回调
return TRUE;
}
BOOL CSSLServer::CheckParams()
{
if(!m_sslCtx.IsValid())
{
SetLastError(SE_SSL_ENV_NOT_READY, __FUNCTION__, ERROR_NOT_READY);
return FALSE;
}
return __super::CheckParams();
}
void CSSLServer::PrepareStart()
{
__super::PrepareStart();
m_sslPool.SetItemCapacity (GetSocketBufferSize());
m_sslPool.SetItemPoolSize (GetFreeBufferObjPool());
m_sslPool.SetItemPoolHold (GetFreeBufferObjHold());
m_sslPool.SetSessionLockTime(GetFreeSocketObjLockTime());
m_sslPool.SetSessionPoolSize(GetFreeSocketObjPool());
m_sslPool.SetSessionPoolHold(GetFreeSocketObjHold());
m_sslPool.Prepare();
}
int CSSLContext::AddContext(int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPasswod, LPCTSTR lpszCAPemCertFileOrPath)
{
int iIndex = -1;
SSL_CTX* sslCtx = SSL_CTX_new(SSLv23_method());
//初始化上下文
SSL_CTX_set_quiet_shutdown(sslCtx, 1);
//当设置为1时,假如关闭后,不通知对方,这样不适合TLS标准 ?
SSL_CTX_set_verify(sslCtx, iVerifyMode, nullptr);
//设置认证模式
SSL_CTX_set_cipher_list(sslCtx, "ALL:!aNULL:!eNULL");
//设置认证算法列表
if(m_enSessionMode == SSL_SM_SERVER)
{//如果是服务器
static volatile ULONG s_session_id_context = 0;
ULONG session_id_context = ::InterlockedIncrement(&s_session_id_context);
//互斥增加
SSL_CTX_set_session_id_context(sslCtx, (BYTE*)&session_id_context, sizeof(session_id_context));
//增加进sessionid
}
//加载证书或者密钥
if(!LoadCertAndKey(sslCtx, iVerifyMode, lpszPemCertFile, lpszPemKeyFile, lpszKeyPasswod, lpszCAPemCertFileOrPath))//失败释放
SSL_CTX_free(sslCtx);
else
{//加入Vector
iIndex = (int)m_lsSslCtxs.size();
m_lsSslCtxs.push_back(sslCtx);
}
return iIndex;
}
BOOL CSSLContext::LoadCertAndKey(SSL_CTX* sslCtx, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPasswod, LPCTSTR lpszCAPemCertFileOrPath)
{
USES_CONVERSION;
if(lpszCAPemCertFileOrPath != nullptr)
{
LPCTSTR lpszCAPemCertFile = nullptr;
LPCTSTR lpszCAPemCertPath = nullptr;
CFile fCAPemCertFile(lpszCAPemCertFileOrPath, O_RDONLY | O_CLOEXEC);
if(!fCAPemCertFile.IsExist())
{
::SetLastError(ERROR_FILE_NOT_FOUND);
return FALSE;
}
if(fCAPemCertFile.IsFile())
lpszCAPemCertFile = lpszCAPemCertFileOrPath;
else if(fCAPemCertFile.IsDirectory())
lpszCAPemCertPath = lpszCAPemCertFileOrPath;
else
{
::SetLastError(ERROR_BAD_FILE_TYPE);
return FALSE;
}
//加载认证目录
if(!SSL_CTX_load_verify_locations(sslCtx, T2CA(lpszCAPemCertFile), T2CA(lpszCAPemCertPath)))
{
::SetLastError(ERROR_INVALID_DATA);
return FALSE;
}
//加载
if(!SSL_CTX_set_default_verify_paths(sslCtx))
{
::SetLastError(ERROR_FUNCTION_FAILED);
return FALSE;
}
//判断服务器模式
if(m_enSessionMode == SSL_SM_SERVER && iVerifyMode & SSL_VM_PEER)
{
STACK_OF(X509_NAME)* caCertNames = SSL_load_client_CA_file(T2CA(lpszCAPemCertFileOrPath));
if(caCertNames == nullptr)
{
::SetLastError(ERROR_EMPTY);
return FALSE;
}
//CA列表
SSL_CTX_set_client_CA_list(sslCtx, caCertNames);
}
}
if(lpszPemCertFile != nullptr)
{
CFile fPemCertFile(lpszPemCertFile, O_RDONLY | O_CLOEXEC);
if(!fPemCertFile.IsFile())
{
::SetLastError(ERROR_FILE_NOT_FOUND);
return FALSE;
}
if(lpszPemKeyFile == nullptr)
{
::SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
CFile fPemKeyFile(lpszPemKeyFile, O_RDONLY | O_CLOEXEC);
if(!fPemKeyFile.IsFile())
{
::SetLastError(ERROR_FILE_NOT_FOUND);
return FALSE;
}
if(lpszKeyPasswod != nullptr)
SSL_CTX_set_default_passwd_cb_userdata(sslCtx, (void*)T2CA(lpszKeyPasswod));
if(!SSL_CTX_use_PrivateKey_file(sslCtx, T2CA(lpszPemKeyFile), SSL_FILETYPE_PEM))
{
::SetLastError(ERROR_INVALID_PASSWORD);
return FALSE;
}
if(!SSL_CTX_use_certificate_chain_file(sslCtx, T2CA(lpszPemCertFile)))
{
::SetLastError(ERROR_INVALID_DATA);
return FALSE;
}
if(!SSL_CTX_check_private_key(sslCtx))
{
::SetLastError(ERROR_INVALID_ACCESS);
return FALSE;
}
}
return TRUE;
}
至此完成了服务器SSL上下文初始化
然后是对ChckeParams、PrepareStart进行覆盖
判断ssl上下文是否初始化成功 以及sslContextpool初始化
BOOL CSSLServer::CheckParams()
{
if(!m_sslCtx.IsValid())
{
SetLastError(SE_SSL_ENV_NOT_READY, __FUNCTION__, ERROR_NOT_READY);
return FALSE;
}
return __super::CheckParams();
}
void CSSLServer::PrepareStart()
{
__super::PrepareStart();
m_sslPool.SetItemCapacity (GetSocketBufferSize());
m_sslPool.SetItemPoolSize (GetFreeBufferObjPool());
m_sslPool.SetItemPoolHold (GetFreeBufferObjHold());
m_sslPool.SetSessionLockTime(GetFreeSocketObjLockTime());
m_sslPool.SetSessionPoolSize(GetFreeSocketObjPool());
m_sslPool.SetSessionPoolHold(GetFreeSocketObjHold());
m_sslPool.Prepare();
}
重写了FireAccept 实现客户SSL上下文初始化
EnHandleResult CSSLServer::FireAccept(TSocketObj* pSocketObj)
{
//先执行Accept
EnHandleResult result = DoFireAccept(pSocketObj);
//Accept完成
if(result != HR_ERROR)
{
//客户ssl迟获取空余位置的指针
CSSLSession* pSession = m_sslPool.PickFreeSession();
//SocketObj 初始化称sslsession
VERIFY(SetConnectionReserved2(pSocketObj, pSession));
//进行挥手测试
VERIFY(::ProcessHandShake(this, pSocketObj, pSession) == HR_OK);
}
return result;
}
//我们可以看到PickFreeSession
CSSLSession* CSSLSessionPool::PickFreeSession(LPCSTR lpszHostName)
{
DWORD dwIndex;
CSSLSession* pSession = nullptr;
//下锁 然后获取一个session 和 id
if(m_lsFreeSession.TryLock(&pSession, dwIndex))
{
//如果大于释放时间 则释放id
if(::GetTimeGap32(pSession->GetFreeTime()) >= m_dwSessionLockTime)
VERIFY(m_lsFreeSession.ReleaseLock(nullptr, dwIndex));
else
{
//否则两个都释放
VERIFY(m_lsFreeSession.ReleaseLock(pSession, dwIndex));
pSession = nullptr;
}
}
//如果session获取失败则 新建一个session
if(!pSession) pSession = new CSSLSession(m_itPool);
ASSERT(pSession);
把session 跟 sslCtx HostName赋值
return pSession->Renew(m_sslCtx, lpszHostName);
}
CSSLSession* CSSLSession::Renew(const CSSLContext& sslCtx, LPCSTR lpszHostName)
{
ASSERT(!IsValid());
m_ssl = SSL_new(sslCtx.GetDefaultContext());
//获得客户sslContext
m_bioSend = BIO_new(BIO_s_mem());
m_bioRecv = BIO_new(BIO_s_mem());
//新建客户bio缓冲区
SSL_set_bio(m_ssl, m_bioRecv, m_bioSend);
//设置bio
if(sslCtx.GetSessionMode() == SSL_SM_SERVER)
//调用Accept
SSL_accept(m_ssl);
else
{
//如果是客户端 则设置主机名
USES_CONVERSION;
if(lpszHostName && lpszHostName[0] != 0 && !::IsIPAddress(A2CT(lpszHostName)))
SSL_set_tlsext_host_name(m_ssl, lpszHostName);
//连接
SSL_connect(m_ssl);
}
//Pool池 获取空余项 给Send Recv
m_pitSend = m_itPool.PickFreeItem();
m_pitRecv = m_itPool.PickFreeItem();
m_bufSend.buf = m_pitSend->Ptr();
m_bufRecv.buf = m_pitRecv->Ptr();
m_enStatus = SSL_HSS_PROC;
return this;
}
此时PickFreeSession已获取到空余Session 我们则把SocketObj 与 Session 连接起来
BOOL CTcpServer::SetConnectionReserved2(TSocketObj* pSocketObj, PVOID pReserved2)
{
if(TSocketObj::IsExist(pSocketObj))
{
pSocketObj->reserved2 = pReserved2;
return TRUE;
}
return FALSE;
}
//然后再执行握手 把SSL相关信息发给客户
ProcessHandShake
template<class T, class S> EnHandleResult ProcessHandShake(T* pThis, S* pSocketObj, CSSLSession* pSession)
{
EnHandleResult result = HR_OK;
//下锁
CCriSecLock locallock(pSession->GetSendLock());
while(TRUE)
{//通过ReadSendChannel里面的BIO_read把数据读到Session 里面的bufsend
VERIFY(pSession->ReadSendChannel());
const WSABUF& buffer = pSession->GetSendBuffer();
//然后通过GetSendBuffer 来获取到Buffer
if(buffer.len == 0)
break;
if(!pThis->DoSendPackets(pSocketObj, &buffer, 1))
{
result = HR_ERROR;
break;
}
}
return result;
}
//然后调用了TcpServer 的DoSendPackets来发送数据 直到BIO里面没有数据了则握手完成 至此SSL Accept完成
我们再来看看
EnHandleResult CSSLServer::FireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength)
{
CSSLSession* pSession = nullptr;
//通过SocketObj来获取Session
GetConnectionReserved2(pSocketObj, (PVOID*)&pSession);
ASSERT(pSession);
return ::ProcessReceive(this, pSocketObj, pSession, pData, iLength);
//然后调用SSLHelper类里面的 ProcessReceive
}
template<class T, class S> EnHandleResult ProcessReceive(T* pThis, S* pSocketObj, CSSLSession* pSession, const BYTE* pData, int iLength)
{
//收到的数据调用WriteRecvChannel 写入BIO RECV缓冲区
if(!pSession->WriteRecvChannel(pData, iLength))
return HR_ERROR;
EnHandleResult result = HR_OK;
EnSSLHandShakeStatus enStatus = pSession->GetStatus();
while(TRUE)
{
//通过SSL Read读取数据读到pSession 里的m_bufRecv
if(!pSession->ReadRecvChannel())
return HR_ERROR;
//如果还木有握手
if(enStatus == SSL_HSS_PROC && pSession->IsReady())
{
result = ProcessHandShake(pThis, pSocketObj, pSession);
if(result == HR_ERROR)
break;
enStatus = SSL_HSS_SUCC;
result = pThis->DoFireHandShake(pSocketObj);
if(result == HR_ERROR)
break;
}
const WSABUF& buffer = pSession->GetRecvBuffer();
if(buffer.len == 0)
break;
//调用Receive接口
result = pThis->DoFireReceive(pSocketObj, (const BYTE*)buffer.buf, buffer.len);
if(result == HR_ERROR)
break;
}
//还未握手成功 继续
if(result != HR_ERROR && pSession->IsHandShaking())
result = ::ProcessHandShake(pThis, pSocketObj, pSession);
return result;
}
BOOL CSSLSession::ReadRecvChannel()
{
BOOL isOK = TRUE;
int bytes = SSL_read(m_ssl, m_bufRecv.buf, m_pitRecv->Capacity());
if(bytes > 0)
m_bufRecv.len = bytes;
else if(!IsFatalError(bytes))
m_bufRecv.len = 0;
else
isOK = FALSE;
if(isOK && m_enStatus == SSL_HSS_PROC && SSL_is_init_finished(m_ssl))
m_enStatus = SSL_HSS_SUCC;
return isOK;
}
//此处因为SSL_set_bio 所以 通过BIO_write(m_bioRecv, pData, iLength); SSL_read会读m_bioRecv的内容
接收完毕我们来看发送 这里多处HandShake是因为 握手过来的包可能被Receive捕获所以要让他继续
BOOL CSSLServer::SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount)
{
ASSERT(pBuffers && iCount > 0);
TSocketObj* pSocketObj = FindSocketObj(dwConnID);
if(!TSocketObj::IsValid(pSocketObj))
{
::SetLastError(ERROR_OBJECT_NOT_FOUND);
return FALSE;
}
CSSLSession* pSession = nullptr;
//取得session
GetConnectionReserved2(pSocketObj, (PVOID*)&pSession);
return ::ProcessSend(this, pSocketObj, pSession, pBuffers, iCount);
}
template<class T, class S> BOOL ProcessSend(T* pThis, S* pSocketObj, CSSLSession* pSession, const WSABUF * pBuffers, int iCount)
{
if(pSession == nullptr || !pSession->IsReady())
{
::SetLastError(ERROR_INVALID_STATE);
return FALSE;
}
//下锁
CCriSecLock locallock(pSession->GetSendLock());
//是否可发 必须是握手完成
if(!pSession->IsReady())
{
::SetLastError(ERROR_INVALID_STATE);
return FALSE;
}
VERIFY(pSession->WriteSendChannel(pBuffers, iCount));
//写入
while(TRUE)
{
//读取发送内容
VERIFY(pSession->ReadSendChannel());
const WSABUF& buffer = pSession->GetSendBuffer();
if(buffer.len == 0)
break;
//调用发送接口
if(!pThis->DoSendPackets(pSocketObj, &buffer, 1))
return FALSE;
}
return TRUE;
}
BOOL CSSLSession::WriteSendChannel(const WSABUF pBuffers[], int iCount)
{
ASSERT(pBuffers && iCount > 0);
BOOL isOK = TRUE;
for(int i = 0; i < iCount; i++)
{
const WSABUF& buffer = pBuffers[i];
if(buffer.len > 0)
{
if(!WriteSendChannel((const BYTE*)buffer.buf, buffer.len))
{
isOK = FALSE;
break;
}
}
}
return isOK;
}
BOOL CSSLSession::WriteSendChannel(const BYTE* pData, int iLength)
{
ASSERT(IsReady());
ASSERT(pData && iLength > 0);
BOOL isOK = TRUE;
int bytes = SSL_write(m_ssl, pData, iLength);
//写入
if(bytes > 0)
ASSERT(bytes == iLength);
else if(IsFatalError(bytes))
isOK = FALSE;
return isOK;
}
至此SSLServer工作流程完毕
BIO工作流程:
socket->(加密数据)bio read -> ssl read -> 原文
SSL_write(原文) -> bio wrtie(加密数据)-> socket