首页 > 编程语言 >muduo实现ssl层的程序设计

muduo实现ssl层的程序设计

时间:2024-12-02 16:34:17浏览次数:5  
标签:muduo 函数 Helper ssl void ret SSL 程序设计

muduo的使用

muduo网络库内部分装了reactor和epoll以及socket,我们不需要知道其底层的内部封装;每次发生连接后都会调用连接onConnection的回调函数来处理连接。
每次当数据到达时都会调用onmessagecallback回调函数来执行数据的处理。

muduo增加ssl层

根据上一节,我们可以设计一个ssl_helper类,当发生连接时,就会调用ssl_helper的ssl握手的相关函数来执行ssl握手;
当数据到达时,我们就调用ssl_helper类的安全接收数据的函数即可;发送数据时也按照这样的步骤;客户端也是一样的。
处理接收数据时,首先会触发muduo的数据回调函数onmessage

ssl层处理连接的过程

  1. muduo的tcpserver中的连接回调函数绑定了sslserver的函数onServerConnection回调函数,当TCP连接完成时或连接中断时,就会执行这个回调函数,这个回调函数的主要步骤是:
    1:设置连接非延迟;
    2:创建SSL_Helper对象,并设置角色为server;
    3:需要将TcpConnectionPtr和SSL_Helper对象放入map中,这么做的目的是为了根据连接的指针,找到对应的ssl,因为数据发送到服务器时会触发onMessage回调函数,而这个回调函
    数的第一个参数就是TcpConnectionPtr,因此我们需要根据这个指针,传递给对应的SSL_Helper对象进行处理
    4:设置SSL层解密完数据后,处理数据的函数,即绑定处理数据的回调函数;
    5:调用SSL_Helper类的onSSLConnect函数来处理TCP连接后的SSL握手过程;
    6:如果是连接断开的,则需要将映射关系删除;
  2. SSL_Helper处理onSSLConnect的过程:
    1:需要判断连接是否已建立,如果不是则退出
    2:需要判断这个SSL_Helper的类型是什么,如果是server就执行do_ssl_accept()函数,否则执行do_ssl_connect()函数;
  3. do_ssl_accept()的执行过程:
    1:初始化openssl及ssl(调用四个函数,网上可以搜索);
    2:创建ssl上下文,并设置服务器不验证对端的公钥证书;
    3:创建ssl指针对象;
    4:服务端需要设置SSL证书;
    5:创建BIO的接收缓冲区,并和ssl对象进行绑定,这么做的目的是为了后续当数据到来时ssl,知道从哪个bio缓冲区去取数据或写数据。(2-5都写在一个函数中)
    6:调用SSL_set_accept_state(m_Ssl)表示已经准备好进行ssl握手了,SSL_set_accept_state(m_Ssl)是一个宏;
    7:调用SSLProcessingAccept()来进行处理,本质是ssl_read,当ssl_read发现握手还没完成时,就会自动触发握手;

SSL层接收数据

  1. 触发muduo网络库的数据接收回调函数onMessage,这个函数内部会调用ssl层的onSSLMessage函数,并将数据传递给ssl层;
    onSSLMessage函数:
    1:调用SSLProcessingRecv函数来执行,这个函数内部主要是讲数据写入到bio内存缓冲区中,然后ssl_read就会从这个bio中解密并读取数据;
    2:调用SSLReceiveData()函数就来处理数据,这个函数内部回调用绑定的服务器的特定的数据处理的回调函数,即1.4绑定的函数;

SSL发送数据

SSL发送数据首先调用SSLSendData函数,这个函数内部使用ssl_write将数据写入到BIO内存中,然后再调用SSLProcessingSend()函数;
SSLProcessingSend()函数,内部主要是用于从bio中读取数据到指定的缓冲区中,然后再调用muduo的TcpConnectionPtr的send方法发送数据出去;

BIO的创建方式

BIO_new(BIO_s_mem()) 和BIO_new_mem_buf的区别

  1. BIO_new(BIO_s_mem())

    • 用途:创建一个新的 BIO 内存缓冲区。
    • 特点:这个 BIO 内存缓冲区可以存放数据,大小是动态变化的,可以根据需要增加或减少。
    • 使用场景:适用于临时数据存储、测试和调试等需要动态处理内存数据的情况。
  2. BIO_new_mem_buf

    • 用途:基于已存在的内存缓冲区创建一个 BIO 内存。
    • 特点:这个已存在的内存缓冲区作为 BIO 的数据源,因此 BIO 的大小是固定的,不会动态调整。
    • 使用场景:适用于读取或处理已经存在的内存数据,例如加载内存中的证书或密钥。

