openSSL SSL

xiaoxiao2021-02-28  20

最近公司项目需要把服务器端C++实现的 websocket 改成websocket secure(websocket + ssl)。百度了一下有一个广泛使用的ssl库,openssl。花了两天时间一顿操作之后客户端用js终于连接上服务器上的wss。但是诡异的是,每次客户端来消息后,服务器调用SSL_read函数总是会读完一个字节后返回,第二次调用(epoll控制)才会读完剩下的字节。 比如客户端发了502字节的数据包,服务器这边epoll响应 调用到SSL_read,会先读1个字节,epoll再次响应,再读剩下的501个字节。等于调用了两次read才把一个数据包读完! 百思不得其解啊,在客户端用wrieshark抓包,客户端从来没发过数据长度位1的有效包。说明不是客户端问题。 只好去翻ssl manual ,怀疑是不是什么参数没设置。也没发现有什么参数可设置的。。 最后,怀疑是协议版本的问题。。然后我把把openssl 用的ssl协议从TSL1.0,改成了TSL1.2,就尼玛好了。。可能是js websocket类用的ssl协议版本高于1.0,结果导致这个问题。 好吧,差点怀疑人生。不知道有没有朋友和我遇到过一样的问题。

顺便把我参考网上的代码封装的openssl类贴一下,一个openSSL类,一个SSL管理类。

// // Created by jamesjiang on 2018/5/15. // #ifndef GAME_OPENSSL_H #define GAME_OPENSSL_H #include <string> #include "common_tcpconnection.h" #include <openssl/ssl.h> #include <openssl/err.h> using namespace GameJayo::Server; class openSSL { public: openSSL(); ~openSSL(); public: // 初始化 openssl 上下文 static int32_t init_ssl_ctx(); static void shutdown_ssl(); int32_t creat_ssl(int32_t fd); int32_t accept_ssl_fd(int32_t fd); // return > 0 recv 字节数, < 0 error , == 0 判断 int32_t ssl_recv(char* data, int32_t size); // return > 0 send 字节数, < 0 error , == 0 判断 int32_t ssl_send(char* data, int32_t size); private: SSL* m_ssl; int32_t m_fd; static SSL_CTX* m_ctx; public: void ShowCerts(); }; #endif //GAME_OPENSSL_H // // Created by jamesjiang on 2018/5/15. // #include "openSSL.h" #define CA_CERT_FILE "ssl/123u.com_combined.crt" #define SERVER_CERT_FILE "ssl/123u.com_combined.crt" #define SERVER_KEY_FILE "ssl/123u.com.key" SSL_CTX* openSSL::m_ctx = NULL; openSSL::openSSL() { } openSSL::~openSSL() { /* 关闭 SSL 连接 */ if (m_ssl) { SSL_shutdown(m_ssl); /* 释放 SSL */ SSL_free(m_ssl); } m_fd = -1; close(m_fd); } int32_t openSSL::init_ssl_ctx() { /* SSL 库初始化 */ SSL_library_init(); /* 载入所有 SSL 算法 */ OpenSSL_add_all_algorithms(); /* 载入所有 SSL 错误消息 */ SSL_load_error_strings(); /* 以 TSL1.2 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */ m_ctx = SSL_CTX_new(TLSv1_2_server_method()); if (m_ctx == NULL) { ERR_print_errors_fp(stdout); return fail; } // 是否要求校验对方证书 此处不验证客户端身份所以为: SSL_VERIFY_NONE SSL_CTX_set_verify(m_ctx, SSL_VERIFY_NONE, NULL); // 加载CA的证书 if(!SSL_CTX_load_verify_locations(m_ctx, CA_CERT_FILE, NULL)) { printf("SSL_CTX_load_verify_locations error!\n"); ERR_print_errors_fp(stderr); return fail; } // 加载自己的证书 此证书用来发送给客户端。 证书里包含有公钥 if(SSL_CTX_use_certificate_file(m_ctx, SERVER_CERT_FILE, SSL_FILETYPE_PEM) <= 0) { printf("SSL_CTX_use_certificate_file error!\n"); ERR_print_errors_fp(stderr); return fail; } // 加载自己的私钥 私钥的作用是,ssl握手过程中,对客户端发送过来的随机 //消息进行加密,然后客户端再使用服务器的公钥进行解密,若解密后的原始消息跟 //客户端发送的消息一致,则认为此服务器是客户端想要链接的服务器 if(SSL_CTX_use_PrivateKey_file(m_ctx, SERVER_KEY_FILE, SSL_FILETYPE_PEM) <= 0) { printf("SSL_CTX_use_PrivateKey_file error!\n"); ERR_print_errors_fp(stderr); return fail; } // 判定私钥是否正确 if(!SSL_CTX_check_private_key(m_ctx)) { printf("SSL_CTX_check_private_key error!\n"); ERR_print_errors_fp(stderr); return fail; } return success; } int32_t openSSL::creat_ssl(int32_t fd) { /* 基于 ctx 产生一个新的 SSL */ m_ssl = SSL_new(m_ctx); if (m_ssl == NULL) { ERR_print_errors_fp(stderr); return fail; } m_fd = fd; /* 将连接用户的 socket 加入到 SSL */ SSL_set_fd(m_ssl, m_fd); /* 设置成服务器模式 */ SSL_set_accept_state(m_ssl); return success; } int32_t openSSL::accept_ssl_fd(int32_t fd) { /* 建立 SSL 连接. 握手在此完成 */ // 或者 SSL_do_handshake(ssl); if (SSL_accept(m_ssl) == -1) { int icode = -1; ERR_print_errors_fp(stderr); int iret = SSL_get_error(m_ssl, icode); printf("SSL_accept error! code = %d, iret = %d\n", icode, iret); return fail; } return success; } int32_t openSSL::ssl_recv(char *data, int32_t size) { if (NULL == data || size <= 0) { return -1; } int32_t recved = 0; while(true) { // int x = recv(m_fd,data,size, 0); recved = SSL_read(m_ssl, data, size); int left = SSL_pending(m_ssl); if (recved > 0) { int err = SSL_get_error(m_ssl,recved); return recved; } else { // TODO 小心陷入死循环 if (recved < 0 && SSL_get_error(m_ssl,recved) == SSL_ERROR_WANT_READ) { printf("got SSL_ERROR_WANT_READ\n"); continue; } // recved=0 || no want read return recved; } } } int32_t openSSL::ssl_send(char *data, int32_t size) { if (NULL == data || size <= 0) { return -1; } int32_t remainded = size; int32_t sended = 0; char* pszTmp = data; while(remainded > 0) { //TODO:检查此处的处理逻辑,是否会造成由于单个连接而拖累整个服务 sended = SSL_write(m_ssl, data, (size_t)remainded); if (sended > 0) { pszTmp += sended; remainded -= sended; } else // sended <= 0 { //TODO if (errno != EINTR || errno != EAGAIN) { printf(" send data(size:%d) error, msg = %s\n", remainded, strerror(errno)); break; } } } return (size - remainded); } void openSSL::ShowCerts() { X509 *cert; char *line; cert = SSL_get_peer_certificate(m_ssl); if (cert != NULL) { printf("数字证书信息:\n"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("证书: %s\n", line); free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("颁发者: %s\n", line); free(line); X509_free(cert); } else printf("无证书信息!\n"); } // ---- rsa非对称加解密 ---- // #define KEY_LENGTH 2048 // 密钥长度 #define PUB_KEY_FILE "pubkey.pem" // 公钥路径 // 函数方法生成密钥对 int32_t generateRSAKey(std::string strKey[2]) { // 公私密钥对 size_t pri_len; size_t pub_len; char *pri_key = NULL; char *pub_key = NULL; // 生成密钥对 RSA *keypair = RSA_generate_key(KEY_LENGTH, RSA_3, NULL, NULL); BIO *pri = BIO_new(BIO_s_mem()); BIO *pub = BIO_new(BIO_s_mem()); PEM_write_bio_RSAPrivateKey(pri, keypair, NULL, NULL, 0, NULL, NULL); PEM_write_bio_RSAPublicKey(pub, keypair); // 获取长度 pri_len = BIO_pending(pri); pub_len = BIO_pending(pub); // 密钥对读取到字符串 pri_key = (char *)malloc(pri_len + 1); pub_key = (char *)malloc(pub_len + 1); BIO_read(pri, pri_key, pri_len); BIO_read(pub, pub_key, pub_len); pri_key[pri_len] = '\0'; pub_key[pub_len] = '\0'; // 存储密钥对 strKey[0] = pub_key; strKey[1] = pri_key; // 存储到磁盘(这种方式存储的是begin rsa public key/ begin rsa private key开头的) FILE *pubFile = fopen(PUB_KEY_FILE, "w"); if (pubFile == NULL) { return fail; } fputs(pub_key, pubFile); fclose(pubFile); FILE *priFile = fopen(SERVER_KEY_FILE, "w"); if (priFile == NULL) { return fail; } fputs(pri_key, priFile); fclose(priFile); // 内存释放 RSA_free(keypair); BIO_free_all(pub); BIO_free_all(pri); free(pri_key); free(pub_key); } void openSSL::shutdown_ssl() { if (m_ctx) SSL_CTX_free(m_ctx); }

