参考链接
- Ubuntu配置gmssl和openssl,且均使用动态库,使用时根据需要进行动态切换_MY CUP OF TEA的博客-CSDN博客 编译gmssl动态库并关闭openssl配置,开启gmssl配置
- 基于GmSSL实现server服务端和client客户端之间SSL通信代码(升级优化公开版)_MY CUP OF TEA的博客-CSDN博客
服务端 Server
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)project(ssl_server)
set(CMAKE_CXX_STANDARD 11)# 忽略警告
set(CMAKE_CXX_FLAGS "-Wno-error=deprecated-declarations -Wno-deprecated-declarations ")# 指定lib目录
link_directories(/usr/local/gmssl/lib)# 指定头文件搜索策略
include_directories(/usr/local/gmssl/include)# 使用指定的源文件来生成目标可执行文件
add_executable(${PROJECT_NAME} ssl_server.cpp)# 将库链接到项目中
target_link_libraries(${PROJECT_NAME} ssl crypto pthread dl)
代码
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>#define MAXBUF 1500void ShowCerts(SSL * ssl)
{X509 *cert;char *line;cert = SSL_get_peer_certificate(ssl);// SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证// 如果验证不通过,那么程序抛出异常中止连接if(SSL_get_verify_result(ssl) == X509_V_OK){printf("证书验证通过\n");}if (cert != nullptr) {printf("数字证书信息:\n");line = X509_NAME_oneline(X509_get_subject_name(cert), nullptr, 0);printf("证书: %s\n", line);free(line);line = X509_NAME_oneline(X509_get_issuer_name(cert), nullptr, 0);printf("颁发者: %s\n", line);free(line);X509_free(cert);} elseprintf("无证书信息!\n");
}int main(int argc, char **argv) {int listen_fd = -1; /* TCP监听套接字 */int accept_fd = -1; /* 已连接TCP套接字 */struct sockaddr_in server_addr, client_addr;bzero(&server_addr, sizeof(server_addr));SSL_CTX *ctx = nullptr; /* SSL会话环境 */SSL *ssl = nullptr; /* SSL安全套接字 */socklen_t len;char buf[MAXBUF]={0}; /* 服务器接收数据buffer */if( 3!=argc ){printf("argcment wrong:ip port\n");}SSL_library_init(); /* SSL 库初始化 */SSLeay_add_ssl_algorithms();OpenSSL_add_all_algorithms(); /* 载入所有 SSL 算法 */SSL_load_error_strings(); /* 载入所有 SSL 错误消息 */
// ERR_load_BIO_strings();//TCP服务器:创建、绑定、监听if ((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {perror("socket create wrong\n");exit(1);} elseprintf("socket created\n");server_addr.sin_family = PF_INET;server_addr.sin_port = htons(atoi(argv[2]));server_addr.sin_addr.s_addr = inet_addr(argv[1]);;if (bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(struct sockaddr))== -1) {perror("bind wrong\n");exit(1);} elseprintf("binded success\n");int lisnum = 2;do{//使用SSL_CTX_new()创建会话环境,建立连接时要使用协议由TLS_server_method()来定。如果这一步出错,需要查看错误栈来查看原因if(nullptr == (ctx = SSL_CTX_new( TLSv1_2_method()))) //using sm3, TLSv1_2_method{ERR_print_errors_fp(stdout);break;}// 双向验证// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);// 设置信任根证书if(SSL_CTX_load_verify_locations(ctx, "/home/chy-cpabe/CLionProjects/learn_GmSSL_server/pem/CaCert.pem", nullptr) != 1){printf("SSL_CTX_load_verify_locations error\n");ERR_print_errors_fp(stdout);break;}/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */if( 0>=SSL_CTX_use_certificate_file(ctx, "/home/chy-cpabe/CLionProjects/learn_GmSSL_server/pem/HuiguanCert.pem", SSL_FILETYPE_PEM/*SSL_FILETYPE_ASN1*/) ) /* 为SSL会话加载用户证书 */{ERR_print_errors_fp(stdout);break;}/* 载入用户私钥 */if( 0>=SSL_CTX_use_PrivateKey_file(ctx, "/home/chy-cpabe/CLionProjects/learn_GmSSL_server/pem/HuiguanKey.pem", SSL_FILETYPE_PEM/*SSL_FILETYPE_ASN1*/) ) /* 为SSL会话加载用户私钥 */{ERR_print_errors_fp(stdout);break;}/* 检查用户私钥是否正确 */if(!SSL_CTX_check_private_key(ctx)) /* 验证私钥和证书是否相符 */{ERR_print_errors_fp(stdout);break;}if (listen(listen_fd, lisnum) == -1) {perror("listen wrong\n");exit(1);} elseprintf("begin listen\n");len = sizeof(struct sockaddr);/* 等待客户端连上来 */if ((accept_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &len))== -1) {perror("accept wrong\n");exit(errno);} else{printf("server: got connection from %s, port %d, socket %d\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),accept_fd);}ssl = SSL_new(ctx); /* 基于 ctx 产生一个新的 SSL */SSL_set_fd(ssl, accept_fd); /* 将连接用户的 socket 加入到 SSL *//* 建立 SSL 连接 */if (SSL_accept(ssl) == -1) {perror("accept wrong\n");SSL_shutdown(ssl);SSL_free(ssl);ssl= nullptr;close(accept_fd);accept_fd=-1;break;}ShowCerts(ssl);/* 开始处理每个新连接上的数据收发 */bzero(buf, MAXBUF + 1);strcpy(buf, "server->client");/* 发消息给客户端 */len = SSL_write(ssl, buf, strlen(buf));if (len <= 0) {printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno,strerror(errno));goto finish;} elseprintf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);bzero(buf, MAXBUF + 1);/* 接收客户端的消息 */len = SSL_read(ssl, buf, MAXBUF);if (len > 0)printf("接收消息成功:'%s',共%d个字节的数据\n", buf, len);elseprintf("消息接收失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno));/* 处理每个新连接上的数据收发结束 */finish:/* 关闭 SSL 连接 */SSL_shutdown(ssl);/* 释放 SSL */SSL_free(ssl);ssl = nullptr;/* 关闭 socket */close(accept_fd);accept_fd = -1;}while(1);/* 关闭监听的 socket */close(listen_fd);listen_fd = -1;/* 释放 CTX */SSL_CTX_free(ctx);ctx = nullptr;return 0;
}
配置
注意事项
ca md too weak:ssl/ssl_rsa.c:301:
- 如果不指定lib目录,即删去link_directories(/usr/local/gmssl/lib)这句话,就会导致一个错误
- 错误如下 140690036335040:error:140AB18E:SSL routines:SSL_CTX_use_certificate:ca md too weak:ssl/ssl_rsa.c:301:
- 这个原因是由于/etc/profile文件中,关闭openssl开启gmssl,并且使用source /etc/profile更新了环境变量,使得gmssl的bin和lib被所有用户了解,但是编译器却没有识别到,仍然使用的是openssl的动态库产生的问题
- 如果指定了lib路径就不会出问题
- # 指定lib目录 link_directories(/usr/local/gmssl/lib)
- https://blog.csdn.net/CHYabc123456hh/article/details/125773799
- 出现ca md too weak:ssl/ssl_rsa.c:301的原因是openssl调整了安全级别,要求ca具备更高等级的安全,因此先前发布的证书,如果采用了不安全的算法,比如MD5,就会显示上述这个错误
- 最简单的方式就是设置安全级别 SSL_CTX_set_security_level(ctx,1);
SSL_CTX_set_security_level(ctx,1)
- SSL_CTX_set_security_level函数需要放在SSL_CTX_new生成ssl之后
//使用SSL_CTX_new()创建会话环境,建立连接时要使用协议由TLS_server_method()来定。如果这一步出错,需要查看错误栈来查看原因if(nullptr == (ctx = SSL_CTX_new( TLSv1_2_method()))) //using sm3, TLSv1_2_method{ERR_print_errors_fp(stdout);break;}SSL_CTX_set_security_level(ctx,0);
- SSL_CTX *ctx = nullptr; /* SSL会话环境 */ SSL_CTX_set_security_level(ctx,0); 刚创建ctx就设置级别,会导致如下错误
- Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
客户端 Client
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(ssl_client)set(CMAKE_CXX_STANDARD 11)# 忽略警告
set(CMAKE_CXX_FLAGS "-Wno-error=deprecated-declarations -Wno-deprecated-declarations ")# 指定lib目录
link_directories(/usr/local/gmssl/lib)# 指定头文件搜索策略
include_directories(/usr/local/gmssl/include)# 使用指定的源文件来生成目标可执行文件
add_executable(${PROJECT_NAME} ssl_client.cpp)# 将库链接到项目中
target_link_libraries(${PROJECT_NAME} ssl crypto pthread dl)
代码
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <cstdlib>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>#define MAXBUF 1024void ShowCerts(SSL * ssl)
{X509 *cert;char *line;cert = SSL_get_peer_certificate(ssl);// SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证// 如果验证不通过,那么程序抛出异常中止连接if(SSL_get_verify_result(ssl) == X509_V_OK){printf("证书验证通过\n");}if (cert != nullptr) {printf("数字证书信息:\n");line = X509_NAME_oneline(X509_get_subject_name(cert), nullptr, 0);printf("证书: %s\n", line);free(line);line = X509_NAME_oneline(X509_get_issuer_name(cert), nullptr, 0);printf("颁发者: %s\n", line);free(line);X509_free(cert);} elseprintf("无证书信息!\n");
}static void PrintData(char *p, char *buf,int len,char *filename)
{char *name=p;printf("%s[%d]:\n",p,len);for (p=buf; p && p++-buf<len;)printf("%02x%c",(unsigned char)p[-1],(!((p-buf)%16) || p-buf==len)?'\n':' ');
// if (filename) FileWrite(name,buf,len,filename);
}int main(int argc, char **argv)
{int sock_fd = -1; /* TCP套接字 */int len = 0; /* SSL会话环境 */SSL *ssl = nullptr; /* SSL安全套接字 */struct sockaddr_in ser_addr; /* 服务器地址 */bzero(&ser_addr, sizeof(ser_addr));SSL_CTX *ctx = nullptr;char buffer[MAXBUF + 1];if( argc != 3 ){printf("argcment wrong:ip port content\n");exit(0);}/* SSL 库初始化,参看 ssl-server.c 代码 */SSL_library_init();SSLeay_add_ssl_algorithms();OpenSSL_add_all_algorithms();SSL_load_error_strings();
// ERR_load_BIO_strings();do{/* 申请SSL会话环境 */if( nullptr==(ctx=SSL_CTX_new(TLSv1_2_method())) ) //使用SSL_CTX_new()创建会话环境,建立连接时要使用协议由TLS_client_method()来定,服务器由对应的TLS_server_method()来定。如果这一步出错,需要查看错误栈来查看原因{ERR_print_errors_fp(stdout);break;}// 双向验证// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);// 设置信任根证书if (SSL_CTX_load_verify_locations(ctx, "/home/chy-cpabe/CLionProjects/learn_GmSSL_server/pem/CaCert.pem",nullptr)<=0){ERR_print_errors_fp(stdout);exit(1);}/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */if (SSL_CTX_use_certificate_file(ctx, "/home/chy-cpabe/CLionProjects/ssl_client/src/pem/TerminalCert.pem", SSL_FILETYPE_PEM) <= 0) {ERR_print_errors_fp(stdout);exit(1);}/* 载入用户私钥 */if (SSL_CTX_use_PrivateKey_file(ctx, "/home/chy-cpabe/CLionProjects/ssl_client/src/pem/TerminalKey.pem", 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);}//https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_mode.htmlSSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);/* 创建一个 socket 用于 tcp 通信 */if(-1==(sock_fd=socket(AF_INET, SOCK_STREAM, 0)) ){printf("creat socket wrong\n");break;}printf("socket created\n");/* 初始化服务器端(对方)的地址和端口信息 */ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(atoi(argv[2]));ser_addr.sin_addr.s_addr = inet_addr(argv[1]);//将网络地址转成网络二进制的数字//http://c.biancheng.net/cpp/html/362.html//另外一种写法
/* if (inet_aton(argv[1], (struct in_addr *) &ser_addr.sin_addr.s_addr) == 0) {perror(argv[1]);exit(errno);}
*/printf("address created\n");//建立连接if( -1==(connect(sock_fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr))) ){printf("connect wrong\n");break;}printf("server connected\n");/* 基于 ctx 产生一个新的 SSL */ssl = SSL_new(ctx);SSL_set_fd(ssl, sock_fd);/* 建立 SSL 连接 */if (SSL_connect(ssl) == -1)ERR_print_errors_fp(stderr);else {printf("The relevant information is as follows:\n");printf("-->ssl version %s\n",SSL_get_version(ssl));printf("-->ssleay version %s\n",SSLeay_version(0));printf("-->Connected with %s encryption\n", SSL_get_cipher(ssl));ShowCerts(ssl);}//导出key和saltunsigned char buf[16];int err = -1;err = SSL_export_keying_material(ssl, buf, 16, nullptr,0, nullptr, 0, 1);if(err != 1){printf("err=%d\n",err);}else{PrintData("SSL_export_keying_material", (char*)buf, 16, nullptr);}/* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */bzero(buffer, MAXBUF + 1);/* 接收服务器来的消息 */len = SSL_read(ssl, buffer, MAXBUF);if (len > 0)printf("接收消息成功:'%s',共%d个字节的数据\n",buffer, len);else {printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno));goto finish;}bzero(buffer, MAXBUF + 1);strcpy(buffer, "from client->server");/* 发消息给服务器 */len = SSL_write(ssl, buffer, strlen(buffer));if (len < 0)printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",buffer, errno, strerror(errno));elseprintf("消息'%s'发送成功,共发送了%d个字节!\n",buffer, len);/* 处理每个新连接上的数据收发结束 */finish:/* 关闭 SSL 连接 */SSL_shutdown(ssl);/* 释放 SSL */SSL_free(ssl);ssl = nullptr;}while(0);/* 关闭socket */close(sock_fd);sock_fd = -1;/* 释放 CTX */SSL_CTX_free(ctx);ctx = nullptr;return 0;
}
配置
执行结果
server
client