赞
踩
最近在整理电脑上项目工程时,发现之前用来测试gmtls通信编写的c/s测试代码。因此顺便整理了下,记录下来,方便以后回忆。
进行SSL编程实现之前,我们肯定已经对SSL原理有了一定的了解。这里,小编也不再赘述。(尚不了解的可以参考这篇文章:SSL/TLS原理 详细整理版)
原理理解的差不多了,就可以开始使用gmssl编程实现基于tls的socket通讯了(PS. gmssl中已经做了大部分事情了,我们只需要调用一些基本的接口就可以完成TLS socket通讯)。具体分为以下几步:
//SSL 库初始化
SSL_library_init();
//载入所有 SSL 算法
//OpenSSL_add_all_algorithms();
//加载SSL错误信息
SSL_load_error_strings();
//指定服务端使用的协议
const SSL_METHOD *GMTLS_server_method(void); /* GMTLSv1.1 */
//指定客户端使用的协议
const SSL_METHOD *GMTLS_client_method(void); /* GMTLSv1.1 */
//建立SSL上下文
SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth);
//指定所支持的密码套件
int SSL_CTX_set_cipher_list(SSL_CTX *, const char *str);
//此函数用来便是加载CA证书文件的
int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);
//加载自己的证书文件.
int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
//加载自己的私钥,以用于签名.
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
//检查用户私钥是否正确
int SSL_CTX_check_private_key(const SSL_CTX *ctx);
//加载私钥时一般要一个验证密码,这个密码是由生成私钥的一方来设定的,客户端得到这个密码后要通过一个接口传入验证
void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u);
//缺省mode是SSL_VERIFY_NONE,如果想要验证对方的话,便要将此项变成SSL_VERIFY_PEER.SSL/TLS中缺省只验证server,如果没有设置 SSL_VERIFY_PEER的话,客户端连证书都不会发过来.
void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, SSL_verify_cb callback);
//创建SSL对象
SSL *SSL_new(SSL_CTX *ctx);
//将普通的SOCKET加入到ssl
int SSL_set_fd(SSL *s, int fd);
//服务端接受客户端ssl连接
int SSL_accept(SSL *ssl);
//客户端连接服务端SSL
int SSL_connect(SSL *ssl);
//发送数据
int SSL_write(SSL *ssl, const void *buf, int num);
//接收数据
int SSL_read(SSL *ssl, void *buf, int num);
SSL_CTX_free(ctx);
SSL_shutdown(ssl);
SSL_free(ssl);
ERR_free_strings();
PS. 想了解更多接口信息可以参考这篇文章:openssl编程——SSL实现
// TestServer.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #ifdef WIN32 #include "Winsock2.h" #pragma comment(lib, "ws2_32.lib") #else #include "sys/socket.h" #endif #include <iostream> #include <conio.h> using namespace std; #include <openssl/err.h> #include <openssl/ssl.h> #define IPADDRESS "127.0.0.1" #define PORT 443 #define LISTENQ 1 #define MAXBUF 1024 #define CA_CERT_FILE "GMCert_GMCA01.cert.pem" #define SIGN_CERT_FILE "server.cert.pem" #define SIGN_KEY_FILE "server.key.pem" #define ENC_CERT_FILE "server_enc.cert.pem" #define ENC_KEY_FILE "server_enc.key.pem" #define CIPHER_ int config_ssl_ctx(SSL_CTX *ctx) { //SSL_CTX_set_cipher_list(ctx, "SM2-WITH-SMS4-SM3"); #if 1 // 是否要求校验对方证书 若不验证客户端身份则设置为: SSL_VERIFY_NONE SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); #else //验证对方 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); //若验证,则放置CA证书 int ret = SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL); if (ret < 0) { printf("SSL_CTX_load_verify_locations failed."); } //设置pass phrase SSL_CTX_set_default_passwd_cb_userdata(ctx, "12345678"); #endif //双证书模式,需要先设置签名证书,然后再设置加密证书 //载入服务端数字签名证书 if (SSL_CTX_use_certificate_file(ctx, SIGN_CERT_FILE, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return(1); } //载入服务端签名私钥 if (SSL_CTX_use_PrivateKey_file(ctx, SIGN_KEY_FILE, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); return(1); } //检查用户私钥是否正确 if (!SSL_CTX_check_private_key(ctx)) { ERR_print_errors_fp(stdout); return(1); } //载入服务端加密证书 if (SSL_CTX_use_certificate_file(ctx, ENC_CERT_FILE, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return(1); } //载入加密私钥 if (SSL_CTX_use_PrivateKey_file(ctx, ENC_KEY_FILE, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); return(1); } //检查用户私钥是否正确 if (!SSL_CTX_check_private_key(ctx)) { ERR_print_errors_fp(stdout); return(1); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { SOCKET sockfd; struct sockaddr_in my_addr; //一般是储存地址和端口的。用于信息的显示及存储使用 SOCKET new_fd; struct sockaddr_in their_addr; //用于存储 当连接成功时所返回的客户的TCP连接信息。 char buf[MAXBUF + 1]; struct fd_set fds; //定义一个读(接受消息)的集合 struct timeval timeout = { 2, 0 }; // select 等待 3 秒,3 秒轮询, 要非阻塞就置 0 int maxfdp; //集合中所有文件描述符的范围,即所有文件描述符的最大值加1 int len; SSL_CTX *ctx = NULL; SSL_METHOD *meth; //SSL 库初始化 SSL_library_init(); //载入所有 SSL 算法 //OpenSSL_add_all_algorithms(); //加载SSL错误信息 SSL_load_error_strings(); //meth = (SSL_METHOD *)SSLv23_server_method(); //gmssl双证书通信 meth = (SSL_METHOD *)GMTLS_server_method(); //建立新的SSL上下文 ctx = SSL_CTX_new(meth); if (ctx == NULL) { ERR_print_errors_fp(stdout); cout << "SSL_CTX_new error!" << endl; exit(1); } if (0 != config_ssl_ctx(ctx)) { ERR_print_errors_fp(stdout); cout << "config_ssl_ctx error!" << endl; SSL_CTX_free(ctx); getchar(); exit(1); } #ifdef WIN32 WSADATA wsaData; //初始化windows socket资源 绑定socket库 版本为2.2 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { cout << "WSAStartup error" << endl; return 1; } #endif //创建socket sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd == INVALID_SOCKET) { cout << "socket error" << endl; goto END2; } //设置 sockaddr_in 结构体中相关参数 memset(&my_addr, 0, sizeof(my_addr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(PORT); my_addr.sin_addr.s_addr = inet_addr(IPADDRESS); // //将一本地地址与一套接口捆绑 if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) { cout << "bind error" << endl; goto END1; } cout << "binded" << IPADDRESS << PORT << endl; //监听这个socket if (listen(sockfd, LISTENQ) == -1) { cout << "listen error" << endl; goto END1; } cout << "begin listen..." << endl; //不停的select才可以读取套接字的状态改变 while (true) { //控制退出循环 if (_kbhit()) break; FD_ZERO(&fds); // 每次循环都要清空,否则不能检测描述符变化 FD_SET(sockfd, &fds); // 添加套接字描述符 maxfdp = sockfd + 1; //描述符最大值加1 //利用select选择出集合中可以读写的多个套接字,有点像筛选 int ret = select(maxfdp, &fds, &fds, NULL, &timeout); if (ret == SOCKET_ERROR) { //select 错误,退出程序 break; } else if (ret == 0) { //等待超时,没有可读写或错误的文件, 则再次轮询 continue; } else { if (FD_ISSET(sockfd, &fds)) // 测试sockfd是否可读,即是否网络上有数据 { len = sizeof(struct sockaddr); //取得一个套接口接受的一个连接 new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len); if (new_fd == INVALID_SOCKET) { perror("accept error"); //exit(errno); break; } printf("server: got connection from %s, port %d, socket %d \n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd); //TCP连接已经建立,执行Server SSL SSL *ssl = SSL_new(ctx); //SOCKET加入到ssl SSL_set_fd(ssl, new_fd); //建立ssll连接 if (SSL_accept(ssl) == -1) { perror("SSL_accept failed."); #ifdef WIN32 closesocket(new_fd); #else close(new_fd); #endif break; } /*打印所有加密算法的信息(可选)*/ printf("SSL connection using %s\n", SSL_get_cipher(ssl)); //发消息给客户端 memset(buf, 0, MAXBUF + 1); strcpy(buf, "let's do someting..."); //len = send(new_fd, buf, strlen(buf), 0); len = SSL_write(ssl, buf, strlen(buf)); if (len < 0) { printf("message send failed! [%s] errcode=%d,error message'%s' \r\n", buf, errno, strerror(errno)); goto finish; } printf("message send succes, [%s] total send %d bytle! \r\n", buf, len); //接收客户端消息 memset(buf, 0, MAXBUF + 1); //len = recv(new_fd, buf, sizeof(buf), 0); len = SSL_read(ssl, buf, MAXBUF); if (len > 0) printf("recive message succes :'%s',total %d bytle data \r\n", buf, len); else printf("recive message error! errcode id=%d,error message is '%s' \r\n", errno, strerror(errno)); finish: SSL_shutdown(ssl); SSL_free(ssl); #ifdef WIN32 closesocket(new_fd); #else close(new_fd); #endif } } } END1: SSL_CTX_free(ctx); ERR_free_strings(); //关闭套接字 #ifdef WIN32 closesocket(sockfd); #else close(sockfd); #endif END2: #ifdef WIN32 WSACleanup(); //解除绑定,释放资源 #endif return 0; }
// TestClient.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #ifdef WIN32 #include "Winsock2.h" #pragma comment(lib, "ws2_32.lib") #else #include "sys/socket.h" #endif #include <iostream> using namespace std; #include <openssl/err.h> #include <openssl/ssl.h> #define MAXBUF 1024 #define SERVER_IP "127.0.0.1" //"172.16.0.175" #define SERVER_PORT 443 #define CA_CERT_FILE "GMCert_GMCA01.cert.pem" #define USR_CERT_FILE "client.cert.pem" #define USR_KEY_FILE "client.key.pem" void ShowCerts(SSL *ssl) { X509 *cert; char *subj = NULL, *issuer = NULL; EVP_PKEY *pstPubKey; int iPubKeyLen; printf("-------show cert information---------\n"); //从SSL连接中获取证书信息 cert = SSL_get_peer_certificate(ssl); if (cert != NULL) { // 获取真实证书的公钥 pstPubKey = X509_get_pubkey(cert); // 获取真实证书中公钥的密钥长度 iPubKeyLen = EVP_PKEY_bits(pstPubKey); if (iPubKeyLen == 0) { cout << "Get num bits from real cert failed!" << endl; } else { printf("Bytes size: %d, Bits length: %d. \n", EVP_PKEY_size(pstPubKey), iPubKeyLen); } // 获取真实证书的持有者信息X509_get_subject_name (同上,假设已经提前获取了指向X509结构真实证书的指针cert) // 将结构体形式的持有者信息输出为一行的形式(X509_NAME_oneline):/type0=value0/type1=value1/type2=... subj = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("cert: %s\n", subj); OPENSSL_free(subj); issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("Issuer: %s\n", issuer); OPENSSL_free(issuer); X509_free(cert); } else { printf("no cert message \n"); } printf("-------show cert info end---------\n"); } int config_ssl_ctx(SSL_CTX *ctx) { //SSL_CTX_set_cipher_list(ctx, "SM2-WITH-SMS4-SM3"); #if 0 //要求校验对方证书,表示需要验证服务器端,若不需要验证则使用 SSL_VERIFY_NONE SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); #else /* Set flag in context to require peer (server) certificate verification */ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); /* Load the CA certificate into the SSL_CTX structure */ /* This will allow this client to verify the server's certificate. */ int ret = SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL); if (ret < 0) { printf("SSL_CTX_load_verify_locations failed."); } #endif #if 0 //载入客户端数字证书 if (SSL_CTX_use_certificate_file(ctx, USR_CERT_FILE, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); exit(1); } //载入客户端私钥 if (SSL_CTX_use_PrivateKey_file(ctx, USR_KEY_FILE, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(1); } //检查私钥是否正确 if (!SSL_CTX_check_private_key(ctx)) { ERR_print_errors_fp(stdout); exit(1); } #endif return 0; } int _tmain(int argc, _TCHAR* argv[]) { SOCKET sockfd; struct sockaddr_in dest; char buffer[MAXBUF + 1] = { 0 }; int len = 0; SSL_CTX *ctx; SSL *ssl; SSL_METHOD *meth; //SSL 库初始化 SSL_library_init(); //载入所有 SSL 算法 //OpenSSL_add_all_algorithms(); //加载SSL错误信息 SSL_load_error_strings(); //meth = (SSL_METHOD *)SSLv23_client_method(); //gmssl双证书通信 meth = (SSL_METHOD *)GMTLS_client_method(); //建立新的SSL上下文 ctx = SSL_CTX_new(meth); if (ctx == NULL) { ERR_print_errors_fp(stdout); exit(1); } if (0 != config_ssl_ctx(ctx)) { ERR_print_errors_fp(stdout); cout << "config_ssl_ctx error!" << endl; SSL_CTX_free(ctx); getchar(); exit(1); } #ifdef WIN32 WSADATA wsaData; //初始化socket资源 绑定socket库 版本为2.2 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { cout << "WSAStartup error" << endl; return 1; } #endif //创建客户端套节字 sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd == INVALID_SOCKET) { cout << "socket error" << endl; #ifdef WIN32 WSACleanup(); #endif exit(errno); } cout << "socket created." << endl; //设置远程服务器的地址信息(端口号、IP地址等) memset(&dest, 0, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(SERVER_PORT); dest.sin_addr.s_addr = inet_addr(SERVER_IP); //连接服务器 连接后可以用sockfd来使用这个连接 dest保存了远程服务器的地址信息 if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) { perror("Connect error!"); exit(errno); } printf("server connected\n"); //TCP连接已经建立,将连接付给SSL ssl = SSL_new(ctx); //socket加入到ssl SSL_set_fd(ssl, sockfd); //建立ssl连接 if (SSL_connect(ssl) == -1) { printf("SSL_connect failed\n"); ERR_print_errors_fp(stderr); } else { /*打印所有加密算法的信息(可选)*/ printf("-------show used cipher---------\n"); printf("%s\n", SSL_get_cipher(ssl)); printf("-------show cipher end---------\n"); ShowCerts(ssl); } //接收服务器来的消息 memset(buffer, 0, MAXBUF + 1); //len = recv(sockfd, buffer, sizeof(buffer), 0); len = SSL_read(ssl, buffer, MAXBUF); if (len <= 0) { printf("recive message failed %d,error message is '%s' \r\n", errno, strerror(errno)); goto END; } printf("recive message succes:'%s',total %d bytle data \r\n", buffer, len); //发消息给服务器 len = 0; memset(buffer, 0, MAXBUF + 1); strcpy(buffer, "Hi server, i am client."); //len = send(sockfd, buffer, strlen(buffer), 0); len = SSL_write(ssl, buffer, strlen(buffer)); if (len < 0) printf("message'%s'send failed!error code id %d,error message is '%s' \r\n", buffer, errno, strerror(errno)); else printf("message'%s'send success,total send %d bytle data! \r\n", buffer, len); getchar(); END: SSL_shutdown(ssl); SSL_free(ssl); #ifdef WIN32 closesocket(sockfd); #else close(sockfd); #endif SSL_CTX_free(ctx); ERR_free_strings(); #ifdef WIN32 WSACleanup(); //解除绑定 释放资源 #endif system("pause"); return 0; }
通过wireshark抓包如下。
从上面的抓包结果我们看到SSL通信过程中的每一包数据,但用过抓包工具的童鞋都知道这个结果似乎不是很直观(并没有将其client hello,server hello, Key Exchange…等这些过程标准出来)。需要我们对于每一包数据进行解析,与SSL通信协议进行比以确定去确实是遵循的ssl协议。
其实还有一个取巧的办法:wireshark之所以无法解析通信的每一个步骤,是因为其无法识别gmtls的协议版本(国际规范中TLS协议号为0x0301、0x0302、0x0303,分别表示TLS1.0 、TLS1.1 、TLS1.2;而国密SSL版本号为0x0101,其参考了TLS1.1)。
因此,我们可以将数据包中的版本号(16 01 01)手动修改为wireshark可识别的TLS协议号(如:16 03 01),然后重新用wireshark打开就能识别了~如下所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。