SSL_ReceiveData函数

SSL_ReceiveData函数内部是调用的绑定的可调用对象的函数,这个可调用对象的函数是通过set_receive_callback进行设置的,而这个可调用对象就是服务器或客户端
进行数据处理的具体的逻辑函数。例如服务端数据处理的函数

SSL握手注意事项

SSL握手是依赖于底层通信的实现的,我们需要实现SSL_processing_sendSSL_processing_Recv函数来处理基于muduo网络库的ssl层的数据通信服务,
即当SSL层的握手数据到达时,也是会触发Muduo网络库的onMessage的回调函数的,然后我们就需要通过这个函数来调用基于ssl层的数据接收和发送函数。
SSL_processing_send函数,本质就是SSL_write将数据加密后填入到bio缓冲区中,然后我们需要从这个缓冲区中读取数据,并发送出去;
SSL_processing_Recv函数,本质是将加密的数据写入到bio缓冲区中,然后就会使用ssl_read解密数据并填写到解密数据的缓冲区中,然后再通过SSL_ReceiveData()函数调用绑定的数据处理函数;而如果握手还未完成,则ssl_read会自动进行握手;

SSL层SSL_Helper实现

SSL_Helper.h

/**
  ******************************************************************************
  * @file           : SSL_Helper.h
  * @author         : sally
  * @brief          : None
  * @attention      : None
  * @date           : 24-12-1
  ******************************************************************************
  */


#ifndef SSL_HELPER_H
#define SSL_HELPER_H
#include <muduo/net/TcpConnection.h>
#include <muduo/net/TcpServer.h>
#include <muduo/base/Timestamp.h>
#include <muduo/base/Logging.h>
#include <openssl/ssl.h>
#include <iostream>

//muduo产生每个连接时,就会调用连接的回调函数,我们需要设置连接的回调函数
extern const char * ca_cert_key_pem;
extern const char * server_cert_key_pem;

enum SSL_OPERATION_TYPE
{
    RECV = 0,
    SEND = 1
};

enum SSL_TYPE
{
    SSL_SERVER = 0,
    SSL_CLIENT = 1
};

//这个类封装了SSL握手过程,以及这个层需要封装接收数据后的实际处理的回调函数,但是这个回调函数是通过设置的方式绑定其它的类的函数的。
class SSL_Helper
{
public:
    SSL_Helper(const muduo::net::TcpConnectionPtr &conn);
    ~SSL_Helper();

    //写一个连接建立后muduo网络库会直接调用ssl层的连接函数
    void onSSLConnection(const muduo::net::TcpConnectionPtr &conn);
    void setSSLType(SSL_TYPE type)
    {
        m_sslType = type;
    }

    void onSSLMessage(const muduo::net::TcpConnectionPtr &conn,muduo::net::Buffer* buff,muduo::Timestamp time);
    void set_connected_callback(std::function<void()> &&func)
    {
        m_SSL_connected_callback_ = func;
    }

    void set_receive_callback(std::function<int(SSL_Helper*,unsigned char *,size_t)> &&func)
    {
        m_SSL_receive_callback_ = func;
    }

    void set_close_callback(std::function<void()> &&func)
    {
        m_SSL_closed_callback_ = func;
    }

private:
    void init_ssl();
    //创建服务端的ssl上下文函数
    void createServerSSLContext();

    //创建客户端的ssl上下文函数
    void createClientSSLContext();
    //还需要设置证书
    void setSSLCertificate();

    void close_session();

    int do_ssl_accept();
    int do_ssl_connect();

    //这个函数就是处理ssl握手的
    void SSL_processing_accept();  //这里是服务器进行ssl握手的函数
    void SSL_processing_connect();  //这是客户端的
    void SSL_processing_send();     //这是处理数据发送的
    void SSL_processing_Recv(const char * RecvBuffer,size_t BytesSizeRecieved);  //这是处理数据接收的

    void ssl_connected() const;

    void SSL_ReceiveData();
    bool IsSSLError(int ssl_error);

private:
    const muduo::net::TcpConnectionPtr m_connection_; //这里表示一个已经连接的tcp
    //发送和接收数据的缓冲区
    BIO *m_bio[2];  //这里表示的是一个0接收的缓冲区,一个发送的缓冲区1
    SSL_CTX *m_ssl_ctx_;
    SSL * m_ssl_;

    bool m_handshaked;

    //存放数据的缓冲区
    std::vector<unsigned char> m_EncryptSendData;   //这是存放将要发送的数据
    std::vector<unsigned char> m_decryptRecvData;   //这是存放接收后解密后的数据
    int m_SendSize;
    SSL_TYPE m_sslType;

