在產品使用中,實施人員常常報告服務器與客戶端無法連接.究其原因是因為客戶端機器與服務端機器系統(tǒng)時間不一致.原因在于系統(tǒng)使用了OpenSSL,證書中有一個有效時間段,當客戶端或服務器的系統(tǒng)時間不在這個時間段內時SSL會因證書驗證失敗而無法連接.在實施中系統(tǒng)時間錯誤是很常見的,因不能上網(wǎng)而未開時間自動同步,bios沒電了,客戶疏忽等原因都會導致系統(tǒng)時間設置有誤.如果連接失敗后再查看系統(tǒng)時間設置總是一項麻煩的事情,那么有哪些辦法可以自動避免這個問題呢?
  一,將證書的有效期設得夠大:如:1970-2099
   這樣估計可以在一定程度上解決這個問題,不過這也是個餿主意.
  二,檢測及必要時自動同步客戶端與服務器的時間
  通過用wireshake抓包分析SSL建立連接的過程,發(fā)現(xiàn)在SSL握手過程中,會向對方傳送本機的系統(tǒng)時間.因此一個顯而易見的辦法就是獲取對方的時間,然后在必要時將本機的系統(tǒng)時間改為對方的系統(tǒng)時間,失敗后再連一次.下面是具體的示例代碼:
#include <openssl/ssl.h>
#include 
<openssl/bio.h>
#include 
<openssl/err.h>
#include 
<winsock2.h>
#include 
<stdio.h>
#include 
<string.h>
#include 
<time.h>

typedef 
struct _TimeInfo
{
    time_t client;  
/*客戶端的時間*/
    time_t server;  
/*服務器的時間*/
} TimeInfo;

/**
* 同步系統(tǒng)時間.
*/
BOOL syncSystemTime(time_t t)
{
    SYSTEMTIME st;
    FILETIME   ft;  
    LONGLONG   ll;  
    
    ll 
= Int32x32To64(t, 10000000+ 116444736000000000//1970.01.01  
    
    ft.dwLowDateTime  
= (DWORD)ll;  
    ft.dwHighDateTime 
= (DWORD)(ll >> 32);  
    
    
return FileTimeToSystemTime(&ft, &st) && SetSystemTime(&st);
}

/**
* 獲取SSL握手過程中服務器與客戶端雙方的系統(tǒng)時間.
*/
void getSSLHandleShakeTimeInfo(int write_p,
                               
int version,
                               
int content_type,
                               
const unsigned char* buf,
                               size_t len,
                               SSL 
*ssl,
                               TimeInfo 
*ti)
{
    
if(content_type != 22)   //require handshake message
        return;
    
if(len < 42)
        
return;
    
if(buf[0== 1)          //ClientHello Message send from client to server
        ti->client = htonl(*((u_long*)(buf + 6)));
    
else if(buf[0== 2)     //ServerHello Message send from server to client
        ti->server = htonl(*((u_long*)(buf + 6)));
    
else
        
return;
}

int main()
{
    BIO 
* bio;
    SSL 
* ssl;
    SSL_CTX 
* ctx;
    TimeInfo timeInfo 
= {-1-1};
    BOOL timeSynced 
= FALSE;
    
long result;

    
/* Set up the library */
    SSL_library_init();
    ERR_load_BIO_strings();
    SSL_load_error_strings();

    
/* Set up the SSL context */
    ctx 
= SSL_CTX_new(SSLv3_client_method());
    
if(ctx == NULL)
    {
        fprintf(stderr, 
"Error new SSL_CTX\n");
        ERR_print_errors_fp(stderr);
        SSL_CTX_free(ctx);
        
return 0;
    }

    
/* Get Server and Client system time via SSL Handshake */
    SSL_CTX_set_msg_callback(ctx, getSSLHandleShakeTimeInfo);
    SSL_CTX_set_msg_callback_arg(ctx, 
&timeInfo);
    
    
/* Load the trust store */
    
if(! SSL_CTX_load_verify_locations(ctx, ".\\certs\\cacert.pem", NULL))
    {
        fprintf(stderr, 
"Error loading trust store\n");
        ERR_print_errors_fp(stderr);
        SSL_CTX_free(ctx);
        
return 0;
    }

    
/* Setup the connection */
    bio 
= BIO_new_ssl_connect(ctx);

    
/* Set the SSL_MODE_AUTO_RETRY flag */
    BIO_get_ssl(bio, 
& ssl);
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

    
/* Create and setup the connection */
    BIO_set_conn_hostname(bio, 
"192.168.1.5:5555");
    
if(BIO_do_connect(bio) <= 0)
    {
        fprintf(stderr, 
"Error attempting to connect\n");
        ERR_print_errors_fp(stderr);
        BIO_free_all(bio);
        SSL_CTX_free(ctx);
        
return 0;
    }
    
    
/* Check the certificate */
    
switch(SSL_get_verify_result(ssl))
    {
    
case X509_V_OK:
        
break;
    
case X509_V_ERR_CERT_NOT_YET_VALID:
    
case X509_V_ERR_CERT_HAS_EXPIRED:
        
if(timeInfo.server != -1 && timeInfo.client != -1)
        {
            printf(
"當前客戶端時間: %s", ctime(&timeInfo.client));
            printf(
"當前服務器時間: %s", ctime(&timeInfo.server));
            printf(
"嘗試與服務器時間同步");
            
            
if(syncSystemTime(timeInfo.server))
                printf(
"成功\n");
            
else
                printf(
"失敗\n");
            printf(
"請重試連接服務器!\n");
        }
    
default:
        fprintf(stderr, 
"Certificate verification error: %i\n", SSL_get_verify_result(ssl));
        BIO_free_all(bio);
        SSL_CTX_free(ctx);
        
return 0;
    }

    
/* Close the connection and free the context */
    BIO_free_all(bio);
    SSL_CTX_free(ctx);
    
return 0;
}