Hi, I have 2 clients applications to send https request to 2 different web server. My first client run on Linux Red Hat 7 and send https request to IIS 4.0 web server on an NT 4.0 Server and my second client run on NT 4.0 Server and send https request to Apache web server on a Linux Red Hat 7 machine. These 2 clients use the same C++ class using OpenSSL to send http request on an SSL connection. My first client application (client on Linux Red Hat 7 connecting to IIS web server) work perfectly. My second client application (client on NT 4 connection to Apache) have some problem with the handshaking. First, my client establish the connection with Apache when I call BIO_write to send my https request. At this point, the handshaking seems to work correctly (client and server certificates are exenged wihtout any error). But when I call BIO_read to read the response from the server, a re-negotiation is performed and the BIO_read function fail and BIO_should_retry return 0 after 1 or 2 retry (this appen only if Apache need a client certificate). My Apache setting: Alias /php/ "/var/www/php/" <Directory "/var/www/php/"> AllowOverride None Order allow,deny Allow from All SSLVerifyClient require SSLVerifyDepth 1 SSLRequireSSL SSLRequire ( %{SSL_CLIENT_S_DN_O} eq "TravelNet Technologies Inc." \ and %{SSL_CLIENT_S_DN_OU} eq "Data Valet" \ and %{SSL_CLIENT_S_DN_CN} eq "10.255.255.253") SSLOptions +StrictRequire +OptRenegotiate </Directory> My code: #ifdef _WINDEF_ #include <winsock2.h> #else #include <string.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #endif #include "DVMacro.h" #include "DVCertPasswd.h" #include "SSLSupport.h" #define CERT_ORGANISATION "TravelNet Technologies Inc." #define CERT_ORGANISATION_UNIT "Data Valet" #define CERT_CITY "Montreal" #define CERT_STATE "Quebec" #define CERT_COUNTRY "CA" #define CA_CERT_ORGANISATION "TravelNet Technologies Inc." #define CA_CERT_ORGANISATION_UNIT "Data Valet" #define CA_CERT_COMMON_NAME "TravelNet" #define CA_CERT_CITY "Montreal" #define CA_CERT_STATE "Quebec" #define CA_CERT_COUNTRY "CA" static char * g_sCertCommonName = NULL; static int VerifyCallback(int preverify_ok, X509_STORE_CTX * ctx); static int VerifyCertData( char * buf, bool bCA); static int PasswordCallback(char *buf, int size, int rwflag, void *password); ////////////////////////////////////////////////////////////////////////////////////////////////// // This function is used to verify the content of the server certificate or the CA certificate. // This function is called by VerifyCallback(). // Return value: 0 if the data is not valide. // 1 if the data is valide. // // Note: buf supposed to have this format: // for CA certificate: /Email is not verify. // /C=CA/ST=Quebec/O=TravelNet Technologies Inc./OU=Data [EMAIL PROTECTED] // for server certificate: /CN must be Data Valet server IP address. // /C=CA/ST=Quebec/L=Montreal/O=TravelNet Technologies Inc./OU=Data // [EMAIL PROTECTED] ////////////////////////////////////////////////////////////////////////////////////////////////// static int VerifyCertData( char * buf, bool bCA) { int iResult = 1; char * sField = NULL; char * sTemp = NULL; #ifdef _WINDEF_ if (buf) sTemp = strtok(buf, "/="); while (sTemp && iResult) { sField = strtok(NULL, "/="); if (sField) #else if (buf) { if (buf[0] == '/') buf++; else iResult = 0; } while (buf && iResult) { sField = strsep(&buf, "/"); if (sField) { sTemp = strsep(&sField, "="); if (sTemp) #endif { if (strcmp(sTemp, "C") == 0) { if (bCA) { if (strcmp(sField, CA_CERT_COUNTRY) != 0) iResult = 0; } else { if (strcmp(sField, CERT_COUNTRY) != 0) iResult = 0; } } else if (strcmp(sTemp, "ST") == 0) { if (bCA) { if (strcmp(sField, CA_CERT_STATE) != 0) iResult = 0; } else { if (strcmp(sField, CERT_STATE) != 0) iResult = 0; } } else if (strcmp(sTemp, "L") == 0) { if (bCA) { if (strcmp(sField, CA_CERT_CITY) != 0) iResult = 0; } else { if (strcmp(sField, CERT_CITY) != 0) iResult = 0; } } else if (strcmp(sTemp, "O") == 0) { if (bCA) { if (strcmp(sField, CA_CERT_ORGANISATION) != 0) iResult = 0; } else { if (strcmp(sField, CERT_ORGANISATION) != 0) iResult = 0; } } else if (strcmp(sTemp, "OU") == 0) { if (bCA) { if (strcmp(sField, CA_CERT_ORGANISATION_UNIT) != 0) iResult = 0; } else { if (strcmp(sField, CERT_ORGANISATION_UNIT) != 0) iResult = 0; } } else if (strcmp(sTemp, "CN") == 0) { if (bCA) { if (strcmp(sField, CA_CERT_COMMON_NAME) != 0) iResult = 0; } else { if (strcmp(sField, g_sCertCommonName) != 0) iResult = 0; } } else if (strcmp(sTemp, "Email") == 0) iResult = 1; // Ignore this field. else iResult = 0; // invalid field. } else iResult = 0; // invalid field. #ifdef _WINDEF_ sTemp = strtok(NULL, "/="); #else } #endif } return iResult; } ////////////////////////////////////////////////////////////////////////////////////////////////// // VerifyCallback is called by OpenSSL when SSL_connect() is called in OpenSSLConnection(). // This function is used to verify the content of the server certificate and their CA certificate. // Return value: 0 if the certificate is not valide. // 1 if the certificate is valide. ////////////////////////////////////////////////////////////////////////////////////////////////// static int VerifyCallback(int preverify_ok, X509_STORE_CTX * ctx) { int iResult = 0; char buf[256]; X509 *err_cert; int err, depth; SSL *ssl; if (preverify_ok == 1) { err_cert = X509_STORE_CTX_get_current_cert(ctx); err = X509_STORE_CTX_get_error(ctx); depth = X509_STORE_CTX_get_error_depth(ctx); if (depth > 2) { preverify_ok = 0; err = X509_V_ERR_CERT_CHAIN_TOO_LONG; X509_STORE_CTX_set_error(ctx, err); } else { ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); memset(buf, 0, 256); X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); if (depth == 0) { // Verify server certificate. if (VerifyCertData(buf, false)) { memset(buf, 0, 256); X509_NAME_oneline(X509_get_issuer_name(err_cert), buf, 256); // Verify issuer => supposed to be the CA. iResult = VerifyCertData(buf, true); } if (iResult == 0) { err = X509_V_ERR_CERT_REJECTED; X509_STORE_CTX_set_error(ctx, err); } } else { // Verify CA certificate. if (VerifyCertData(buf, true)) { memset(buf, 0, 256); X509_NAME_oneline(X509_get_issuer_name(err_cert), buf, 256); // Verify issuer => supposed to be the CA. iResult = VerifyCertData(buf, true); } if (iResult == 0) { err = X509_V_ERR_INVALID_CA; X509_STORE_CTX_set_error(ctx, err); } } } } return iResult; } static int PasswordCallback(char *buf, int size, int rwflag, void *password) { strncpy(buf, (char *)(password), size); buf[size - 1] = '\0'; return(strlen(buf)); } ///////////////////////////////////////////////////////////////////////////////////////////////////////// // CSSLSupport class ///////////////////////////////////////////////////////////////////////////////////////////////////////// CSSLSupport::CSSLSupport() { m_pBIO = NULL; m_pSSLContext = NULL; m_pSSLConnection = NULL; m_bInitialized = false; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// CSSLSupport::~CSSLSupport() { if (m_pBIO) Disconnect(); if (m_pSSLContext) { SSL_CTX_free(m_pSSLContext); m_pSSLContext = NULL; } if (g_sCertCommonName) DELETE_NULL_TAB(g_sCertCommonName); } ///////////////////////////////////////////////////////////////////////////////////////////////////////// int CSSLSupport::Init(const char *sCACertsPath, const char *sCACertificate, const char *sClientCertificate, const char *sClientPrivateKey, const char *sValidCN) { int iResult = DV_ERROR; if (sCACertsPath && sCACertificate && sValidCN) { SSL_library_init(); CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); SSLeay_add_ssl_algorithms(); m_pSSLContext = SSL_CTX_new( SSLv23_client_method() ); if (m_pSSLContext) { char sCACert[512]; strcpy(sCACert, sCACertsPath); strcat(sCACert, sCACertificate); if(SSL_CTX_load_verify_locations(m_pSSLContext, sCACert, sCACertsPath)) { SSL_CTX_set_verify(m_pSSLContext, SSL_VERIFY_PEER, VerifyCallback); SSL_CTX_set_default_passwd_cb(m_pSSLContext, PasswordCallback); SSL_CTX_set_default_passwd_cb_userdata(m_pSSLContext, (void *)CLIENT_CERTIFICATE_PASSWORD); if(SSL_CTX_use_certificate_file(m_pSSLContext, sClientCertificate, SSL_FILETYPE_PEM)==1) { if(SSL_CTX_use_PrivateKey_file(m_pSSLContext, sClientPrivateKey, SSL_FILETYPE_PEM)==1) { if (g_sCertCommonName) DELETE_NULL_TAB(g_sCertCommonName); g_sCertCommonName = new char[strlen(sValidCN)+1]; if (g_sCertCommonName) { strcpy(g_sCertCommonName, sValidCN); iResult = DV_SUCCESS; m_bInitialized = true; } } } } } } return iResult; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// // I'm not sure how to use BIO in this function. //////////////////////////////// int CSSLSupport::Connect(char *sHostName, int iPort) { int iResult = DV_ERROR; if (sHostName && m_bInitialized) { m_pSSLConnection = SSL_new( m_pSSLContext ); if (m_pSSLConnection) { BIO * pSSL_BIO = BIO_new(BIO_f_ssl()); if (pSSL_BIO) { char *sConnectStr = new char[strlen(sHostName) + 7]; if (sConnectStr) { sprintf(sConnectStr, "%s:%d", sHostName, iPort); SSL_set_connect_state(m_pSSLConnection); SSL_set_cipher_list(m_pSSLConnection, SSL_TXT_ALL); BIO_set_ssl(pSSL_BIO, m_pSSLConnection, BIO_NOCLOSE); BIO_set_ssl_mode(pSSL_BIO, 1); // Set to client mode. m_pBIO = BIO_new_connect(sConnectStr); if (m_pBIO) { BIO_set_nbio(m_pBIO, 1); m_pBIO = BIO_push(pSSL_BIO, m_pBIO); // if (BIO_do_connect(m_pBIO) == 1) // Alawys fail and I don't know why ???? => remove it. // { // The connection is perfomed automatically on the first call of BIO_write. // if(SSL_get_verify_result(m_pSSLConnection) == X509_V_OK) // { if (m_pBIO) iResult = DV_SUCCESS; // } // } } DELETE_NULL_TAB(sConnectStr); } } } if (DV_FAILED(iResult)) Disconnect(); } return iResult; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// void CSSLSupport::Disconnect() { if (m_pBIO) { BIO_free_all(m_pBIO); m_pBIO = NULL; } } ///////////////////////////////////////////////////////////////////////////////////////////////////////// int CSSLSupport::Send(const char *sHTTPRequest) { int iResult = DV_SUCCESS; int iResLen = strlen(sHTTPRequest); int iLen; int iOffset = 0; if (m_bInitialized) { while (DV_SUCCEEDED(iResult) && iResLen > 0) { iLen = BIO_write(m_pBIO, &sHTTPRequest[iOffset], iResLen); if (iLen <= 0) { if (BIO_should_retry(m_pBIO)) { struct timeval tv; fd_set wfds; int iSockId = SSL_get_fd(m_pSSLConnection); FD_ZERO(&wfds); FD_SET(iSockId, &wfds); tv.tv_sec = 5; tv.tv_usec = 0; select(iSockId + 1, NULL, &wfds, NULL, &tv); } else if (iLen < 0) iResult = DV_ERROR; } else { iOffset += iLen; iResLen -= iLen; } } } else iResult = DV_ERROR; return iResult; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// int CSSLSupport::Receive(char *&sHTTPResponse) { int iResult = DV_SUCCESS; int iBufSize = 1024; int iResLen = iBufSize; if (m_bInitialized) { if (sHTTPResponse) DELETE_NULL_TAB(sHTTPResponse); sHTTPResponse = new char[iResLen+1]; if (sHTTPResponse) { char *sBuffer = sHTTPResponse; char *sNewHTTPResponse; int iReadResult = -1; memset(sHTTPResponse, 0, iResLen+1); while (DV_SUCCEEDED(iResult) && iReadResult != 0) { iReadResult = BIO_read(m_pBIO, sBuffer, iResLen); if (iReadResult <= 0) { if (BIO_should_retry(m_pBIO)) { struct timeval tv; fd_set rfds; int iSockId = SSL_get_fd(m_pSSLConnection); FD_ZERO(&rfds); FD_SET(iSockId, &rfds); tv.tv_sec = 5; tv.tv_usec = 0; select(iSockId + 1, &rfds, NULL, NULL, &tv); } else if (iReadResult < 0) iResult = DV_ERROR; } else if (iReadResult > 0) { if (iReadResult < iResLen) { iResLen -= iReadResult; sBuffer += iReadResult; } else { // The buffer sHTTPResponse is full and maybe more data must be read // => save the data and try to read more data. sNewHTTPResponse = (char *)realloc(sHTTPResponse, sizeof(sHTTPResponse) + iBufSize); if (sNewHTTPResponse) { iResLen = iBufSize; sHTTPResponse = sNewHTTPResponse; sBuffer = sHTTPResponse + strlen(sHTTPResponse); memset(sBuffer, 0, iBufSize); } else iResult = DV_ERROR; } } } } else iResult = DV_ERROR; } else iResult = DV_ERROR; return iResult; } My main program: ... char * buffer = NULL; SSLSupport cSSL; cSSL.Init("c:\\winnt\\cert\\", "MyCA.cer", "c:\\winnt\\cert\\clnt.cer", "c:\\winnt\\cert\\clnt.key", "10.255.255.253"); cSSL.Connect("10.255.255.253", 443); cSSL.Send("GET /php/test.html HTTP/1.0\r\nHost: 10.255.255.253\r\n\r\n"); cSSL.Receive(buffer); ... NOTE: 1) When I use IE with the same client certificate, I can access to my page on my Apache web server. => My Apache configuration and my certificates (clnt.cer, MyCA.cer and my Apache server certificate) should be correct. 2) Also, if I configure my Apache like this: <Directory "/var/www/php/"> AllowOverride None Order allow,deny Allow from All SSLRequireSSL </Directory> my client program work but my client certificate is not used. 3) I have tried to run my client program with the same client and CA certificate on some others NT Server and it work on only one PC! What is wrong???? I think it's maybe my Connect() function because I'm not sure how to use BIO. ______________________________________________________________________ OpenSSL Project http://www.openssl.org User Support Mailing List [EMAIL PROTECTED] Automated List Manager [EMAIL PROTECTED]