    unsigned long long  m_BytesSizeRecieved;
    unsigned long long  m_TotalRecived;
    unsigned long long  m_CurrRecived;


    //ssl连接建立完成后的回调函数
    std::function<void()> m_SSL_connected_callback_;
    //处理数据的回调函数,这个函数通常绑定的是server类的处理函数
    std::function<int(SSL_Helper*,unsigned char *,size_t)> m_SSL_receive_callback_;
    std::function<void()> m_SSL_closed_callback_;
};


#endif //SSL_HELPER_H

SSL_Helper.cpp

/**
  ******************************************************************************
  * @file           : SSL_Helper.cpp
  * @author         : sally
  * @brief          : None
  * @attention      : None
  * @date           : 24-12-1
  ******************************************************************************
  */


#include "SSL_Helper.h"

SSL_Helper::SSL_Helper(const muduo::net::TcpConnectionPtr& conn)
    : m_connection_(conn),
      m_CurrRecived(0),
      m_BytesSizeRecieved(0),
      m_TotalRecived(0),
      m_handshaked(false)
{
    m_EncryptSendData.resize(1024 * 10);
    m_decryptRecvData.resize(1024 * 10);
    init_ssl();
}

SSL_Helper::~SSL_Helper()
{
}

void SSL_Helper::onSSLConnection(const muduo::net::TcpConnectionPtr& conn)
{
    if (conn->connected())
    {
        if (m_sslType == SSL_SERVER)
            do_ssl_accept();
        else
            do_ssl_connect();
    }
    else
        LOG_WARN << "connect close";
}

void SSL_Helper::onSSLMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buff, muduo::Timestamp time)
{
    std::cout << "receive data,size: " << buff->readableBytes() << std::endl;

    auto datalen = buff->readableBytes();
    m_BytesSizeRecieved += datalen;
    SSL_processing_Recv(buff->peek(),datalen); //处理数据
    buff->retrieveAll(); //处理完毕后重置缓冲区
}

/**
 * 这个函数的作用就是初始化openssl的
 */
void SSL_Helper::init_ssl()
{
    SSL_load_error_strings();
    SSL_library_init();
    OpenSSL_add_all_algorithms();
}

/**
 * 这个函数的主要作用就是初始化ssl的上下文么ssl句柄的
 */
void SSL_Helper::createServerSSLContext()
{
    m_ssl_ctx_ = SSL_CTX_new(SSLv23_method());
    //服务端设置不检查客户端的证书
    SSL_CTX_set_verify(m_ssl_ctx_,SSL_VERIFY_NONE, nullptr);

    //将ssl和证书关联起来
    setSSLCertificate();
    //创建ssl句柄
    m_ssl_ = SSL_new(m_ssl_ctx_);

    //创建bio内存
    m_bio[RECV] = BIO_new(BIO_s_mem());
    m_bio[SEND] = BIO_new(BIO_s_mem());

    SSL_set_bio(m_ssl_, m_bio[RECV], m_bio[SEND]); //这是将ssl句柄和bio绑定在一起
}

void SSL_Helper::createClientSSLContext()
{
    m_ssl_ctx_ = SSL_CTX_new(SSLv23_method());
    //客户端需要设置验证服务端的公钥证书
    // SSL_CTX_set_verify(m_ssl_ctx_,SSL_VERIFY_PEER,nullptr);

    //客户端没有证书就不需要将证书和ssl上下文绑定起来
    //初始化ssl句柄
    m_ssl_ = SSL_new(m_ssl_ctx_);

    m_bio[RECV] = BIO_new(BIO_s_mem());
    m_bio[SEND] = BIO_new(BIO_s_mem());
    SSL_set_bio(m_ssl_, m_bio[RECV], m_bio[SEND]);
}

void SSL_Helper::setSSLCertificate()
{
    int length = strlen(server_cert_key_pem);
    BIO* bio_cert = BIO_new_mem_buf((void*)server_cert_key_pem, length);
    X509* cert = PEM_read_bio_X509(bio_cert, nullptr, nullptr, nullptr);

    //获取私钥,从server_cert_key_pem中
    EVP_PKEY* prikey = PEM_read_bio_PrivateKey(bio_cert, nullptr, nullptr, nullptr);
    int ret = SSL_CTX_use_certificate(m_ssl_ctx_, cert);
    if (ret != 1)
        close_session();

    ret = SSL_CTX_use_PrivateKey(m_ssl_ctx_, prikey);
    if (ret != 1)
        close_session();
    X509_free(cert);
    EVP_PKEY_free(prikey);
    BIO_free(bio_cert);
}