我们服务器用的是LT模式,用ET模式的ssl_recv函数和ssl_send可能会有bug。 SSLMgr类:

// // Created by jamesjiang on 2018/5/16. // #ifndef GAME_CONNECTOR_SSLMGR_H #define GAME_CONNECTOR_SSLMGR_H #include <stdlib.h> #include <stdint.h> #include <map> #include "Common/openSSL.h" namespace GameJayo { namespace Server { class SSLMgr { public: SSLMgr() { } ~SSLMgr() { clear(); } openSSL* get_ssl(int32_t fd) { if (m_clientSSL.count(fd)) { return m_clientSSL.at(fd); } return NULL; } void del_ssl(int32_t fd) { if (m_clientSSL.count(fd)) { delete m_clientSSL.at(fd); m_clientSSL.erase(fd); } } int32_t add_ssl(int32_t fd) { del_ssl(fd); openSSL *ssl = new openSSL(); if (fail == ssl->creat_ssl(fd)) { return fail; } m_clientSSL[fd] = ssl; return success; } int32_t accept_fd(int32_t fd) { openSSL *ssl = get_ssl(fd); if (ssl == NULL) { return fail; } if (fail == ssl->accept_ssl_fd(fd)) { return fail; } return success; } void clear() { for(auto &it : m_clientSSL) { delete it.second; } m_clientSSL.clear(); } private: std::map<int32_t, openSSL*> m_clientSSL; }; } } #endif //GAME_CONNECTOR_SSLMGR_H
转载请注明原文地址: https://www.6miu.com/read-2629041.html

最新回复(0)