我有一个用 C++ 编写的 Windows 客户端/服务器程序,我几乎将 OpenSSL 集成到其中。
我确实想声明,我已经查看了与我自己类似的所有其他帖子,但我还没有找到我的问题的正确答案。大多数其他示例都非常接近答案,但我似乎无法产生实际结果。
对于我的测试用例,我只是每 5 秒使用 OpenSSL 安全套接字将 7 个字符的字符串从客户端传递到服务器。我没有发送任何其他信息。最初的问题是我没有正确使用 SSL_read 并且我没有读取所需的明显额外数据,并且 SSL_read 将停止响应并且客户端将失去连接。
在阅读了所有其他示例之后,我进一步完成了这一步,但我永远无法得到想要的结果。我尝试了超过 10 种类型的 SSL_read 实现,但在读取数据时我无法让代码成为非阻塞的。我将发布我最新的实现(不是难以置信的优化):
int received = -1;
int tE = 0;
if( tE != SSL_ERROR_WANT_READ && tE != SSL_ERROR_WANT_WRITE )
{
received = SSL_read(sSecureSocket.ssl, buffer, sizeof(buffer));
if( received == -1 )
{
tE = SSL_get_error( sSecureSocket.ssl, received );
if( tE == SSL_ERROR_WANT_READ )
{
int tReadWaiting = 1;
while( tReadWaiting )
{
tReadWaiting = 0;
fd_set rfd;
FD_ZERO(&rfd);
FD_SET(receiveSocket, &rfd);
timeval tv = { 0 };
select(receiveSocket+1, &rfd, 0, 0, &tv);
if (!FD_ISSET(receiveSocket, &rfd))
{
tReadWaiting = 1;
}
}
received = SSL_read(sSecureSocket.ssl, buffer, sizeof(buffer));
}
}
}
这段代码在每次发送数据包时都会成功提取数据包,但是如您所见,一旦触发 SSL_ERROR_WANT_READ/WRITE,我必须使用 select() 语句。这种方法取自本论坛的另一个主题,假设您应该阅读直到调用 SSL_ERROR_WANT_READ/WRITER。另外,我确实使用了 SSL_pending() 但在我拥有的每种情况下它总是返回 0,它绝不是非零数。
该程序是用非阻塞套接字编写的,带有对 Windows 消息的 WSAASyncSelect 调用。所有这些工作正常。
但发生的事情与我的预期相反。代码似乎总是等待不存在的数据。我意识到 SSL 套接字也可以在随机时间进行握手。我只是想弄清楚为什么一旦我收到来自服务器的数据,为什么 SSL_read 似乎有数据,或者 select() 检测到需要在我发送的 5 秒间隔之间读取数据我的数据包,以便 select() 循环一直阻塞。即使数据包被完全读取,下一条消息也会被回调到 read 函数中,并且程序本身永远不能离开这个函数来执行任何其他过程。
非常感谢任何帮助,我以前从来没有在我的智慧结束编程,这让我非常接近它。
更新 1:
这是套接字监听代码:
ctx = sSecureSocket.InitServerCTX(); // initialize SSL
sSecureSocket.LoadCertificates(ctx, VGlobal::CURRWORKDIR + "\\openssl2\\" + "mycert.pem", VGlobal::CURRWORKDIR + "\\openssl2\\" + "mycert.pem"); // load certs
servSecureSocket = sSecureSocket.OpenListener(39245); // create server socket
当服务器上接受套接字时调用它:
//Always connect as a secure socket first
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
SSL *ssl;
receiveSocket = accept(servSecureSocket, (struct sockaddr*)&addr, &len); // accept connection as usual
printf("Connection: %s:%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
sSecureSocket.ssl = SSL_new(ctx); // get new SSL state with context
SSL_set_fd(sSecureSocket.ssl, receiveSocket); // set connection socket to SSL state
sSecureSocket.Servlet(sSecureSocket.ssl); // service connection
这些是功能:
//-----------------------------------------------------
int SecureSocket::OpenListener(int port)
{ int sd;
struct sockaddr_in addr;
WSADATA wsadata;
int error = WSAStartup( 0x0202, &wsadata );
if( error )
return false;
sd = socket(PF_INET, SOCK_STREAM, 0);
//bzero(&addr, sizeof(addr));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
{
perror("can't bind port");
// abort();
}
if ( listen(sd, 10) != 0 )
{
perror("Can't configure listening port");
// abort();
}
return sd;
}
//-----------------------------------------------------
SSL_CTX* SecureSocket::InitServerCTX(void)
{
SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
ctx = SSL_CTX_new(SSLv23_server_method());
if ( ctx == NULL )
{
ERR_print_errors_fp(stderr);
}
return ctx;
}
//-----------------------------------------------------
void SecureSocket::LoadCertificates(SSL_CTX* ctx, CString CertFile, CString KeyFile)
{
/* set the local certificate from CertFile */
if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
}
/* set the private key from KeyFile (may be the same as CertFile) */
if ( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
}
/* verify private key */
if ( !SSL_CTX_check_private_key(ctx) )
{
fprintf(stderr, "Private key does not match the public certificate\n");
}
}
//-----------------------------------------------------
void SecureSocket::ShowCerts(SSL* ssl)
{ X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */
if ( cert != NULL )
{
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line);
X509_free(cert);
}
else
printf("No certificates.\n");
}
//-----------------------------------------------------
void SecureSocket::Servlet(SSL* ssl) /* Serve the connection -- threadable */
{
int acceptDone = SSL_accept(ssl);
//If this blocking this will be done immediately, if it is not we must loop until it is complete
while( acceptDone < 1 )
{
acceptDone = SSL_accept(ssl);
}
ShowCerts(ssl); /* get any certificates */
}