Hi all,
I have a daemon where I am trying to retrieve the common name from the
certificate during an HTTPS connection. What i am observing is that ,
SSL_Connect() function is taking more time to connect on some of the
websites.
I am trying on Mac OS X 10.8.3. Below is the code I have been using and I
am using non-blocking sockets
#include "SSLUtil.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/crypto.h>
#include <errno.h>
#include <fcntl.h>
#include <string>
#include "Logging.h"
bool SSLUtil::GetSSLServerNameFromIP(std::string ipAddress, std::string
&serverName)
{
bool bRet = false;
int sock=-1;
MCLOG("SSLUtil::GetSSLServerNameFromIP Call for IP",ipAddress.c_str());
if(ConnectToServerAsync(ipAddress,443,sock))
{
if(RetrieveNameUsingSSL(sock,serverName))
{
bRet = true;
MCLOG("SSLUtil::GetSSLServerNameFromIP Call
return",ipAddress.c_str(),serverName.c_str());
}
else
{
MCLOG("SSLUtil::GetSSLServerNameFromIP RetrieveNameUsingSSL
call failed",ipAddress.c_str());
}
close(sock);
}else
{
MCLOG("SSLUtil::GetSSLServerNameFromIP connect asycn
failed",ipAddress.c_str());
}
return bRet;
}
#define TIMEOUT_SERVER 3 //seconds
bool SSLUtil::ConnectToServerAsync(std::string ipaddress, int port , int
&sock)
{
//create the socket
sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock<0)
{
//creating socket failed
MCLOG("SSLUtil::ConnectToServerAsync soceket creation failed ");
return false;
}
//change socket options
int opts = fcntl(sock,F_GETFL);
if (opts < 0) {
//pFailed F_GETFL
MCLOG("SSLUtil::ConnectToServerAsync F_GETFL failed ");
return false;
}
opts = (opts | O_NONBLOCK);
if (fcntl(sock,F_SETFL,opts) < 0) {
//Failed F_SETFL
MCLOG("SSLUtil::ConnectToServerAsync F_SETFL failed");
return false;
}
//make sure there is no error pending
int err = errno;
if (err < 0)
{
MCLOG("SSLUtil::ConnectToServerAsync can't create non blocking
socket ");
//printf("MakeConnectAttempt = can't set non-blocking socket");
if(sock)
{
close(sock);
}
return false;
}
struct sockaddr_in server_addr;
memset (&server_addr, '\0', sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port); /* Server Port number
*/
server_addr.sin_addr.s_addr = inet_addr(ipaddress.c_str()); /* Server
IP */
int status = connect(sock, (struct sockaddr*) &server_addr,
sizeof(server_addr));
//wait for connection to succeed
if(status<0)
{
status = errno;
if(status == EINPROGRESS)
{
MCLOG("SSLUtil::ConnectToServerAsync in progress ",
ipaddress.c_str());
if(!WaitOnSocket(sock,TIMEOUT_SERVER))
{
//printf("connect to %s failed\n",argv[1]);
MCLOG("SSLUtil::ConnectToServerAsync wait failed ",
ipaddress.c_str());
return false;
}
}else
{
//printf("connect to %s failed\n",argv[1]);
MCLOG("SSLUtil::ConnectToServerAsync connect failed ", status);
return false;
}
}
MCLOG("SSLUtil::ConnectToServerAsync connect success ", status);
return true;
}
bool SSLUtil::WaitOnSocket(int &sockfd,int timeOutInSeconds)
{
fd_set readFDs;
fd_set writeFDs;
int selectResult;
// bool timeOut = false;
FD_ZERO(&readFDs);
FD_ZERO(&writeFDs);
FD_SET(sockfd, &readFDs);
FD_SET(sockfd, &writeFDs);
bool continueSelect ;
struct timeval waitd;
time_t seconds;
time_t future;
time_t now;
seconds = time(NULL);
future = seconds + timeOutInSeconds;
// waitd.tv_sec = timeOutInSeconds; // Make select wait up to 10
seconds for data
// waitd.tv_usec = 0; // and 0 milliseconds.
int err = 0;
continueSelect = true;
do
{
waitd.tv_sec = timeOutInSeconds; // Make select wait up to 5
seconds for data
waitd.tv_usec = 0; // and 0 milliseconds.
selectResult = select(sockfd+1, &readFDs, &writeFDs, NULL, &waitd);
//printf("after select %d\n", selectResult);
err = errno;
if (selectResult > 0)
{
for(int i=0;i<sockfd+1;i++)
{
if (FD_ISSET(i,&readFDs))
{
continueSelect = false;
break;
}
if (FD_ISSET(i,&writeFDs))
{
continueSelect = false;
break;
}
}
}
else
{
now = time(NULL);
if (now > future )
{
continueSelect = false;
MCLOG("timed out");
break;
}
else
{
MCLOG("time left",(future - now));
continueSelect = false;
}
//err = errno;
if (err == EINTR)
continueSelect = true;
}
}
while (continueSelect);
FD_ZERO(&readFDs);
FD_ZERO(&writeFDs);
if (selectResult > 0 )
{
return true;
}
MCLOG("**** failed connection and got file descriptor %d\n", sockfd);
return false;
}
bool SSLUtil::RetrieveNameUsingSSL(int &sock , std::string &serverName)
{
serverName="";
SSL_library_init();
SSL_METHOD *meth=SSLv3_method();
SSL_CTX *sslctx=SSL_CTX_new(meth);
if(!sslctx)
{
//printf("SSL_CTX_new failed");
MCLOG("SSLUtil::RetrieveNameUsingSSL SSL_CTX_new failed ");
//close(sock);
return false;
}
SSL_CTX_set_verify(sslctx,SSL_VERIFY_NONE,NULL);
SSL *ssl =SSL_new(sslctx);
if(!ssl)
{
//printf("SSL_new failed\n"); close(sock);
MCLOG("SSLUtil::RetrieveNameUsingSSL SSL_new failed ");
//exit(4);
return false;
}
int status=SSL_set_fd(ssl,sock);
if(!status)
{
//printf("SSL_set_fd failed\n"); close(sock);
//exit(5);
MCLOG("SSLUtil::RetrieveNameUsingSSL SSL_set_fd failed ");
return false;
}
status = SSL_connect(ssl);
int error=SSL_get_error(ssl,status);
//printf("Error %d\n",error);
switch(error)
{
case SSL_ERROR_NONE:
//printf("connect successful\n");
break;
case SSL_ERROR_ZERO_RETURN:
//printf("peer close ssl connection \n");
break;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
{
time_t seconds;
time_t future;
time_t now;
seconds = time(NULL);
future = seconds + 2;
MCLOG("SSLUtil::RetrieveNameUsingSSL Before SSL_ERROR_WANT_READ
& SSL_ERROR_WANT_WRITE");
while(error == SSL_ERROR_WANT_READ || error ==
SSL_ERROR_WANT_WRITE)
{
status = SSL_connect(ssl);
if(!WaitOnSocket(sock,TIMEOUT_SERVER))
{
MCLOG("WaitOnSocket func failed");
break;
}
now = time(NULL);
if(now > future)
{
MCLOG("*** break");
break;
}
error=SSL_get_error(ssl,status);
if(error == SSL_ERROR_NONE)
{
MCLOG("SSL_ERROR_NONE");
break;
}
}
}
break;
default:
MCLOG("SSLUtil::RetrieveNameUsingSSL failed ",error);
//printf("connect error is %d\n",error);
break;
}
X509* server_cert = SSL_get_peer_certificate (ssl);
if (server_cert != NULL)
{
//MessageLog.Write("Server certificate");
//str = X509_NAME_oneline(X509_get_subject_name(server_cert),0,0);
X509_NAME * name = X509_get_subject_name(server_cert);
char str[512] = {} ;
X509_NAME_get_text_by_NID(name, NID_commonName, str, 512);
if(str != NULL)
{
serverName = str;
//MessageLog.Write("Domain name :", str);
//MessageLog.Write("Successfully fetched the certificate");
}
else
{
MCLOG("SSLUtil::RetrieveNameUsingSSL server name empty ");
}
X509_free (server_cert);
}
else
{
MCLOG("SSLUtil::RetrieveNameUsingSSL certificate empty ");
}
if(ssl)
{
error = SSL_shutdown(ssl);
if(error == -1)
{
//MessageLog.Write("Failed to do SSLShutdown");
MCLOG("SSLUtil::RetrieveNameUsingSSL SSLShutdown failed ");
}
// Free the SSL structure
//MessageLog.Write("free SSL structure");
SSL_free(ssl);
}
// Free the SSL_CTX structure
if(sslctx)
{
SSL_CTX_free(sslctx);
}
return (!serverName.empty());
}