最近一直在学习SSL相关知识,也明白了理论相关知识,主要SSL基本概念和连接建立。主要依据PolarSSL开源库学习。学习完了之后就希望能给有所运用,就想用Qt写一个简单的程序,添加对SSL相关概念的把握和对PolarSSL库的运用。当然,终于希望是能够使用Qt做一个比較完好的工具,帮助大家更好的理解和学习SSL相关知识。这都是后话,在第一篇里面,我们就简单用样例展示怎样在Qt里面调用PolarSSL库。
这篇博客主要是解说Qt里面调用PolarSSL库,至于SSL相关概念在后面的博客再具体介绍。
SSL握手须要客户端和服务器端交互。这里我们分别介绍。
1、编译PolarSSL库
我们准备使用的方式就是编译PolarSSL为.a静态库,然后在Qt中连接,使用的PolarSSL的版本号是0.10.1。
下载相应的软件版本号。解压缩后在library文件夹下执行make就可以生成libpolarssl.a库文件,例如以下图:
2、服务器端
使用Qt设计一个简单的界面。在button的槽函数中进行相关的操作。也就是调用PolarSSL库函数进行编程。初始化ssl相关结构体,监听port。等等。
SSL中最重要的就是执行握手操作。这里须要注意一点,因为涉及到socket编程。像accept函数都是堵塞的。假设在gui主线程中调用会造成界面冻结,也就是我们常说的ANR。解决方法就是将这些操作放在一个线程中,Qt中创建一个线程比較easy。创建一个类,继承自QThread,实现run函数。就可以。最后启动线程也比較简单,调用该类的start()
函数就可以。
好了,不多说了。上代码。首先看看服务器端的代码结构:workThread即是线程。实现SSL相关的功能,监听套接字,实现SSL握手,读取客户端发来的消息,向客户端发送消息。
mianwindow即是主窗体界面,有个button,在button的槽函数中启动线程
代码:
project文件:
#------------------------------------------------- # # Project created by QtCreator 2014-05-11T22:28:07 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = MyPolarSSLToolSrv TEMPLATE = app INCLUDEPATH += /home/chenlong12580/develop/polarTool/polarssl/include LIBS += -L "/home/chenlong12580/develop/polarTool/polarssl/lib/" -lpolarssl SOURCES += main.cpp widget.cpp workthread.cpp HEADERS += widget.h workthread.h FORMS += widget.ui
线程类:
void WorkThread::run() { qDebug() << "I am a thread!"; int listen_fd = 0; int client_fd =0; int ret= 0; havege_state hs; ssl_context ssl; ssl_session ssn; x509_cert srvcert; rsa_context rsa; unsigned char buf[1024]; int len = 0; memset( &srvcert, 0, sizeof( x509_cert ) ); ret = x509parse_crt( &srvcert, (unsigned char *) test_srv_crt, strlen( test_srv_crt ) ); if( ret != 0 ) { printf( " failed ! x509parse_crt returned %d ", ret ); return; } ret = x509parse_crt( &srvcert, (unsigned char *) test_ca_crt, strlen( test_ca_crt ) ); if( ret != 0 ) { printf( " failed ! x509parse_crt returned %d ", ret ); return; } ret = x509parse_key( &rsa, (unsigned char *) test_srv_key, strlen( test_srv_key ), NULL, 0 ); if( ret != 0 ) { printf( " failed ! x509parse_key returned %d ", ret ); return; } ret = net_bind( &listen_fd, NULL, 8443 ); if (0 != ret) { qDebug() << ret; return; } qDebug() << "bind ok"; /* socket is block */ ret = net_accept( listen_fd, &client_fd, NULL ); if (0 != ret) { return; } qDebug() << "accept ok"; havege_init( &hs ); ret = ssl_init( &ssl ); if (0 != ret) { return; } ssl_set_endpoint( &ssl, SSL_IS_SERVER ); ssl_set_authmode( &ssl, SSL_VERIFY_NONE ); ssl_set_rng( &ssl, havege_rand, &hs ); ssl_set_dbg( &ssl, my_debug, stdout ); ssl_set_bio( &ssl, net_recv, &client_fd, net_send, &client_fd ); ssl_set_scb( &ssl, my_get_session, my_set_session ); ssl_set_ciphers( &ssl, my_ciphers ); ssl_set_session( &ssl, 1, 0, &ssn ); memset( &ssn, 0, sizeof( ssl_session ) ); ssl_set_ca_chain( &ssl, srvcert.next, NULL ); ssl_set_own_cert( &ssl, &srvcert, &rsa ); ssl_set_dh_param( &ssl, my_dhm_P, my_dhm_G ); qDebug() << "before ssl_handshake ok"; while( ( ret = ssl_handshake( &ssl ) ) != 0 ) { ; } qDebug() << "ssl_handshake ok"; do { len = sizeof( buf ) - 1; memset( buf, 0, sizeof( buf ) ); ret = ssl_read( &ssl, buf, len ); if( ret == POLARSSL_ERR_NET_TRY_AGAIN ) continue; if( ret <= 0 ) { switch( ret ) { case POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY: printf( " connection was closed gracefully " ); break; case POLARSSL_ERR_NET_CONN_RESET: printf( " connection was reset by peer " ); break; default: printf( " ssl_read returned %d ", ret ); break; } break; } len = ret; printf( " %d bytes read %s", len, (char *) buf ); }while( 0 ); char *cc = (char *)buf; QString ss(cc); qDebug() << ss; (void)sprintf( (char *) buf, HTTP_RESPONSE, ssl_get_cipher( &ssl ) ); while( ( ret = ssl_write( &ssl, buf, len ) ) <= 0 ) { if( ret == POLARSSL_ERR_NET_CONN_RESET ) { printf( " failed ! peer closed the connection " ); return; } if( ret != POLARSSL_ERR_NET_TRY_AGAIN ) { printf( " failed ! ssl_write returned %d ", ret ); return; } } ssl_close_notify( &ssl ); net_close( client_fd ); x509_free( &srvcert ); rsa_free( &rsa ); ssl_free( &ssl ); cur = s_list_1st; while( cur != NULL ) { prv = cur; cur = cur->next; memset( prv, 0, sizeof( ssl_session ) ); free( prv ); } memset( &ssl, 0, sizeof( ssl_context ) ); }
主窗体类:
#include "widget.h" #include "ui_widget.h" #include <QFileDialog> #include <QDebug> #include <QMessageBox> #include "workthread.h" #include "polarssl/havege.h" #include "polarssl/certs.h" #include "polarssl/x509.h" #include "polarssl/ssl.h" #include "polarssl/net.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); qDebug() << "server"; connect(this, SIGNAL(emit_parse_cer()), this, SLOT(slot_parse_cer())); } Widget::~Widget() { delete ui; } void Widget::on_BrowseBtn_clicked() { QString pathStr = QFileDialog::getOpenFileName(this, QString("选择证书文件"), QString("C:\Users\Administrator\Desktop"), QString("*.*")); if (pathStr.length() == 0) { qDebug() << "please select a cer file!"; return; } ui->PathEdit->setText(pathStr); emit emit_parse_cer(); } void Widget::slot_parse_cer() { x509_cert crt; memset(&crt, 0, sizeof(crt)); int res = x509parse_crtfile( &crt, ui->PathEdit->text().toLatin1().data()); if (0 != res) { QMessageBox::warning(this, "警告", "解析证书失败,请选择正确的证书文件", QMessageBox::Ok); return; } ui->CrtInfo->setText(QString("是否为根证书:") + QString::number(crt.ca_istrue)); ui->CrtInfo->append(QString("证书版本号号:") + QString::number(crt.version)); ui->CrtInfo->append(QString("有效期:") + QString::number(crt.valid_from.year) + "-" + QString::number(crt.valid_from.mon) + QString(" 到:") + QString::number(crt.valid_to.year) + "-" + QString::number(crt.valid_to.mon)); qDebug() << crt.ca_istrue; qDebug() << crt.valid_from.year; } void Widget::on_pushButton_clicked() { WorkThread *workThread = new WorkThread; workThread->start(); }
3、客户端
客户端比較简单,直接在界面类进行的SSL功能相关的实现,就是创建套接字,链接服务器,进行SSL握手,向服务器发消息,读取服务器发来的消息。
project文件:
#------------------------------------------------- # # Project created by QtCreator 2014-05-11T22:28:07 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = MyPolarSSLToolCli TEMPLATE = app INCLUDEPATH += /home/chenlong12580/develop/polarTool/polarssl/include LIBS += -L "/home/chenlong12580/develop/polarTool/polarssl/lib/" -lpolarssl SOURCES += main.cpp widget.cpp HEADERS += widget.h FORMS += widget.ui
主窗体类:
#include "widget.h" #include "ui_widget.h" #include <QFileDialog> #include <QDebug> #include <QMessageBox> #include "polarssl/havege.h" #include "polarssl/certs.h" #include "polarssl/x509.h" #include "polarssl/ssl.h" #include "polarssl/net.h" #define SERVER_PORT 8443 /* #define SERVER_NAME "localhost" #define GET_REQUEST "GET / HTTP/1.0 " */ #define SERVER_NAME "polarssl.org" #define GET_REQUEST "GET /hello/ HTTP/1.1 " "Host: polarssl.org " #define DEBUG_LEVEL 0 void my_debug( void *ctx, int level, char *str ) { if( level < DEBUG_LEVEL ) { fprintf( (FILE *) ctx, "%s", str ); fflush( (FILE *) ctx ); } } Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); qDebug() << "client"; connect(this, SIGNAL(emit_parse_cer()), this, SLOT(slot_parse_cer())); } Widget::~Widget() { delete ui; } void Widget::on_BrowseBtn_clicked() { QString pathStr = QFileDialog::getOpenFileName(this, QString("选择证书文件"), QString("C:\Users\Administrator\Desktop"), QString("*.*")); if (pathStr.length() == 0) { qDebug() << "please select a cer file!"; return; } ui->PathEdit->setText(pathStr); emit emit_parse_cer(); } void Widget::slot_parse_cer() { x509_cert crt; memset(&crt, 0, sizeof(crt)); int res = x509parse_crtfile( &crt, ui->PathEdit->text().toLatin1().data()); if (0 != res) { QMessageBox::warning(this, "警告", "解析证书失败,请选择正确的证书文件", QMessageBox::Ok); return; } ui->CrtInfo->setText(QString("是否为根证书:") + QString::number(crt.ca_istrue)); ui->CrtInfo->append(QString("证书版本号号:") + QString::number(crt.version)); ui->CrtInfo->append(QString("有效期:") + QString::number(crt.valid_from.year) + "-" + QString::number(crt.valid_from.mon) + QString(" 到:") + QString::number(crt.valid_to.year) + "-" + QString::number(crt.valid_to.mon)); qDebug() << crt.ca_istrue; qDebug() << crt.valid_from.year; } void Widget::on_pushButton_clicked() { int ret, len, server_fd; unsigned char buf[1024]; havege_state hs; ssl_context ssl; ssl_session ssn; /* * 0. Initialize the RNG and the session data */ havege_init( &hs ); memset( &ssn, 0, sizeof( ssl_session ) ); /* * 1. Start the connection */ printf( " . Connecting to tcp/%s/%4d...", SERVER_NAME, SERVER_PORT ); fflush( stdout ); if( ( ret = net_connect( &server_fd, "127.0.0.1", SERVER_PORT ) ) != 0 ) { printf( " failed ! net_connect returned %d ", ret ); return; } printf( " ok " ); /* * 2. Setup stuff */ printf( " . Setting up the SSL/TLS structure..." ); fflush( stdout ); if( ( ret = ssl_init( &ssl ) ) != 0 ) { printf( " failed ! ssl_init returned %d ", ret ); return; } printf( " ok " ); ssl_set_endpoint( &ssl, SSL_IS_CLIENT ); ssl_set_authmode( &ssl, SSL_VERIFY_NONE ); ssl_set_rng( &ssl, havege_rand, &hs ); ssl_set_dbg( &ssl, my_debug, stdout ); ssl_set_bio( &ssl, net_recv, &server_fd, net_send, &server_fd ); ssl_set_ciphers( &ssl, ssl_default_ciphers ); ssl_set_session( &ssl, 1, 600, &ssn ); /* * 3. Write the GET request */ printf( " > Write to server:" ); fflush( stdout ); len = sprintf( (char *) buf, GET_REQUEST ); while( ( ret = ssl_write( &ssl, buf, len ) ) <= 0 ) { if( ret != POLARSSL_ERR_NET_TRY_AGAIN ) { printf( " failed ! ssl_write returned %d ", ret ); return; } } len = ret; printf( " %d bytes written %s", len, (char *) buf ); /* * 7. Read the HTTP response */ printf( " < Read from server:" ); fflush( stdout ); do { len = sizeof( buf ) - 1; memset( buf, 0, sizeof( buf ) ); ret = ssl_read( &ssl, buf, len ); if( ret == POLARSSL_ERR_NET_TRY_AGAIN ) continue; if( ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY ) break; if( ret <= 0 ) { printf( "failed ! ssl_read returned %d ", ret ); break; } len = ret; printf( " %d bytes read %s", len, (char *) buf ); } while( 0 ); char *cc= (char *)buf; QString ss(cc); qDebug() << ss; ssl_close_notify( &ssl ); return; }
4、执行效果
以下说说执行的效果,首先启动服务器端。服务器端启动线程,监听套接字:
接着启动客户端。客户端链接服务器端,写入消息,读取服务器端的响应:
最后依据打印能够看出服务器端和客户端握手成功。写入读取消息成功:
我们能够依据抓包来看看SSL握手的过程。例如以下:
通过抓包能够看到服务器端和客户端的握手过程,以及在不同的握手阶段中做得事情:
服务器端:
/* * SSL handshake -- server side */ int ssl_handshake_server( ssl_context *ssl ) { int ret = 0; SSL_DEBUG_MSG( 2, ( "=> handshake server" ) ); while( ssl->state != SSL_HANDSHAKE_OVER ) { SSL_DEBUG_MSG( 2, ( "server state: %d", ssl->state ) ); if( ( ret = ssl_flush_output( ssl ) ) != 0 ) break; switch( ssl->state ) { case SSL_HELLO_REQUEST: ssl->state = SSL_CLIENT_HELLO; break; /* * <== ClientHello */ case SSL_CLIENT_HELLO: ret = ssl_parse_client_hello( ssl ); break; /* * ==> ServerHello * Certificate * ( ServerKeyExchange ) * ( CertificateRequest ) * ServerHelloDone */ case SSL_SERVER_HELLO: ret = ssl_write_server_hello( ssl ); break; case SSL_SERVER_CERTIFICATE: ret = ssl_write_certificate( ssl ); break; case SSL_SERVER_KEY_EXCHANGE: ret = ssl_write_server_key_exchange( ssl ); break; case SSL_CERTIFICATE_REQUEST: ret = ssl_write_certificate_request( ssl ); break; case SSL_SERVER_HELLO_DONE: ret = ssl_write_server_hello_done( ssl ); break; /* * <== ( Certificate/Alert ) * ClientKeyExchange * ( CertificateVerify ) * ChangeCipherSpec * Finished */ case SSL_CLIENT_CERTIFICATE: ret = ssl_parse_certificate( ssl ); break; case SSL_CLIENT_KEY_EXCHANGE: ret = ssl_parse_client_key_exchange( ssl ); break; case SSL_CERTIFICATE_VERIFY: ret = ssl_parse_certificate_verify( ssl ); break; case SSL_CLIENT_CHANGE_CIPHER_SPEC: ret = ssl_parse_change_cipher_spec( ssl ); break; case SSL_CLIENT_FINISHED: ret = ssl_parse_finished( ssl ); break; /* * ==> ChangeCipherSpec * Finished */ case SSL_SERVER_CHANGE_CIPHER_SPEC: ret = ssl_write_change_cipher_spec( ssl ); break; case SSL_SERVER_FINISHED: ret = ssl_write_finished( ssl ); break; case SSL_FLUSH_BUFFERS: SSL_DEBUG_MSG( 2, ( "handshake: done" ) ); ssl->state = SSL_HANDSHAKE_OVER; break; default: SSL_DEBUG_MSG( 1, ( "invalid state %d", ssl->state ) ); return( POLARSSL_ERR_SSL_BAD_INPUT_DATA ); } if( ret != 0 ) break; } SSL_DEBUG_MSG( 2, ( "<= handshake server" ) ); return( ret ); }
客户端:
/* * SSL handshake -- client side */ int ssl_handshake_client( ssl_context *ssl ) { int ret = 0; SSL_DEBUG_MSG( 2, ( "=> handshake client" ) ); while( ssl->state != SSL_HANDSHAKE_OVER ) { SSL_DEBUG_MSG( 2, ( "client state: %d", ssl->state ) ); if( ( ret = ssl_flush_output( ssl ) ) != 0 ) break; switch( ssl->state ) { case SSL_HELLO_REQUEST: ssl->state = SSL_CLIENT_HELLO; break; /* * ==> ClientHello */ case SSL_CLIENT_HELLO: ret = ssl_write_client_hello( ssl ); break; /* * <== ServerHello * Certificate * ( ServerKeyExchange ) * ( CertificateRequest ) * ServerHelloDone */ case SSL_SERVER_HELLO: ret = ssl_parse_server_hello( ssl ); break; case SSL_SERVER_CERTIFICATE: ret = ssl_parse_certificate( ssl ); break; case SSL_SERVER_KEY_EXCHANGE: ret = ssl_parse_server_key_exchange( ssl ); break; case SSL_CERTIFICATE_REQUEST: ret = ssl_parse_certificate_request( ssl ); break; case SSL_SERVER_HELLO_DONE: ret = ssl_parse_server_hello_done( ssl ); break; /* * ==> ( Certificate/Alert ) * ClientKeyExchange * ( CertificateVerify ) * ChangeCipherSpec * Finished */ case SSL_CLIENT_CERTIFICATE: ret = ssl_write_certificate( ssl ); break; case SSL_CLIENT_KEY_EXCHANGE: ret = ssl_write_client_key_exchange( ssl ); break; case SSL_CERTIFICATE_VERIFY: ret = ssl_write_certificate_verify( ssl ); break; case SSL_CLIENT_CHANGE_CIPHER_SPEC: ret = ssl_write_change_cipher_spec( ssl ); break; case SSL_CLIENT_FINISHED: ret = ssl_write_finished( ssl ); break; /* * <== ChangeCipherSpec * Finished */ case SSL_SERVER_CHANGE_CIPHER_SPEC: ret = ssl_parse_change_cipher_spec( ssl ); break; case SSL_SERVER_FINISHED: ret = ssl_parse_finished( ssl ); break; case SSL_FLUSH_BUFFERS: SSL_DEBUG_MSG( 2, ( "handshake: done" ) ); ssl->state = SSL_HANDSHAKE_OVER; break; default: SSL_DEBUG_MSG( 1, ( "invalid state %d", ssl->state ) ); return( POLARSSL_ERR_SSL_BAD_INPUT_DATA ); } if( ret != 0 ) break; } SSL_DEBUG_MSG( 2, ( "<= handshake client" ) ); return( ret ); }
版权声明:本文博客原创文章。博客,未经同意,不得转载。