参考链接
工程搭建介绍
- Ubuntu安装GmSSL库适用于ubuntu18和ubuntu20版本_MY CUP OF TEA的博客-CSDN博客
- CLion运行程序时添加命令行参数 即设置argv输入参数_MY CUP OF TEA的博客-CSDN博客
- 基于SM2证书实现SSL通信_MY CUP OF TEA的博客-CSDN博客
- 基于Gmssl库静态编译,实现服务端和客户端之间的SSL通信_MY CUP OF TEA的博客-CSDN博客
- 基于openssl和国密算法生成CA、服务器和客户端证书_MY CUP OF TEA的博客-CSDN博客
- openssl实现双向认证教程(服务端代码+客户端代码+证书生成)_MY CUP OF TEA的博客-CSDN博客
- mac系统使用 clion远程调试redis4源码_迹忆客 target_link_libraries
相关函数介绍
- dtls到srtp的整个流程_小狮子slioner的博客-CSDN博客
- dtls_srtp学习笔记_weixin_30779691的博客-程序员宅基地 - 程序员宅基地 SSL_export_keying_material
- C语言inet_aton()函数:将网络地址转成网络二进制的数字_C语言中文网
- /docs/man1.0.2/man3/SSL_CTX_set_mode.html
- /docs/man1.0.2/man3/SSLeay_add_ssl_algorithms.html
Server
层次结构
- lib存放静态编译gmssl代码生成的静态库,即libssl.a和libcrypto.a
- pem存放证书文件,因为SSL双向验证,服务端会验证客户端的证书,因此服务端存放客户端的证书和私钥,以及生成客户端证书时用于签名的ca证书
Code
#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 1500
void 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);
} else
printf("无证书信息!\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);
} else
printf("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);
} else
printf("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);
} else
printf("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;
} else
printf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);
bzero(buf, MAXBUF + 1);
/* 接收客户端的消息 */
len = SSL_read(ssl, buf, MAXBUF);
if (len > 0)
printf("接收消息成功:'%s',共%d个字节的数据\n", buf, len);
else
printf("消息接收失败!错误代码是%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;
}
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(${PROJECT_SOURCE_DIR}/lib)
# 指定头文件搜索策略
include_directories(/usr/local/gmssl/include)
# 使用指定的源文件来生成目标可执行文件
add_executable(${PROJECT_NAME} ssl_server.cpp)
# 将库链接到项目中
target_link_libraries(${PROJECT_NAME} libssl.a libcrypto.a pthread dl)
配置执行输入参数
- CLion运行程序时添加命令行参数 即设置argv输入参数_MY CUP OF TEA的博客-CSDN博客
- 127.0.0.1
7838
执行结果
Client
层次结构
- lib存放静态编译gmssl代码生成的静态库,即libssl.a和libcrypto.a
- pem存放证书文件,因为SSL双向验证,客户端同样会验证客户端的证书,因此客户端存放服务端的证书和私钥,以及生成服务端证书时用于签名的ca证书
Code
#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 1024
void 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);
} else
printf("无证书信息!\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.html
SSL_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和salt
unsigned 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));
else
printf("消息'%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;
}
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(${PROJECT_SOURCE_DIR}/lib)
# 指定头文件搜索策略
include_directories(/usr/local/gmssl/include)
link_libraries(ssl crypto)
# 使用指定的源文件来生成目标可执行文件
add_executable(${PROJECT_NAME} ssl_client.cpp)
# 将库链接到项目中
target_link_libraries(${PROJECT_NAME} libssl.a libcrypto.a pthread dl)
配置执行输入参数
- 127.0.0.1
7838
执行结果