void SSL_Helper::close_session()
{
    m_connection_->forceClose();
    printf("close_session()\n");
}

int SSL_Helper::do_ssl_accept()
{
    //调用函数创建ssl上下文
    createServerSSLContext();
    SSL_set_accept_state(m_ssl_);
    //处理握手的函数
    SSL_processing_accept();
    return 1;
}

int SSL_Helper::do_ssl_connect()
{
    createClientSSLContext();
    SSL_set_connect_state(m_ssl_);
    SSL_processing_connect();
    return 1;
}

void SSL_Helper::SSL_processing_accept()
{
    int ret;
    int ssl_error;
    int dwBytesSizeRecieved = 0;

    do
    {
        ret = SSL_read(m_ssl_, m_decryptRecvData.data(), m_decryptRecvData.size());
        ssl_error = SSL_get_error(m_ssl_, ret);

        if (IsSSLError(ssl_error))
            close_session();
        if (ret > 0)
            dwBytesSizeRecieved += ret;
    }
    while (ret > 0);

    //判断ssl握手是否已经完成,如果完成了则是调用的结束数据的函数
    if (SSL_is_init_finished(m_ssl_))
    {
        m_handshaked = true;
        SSL_ReceiveData();
    }
    //处理完毕就需要调用发送的函数
    SSL_processing_send();
}

void SSL_Helper::SSL_processing_connect()
{
    int ret,ssl_error;
    int bytesSizeRecived = 0;
    do
    {
        ret = SSL_read(m_ssl_,m_decryptRecvData.data(),m_decryptRecvData.size());
        ssl_error = SSL_get_error(m_ssl_,ret);

        if(IsSSLError(ssl_error))
            close_session();
        if(ret >0)
            bytesSizeRecived += ret;
    }
    while (ret >0);

    if (SSL_is_init_finished(m_ssl_))
    {
        m_handshaked = true;
        SSL_ReceiveData();//receive data from ssl sockets
    }


    SSL_processing_send();
}

/**
 * 这个函数主要就是用来将数据发送出去的,在调用这个函数之前,SSL_Write会将数据写入到BIO[SEND]中,然后我们需要将
 * 数据从Bio中读取到加密数据的缓冲区中
 */
void SSL_Helper::SSL_processing_send()
{
    int ret;
    int ssl_error;
    while (BIO_pending(m_bio[SEND]))
    {
        ret = BIO_read(m_bio[SEND], m_EncryptSendData.data(), m_EncryptSendData.size());

        if (ret > 0)
            m_connection_->send(m_EncryptSendData.data(), ret);
        else
        {
            ssl_error = SSL_get_error(m_ssl_, ret);

            if (IsSSLError(ssl_error))
                close_session();
        }
    }
}

void SSL_Helper::SSL_processing_Recv(const char* RecvBuffer, size_t BytesSizeRecieved)
{
    int ret;
    int ssl_error;

    if (m_BytesSizeRecieved > 0)
    {
        //将数据写入到bio缓冲区中,以便使用ssl_read函数从bio缓冲区中读取并解密
        ret = BIO_write(m_bio[RECV], RecvBuffer, BytesSizeRecieved);

        if (ret > 0)
        {
            int intRet = ret;
            if (intRet > m_BytesSizeRecieved)
                close_session();

            m_BytesSizeRecieved -= intRet;
        }
        else
        {
            ssl_error = SSL_get_error(m_ssl_, ret);
            if (IsSSLError(ssl_error))
                close_session();
        }
    }

    do
    {
        assert(m_decryptRecvData.size() - m_CurrRecived >0);
        ret = SSL_read(m_ssl_, m_decryptRecvData.data() + m_CurrRecived, m_decryptRecvData.size() - m_CurrRecived);

        if (ret > 0)
        {
            m_CurrRecived += ret;
            m_TotalRecived += ret;
            if (m_handshaked)
            {
                SSL_ReceiveData();
            }
        }
        else
        {
            ssl_error = SSL_get_error(m_ssl_, ret);
            if (IsSSLError(ssl_error))
                close_session();
        }
    }
    while (ret > 0);

    if (!m_handshaked)
    {
        if (SSL_is_init_finished(m_ssl_))
        {
            m_handshaked = true;
            ssl_connected(); //这是连接完成后的回调函数
        }
    }

    SSL_processing_send();
}

void SSL_Helper::ssl_connected() const
{
    if(m_SSL_connected_callback_)
        m_SSL_connected_callback_();
}

/**
 * 这里面封装的调用绑定的数据处理的逻辑函数
 */
