參考鏈接
- 使用Clion和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/openssl/lib64)# 指定頭文件搜索策略
include_directories(/usr/local/openssl/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_CTX_set_security_level(ctx,0);// 雙向驗證// 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)if(SSL_CTX_load_verify_locations(ctx, "/home/chy-cpabe/ssl_server_client.openssl_bak/ca/ca.crt", 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會話加載用戶證書 */if( 0>=SSL_CTX_use_certificate_file(ctx, "/home/chy-cpabe/ssl_server_client.openssl_bak/server/pem/server.crt", 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會話加載用戶私鑰 */if( 0>=SSL_CTX_use_PrivateKey_file(ctx, "/home/chy-cpabe/ssl_server_client.openssl_bak/server/pem/server_rsa_private.pem.unsecure", 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;
}
配置

問題
unknown certificate type:ssl/ssl_rsa.c:239:
- 0040840C3D7F0000:error:0A0000F7:SSL routines:ssl_set_cert:unknown certificate type:ssl/ssl_rsa.c:239:
- 還沒有從網上找到合適的解決錯誤,我猜測是因為無法讀取國密證書所導致的問題
- 更換為RSA證書之后,此問題消失
使用一對RSA證書,但是使用與其不對應的CA,不會報錯
- 單個server代碼里面這么寫不會報錯,但是在和客戶端之間進行通信驗證之后會報錯的
- 0060C8001A7F0000:error:0A000418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:ssl/record/rec_layer_s3.c:1584:SSL alert number 48
RSA的私鑰需要刪除密碼
0060F01A847F0000:error:1400006B:UI routines:UI_process:processing error:crypto/ui/ui_lib.c:544:while reading strings
0060F01A847F0000:error:0480006D:PEM routines:PEM_def_callback:problems getting password:crypto/pem/pem_lib.c:62:
0060F01A847F0000:error:07880109:common libcrypto routines:do_ui_passphrase:interrupted or cancelled:crypto/passphrase.c:184:
0060F01A847F0000:error:07880109:common libcrypto routines:do_ui_passphrase:interrupted or cancelled:crypto/passphrase.c:184:
0060F01A847F0000:error:1C80009F:Provider routines:epki2pki_decode:unable to get passphrase:providers/implementations/encode_decode/decode_epki2pki.c:96:
0060F01A847F0000:error:1400006B:UI routines:UI_process:processing error:crypto/ui/ui_lib.c:544:while reading strings
0060F01A847F0000:error:0480006D:PEM routines:PEM_def_callback:problems getting password:crypto/pem/pem_lib.c:62:
0060F01A847F0000:error:07880109:common libcrypto routines:do_ui_passphrase:interrupted or cancelled:crypto/passphrase.c:184:
0060F01A847F0000:error:04800068:PEM routines:pem_read_bio_key_legacy:bad password read:crypto/pem/pem_pkey.c:155:
0060F01A847F0000:error:0A080009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib:ssl/ssl_rsa.c:384:

客戶端 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/openssl/lib64)# 指定頭文件搜索策略
include_directories(/usr/local/openssl/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)if(SSL_CTX_load_verify_locations(ctx, "/home/chy-cpabe/ssl_server_client.openssl_bak/ca/ca.crt", nullptr) != 1){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)if (SSL_CTX_use_certificate_file(ctx, "/home/chy-cpabe/ssl_server_client.openssl_bak/client/pem/client.crt", 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)if (SSL_CTX_use_PrivateKey_file(ctx, "/home/chy-cpabe/ssl_server_client.openssl_bak/client/pem/client_rsa_private.pem.unsecure", 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;
}
配置?

執行結果
- 參考鏈接?使用Clion和gmssl動態庫實現服務器server和客戶端client之間的SSL通信_MY CUP OF TEA的博客-CSDN博客?
- 使用函數?SSL_get_version 可以得到代碼編譯使用的庫的版本號
- -->ssleay version OpenSSL 3.0.4 21 Jun 2022
- -->ssleay version Gmssl 2.5.4 - OpenSSL1.1.0d 19 Jun 2019?
server

?client

補充知識?
查看證書的類型
- openssl x509 -text -in ca.crt?