void SSL_Helper::SSL_ReceiveData()
{
    if(m_SSL_receive_callback_)
        m_SSL_receive_callback_(this,m_decryptRecvData.data(),m_decryptRecvData.size());

    //处理完成后,清空数据缓冲区
    m_decryptRecvData.clear();
}

bool SSL_Helper::IsSSLError(int ssl_error)
{
    switch (ssl_error)
    {
    case SSL_ERROR_NONE:
    case SSL_ERROR_WANT_READ:
    case SSL_ERROR_WANT_WRITE:
    case SSL_ERROR_WANT_CONNECT:
    case SSL_ERROR_WANT_ACCEPT:
        return false;

    default: return true;
    }
}

标签:muduo,函数,Helper,ssl,void,ret,SSL,程序设计
From: https://www.cnblogs.com/wuhaiqiong/p/18580662

相关文章

  • JavaScript操作DOM元素的classList
    在JavaScript中,classList是一个DOM元素属性,它提供了一组方法来添加、移除和切换元素的类名。classList属性返回一个DOMTokenList集合,表示元素的类名。这个集合提供了几个非常有用的方法,我们可以方便地对元素的类名进行操作,包括添加、移除、切换类名等。1,添加类名add(class1......
  • PAT-程序设计(基础级)数字命理计算器
    解题思路:在每一轮字符串输入中,使用字符数组存储每一行字符串,并从字符串首进行遍历,对每一位字母进行数字转换,并对数字求和,求完总和之后的数字再进行条件判断,若不为题目要求的命理数,就对求和总数进行各位数字相加,直到出现命理数,输出即可。代码实现如下:#include<stdio.h>#inc......
  • # 学期(如2024-2025-1) 学号(如:20241402) 《计算机基础与程序设计》第11周学习总结
    学期(如2024-2025-1)学号(如:20241402)《计算机基础与程序设计》第11周学习总结作业信息这个作业属于哪个课程<班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标<写上......
  • # 学期(如2024-2025-1) 学号(如:20241325) 《计算机基础与程序设计》第十i周学习总结
    作业信息这个作业属于哪个课程<班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2024-2025-1计算机基础与程序设计第十周作业)这个作业的目标<信息系统数据库与SQL人工智能与专家系统人工神经网络模拟与离散事......
  • 2024-2025-1 20241312 《计算机基础与程序设计》第10周学习总结
    作业信息这个作业属于哪个课程<班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2024-2025-1计算机基础与程序设计第十周作业)这个作业的目标信息系统数据库与SQL人工智能与专家系统人工神经网络模拟与离散事......
  • # 学期(2024-2025-1) 学号(20241420) 《计算机基础与程序设计》第10周学习总结
    学期(2024-2025-1)学号(20241420)《计算机基础与程序设计》第10周学习总结作业信息这个作业属于哪个课程<班级的链接>(2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标<计算机科学......
  • 2024-2025-1 20241406 刘书含《计算机基础与程序设计》第十周学习总结
    一·教材内容学习《计算机科学概论》第14章1.模拟与离散事件模拟:使用计算机模型来模拟现实世界的过程或系统。离散事件模拟:详细阐述离散事件模拟的原理和方法,包括如何定义事件、时间推进、状态更新等关键步骤,关注于模拟随时间发生的离散事件,如排队系统中顾客的到达和服务。2.......
  • 2024-2025-1 20231309《计算机基础与程序设计》第九周助教总结
    课程答疑现阶段,大家都主要在学习C语言编程,时不时会遇到程序不会写,报错不会改的问题。而出现此类问题的主要原因包括:算法不熟悉,如写不出素数的判断等解决方案:多熟悉学习常见的简单算法包括比大小,判断素数等语法不熟悉,如赋值和判断语句等解决方案:多熟悉课本和PPT相关内......
  • 《计算机基础与程序设计》第十周学习总结
    学期(2024-2025-1)学号(20241412)《计算机基础与程序设计》第十周学习总结作业信息这个作业属于哪个课程<班级的链接>2024-2025-1-计算机基础与程序设计这个作业要求在哪里<作业要求的链接>2024-2025-1计算机基础与程序设计第十周作业这个作业的目标信息系统数据......
  • 2024-2025-1 20241329 《计算机基础与程序设计》第十周学习总结
    作业信息作业归属课程:2024-2025-1-计算机基础与程序设计作业要求:2024-2025-1计算机基础与程序设计第十周作业作业目标:信息系统、数据库与SQL、人工智能与专家系统、人工神经网络、模拟与离散事件、排队系统、天气与地震模型、图形图像作业正文:2024-2025-120241329《计算机基......