IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> boost asio 写一个TLS 链接的client 和server -> 正文阅读

[C++知识库]boost asio 写一个TLS 链接的client 和server

最近工作中需要写TLS的 client, 同时需要一个server 测试 ut, 从学习TLS链接 和写代码花了一点时间 想记录下过程中遇到的问题

首先需要学习下什么是TLS链接,TLS链接就是再TCP的基础上增加双方的证书验证

从代码结果上看就是TCP 链接之后加一个握手 handshake, 这个handshake 用来验证client 和server的证书验证

那什么是证书呢 我附上一些我之前学习的一篇文章,很好

https://blog.csdn.net/ustccw/article/details/76691248?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

证书验证我的理解是这样的,

首先是公钥 和私钥,公钥加密的东西可以用私钥解密,所以一般是把自己的公钥分发给别人,这样别人就可以解密你用私钥加密的东西?

理解这个之后,我们来理解一下证书,首先有个CA 证书授权中心权威机构,他把自己的公钥分给大家就是Ca证书, 然后client 和server都有各自的公钥和私钥,client 和server都会把自己的公钥和一些各自的信息制作成req文件发给CA机构,机构相信你是正经的以后就会用自己的私钥给你们的req加密(签字)生成client.cert server.cert,这样client 和server就有各自的证书.这样以后就可以双向证书验证

比如client这边又Ca证书,server发过来自己的证书,client用Ca证书(公钥)把server的证书进行解密,从而可以得到server的公钥,同理,server也可以得到client的公钥。这样就算是证书验证了

当然 其中还有其他的过程什么cipher 随机数啥的?可以看附上的链接

这里有一点需要解释就是EE证书

EE证书就是末证书,他需要串成一个证书链,就是要找到他上一级的证书签了他一直找到根证书,根证书就是自签证书,自己签自己的证书

贴一个证书解析的link

https://www.ssleye.com/cer_check.html

证书验证讲解清楚以后 TLS链接就没什么了, 下面贴一下我基于boost asio的TLS链接代码

// client.cpp
#include <sstream>
#include <memory>
#include <limits>
#include <string>
#include <algorithm>
#include <vector>
#include "AsyncHttpClient.hpp"


namespace
{
namespace
{
// 这里传进来的boost::asio::ssl::context 需要之前进行一些证书加载的操作 
uint32_t AsyncHttpClient::httpClientId_ = 0;
AsyncHttpClient::AsyncHttpClient(
    boost::asio::io_service &io_service,
    boost::asio::ssl::context& context)
    : sslSocket_(io_service, context)
    , sasMessageTimer_(new boost::asio::steady_timer(io_service))
    , asyncConnectTimer_(new boost::asio::steady_timer(io_service))
{
}

AsyncHttpClient::~AsyncHttpClient()
{
    std::cout << info << "Destructoring AsyncHttpClient";
    close();
}

void AsyncHttpClient::send(const std::string& uri, const std::string& payload,
    std::function<void(Response)> responseCallback)
{
    buffer_.consume(buffer_.size());
    responseCallback_ = responseCallback;
    boost::beast::http::request<boost::beast::http::string_body> request;  // beast来写http消息
    request_.set(boost::beast::http::field::connection, "Keep-Alive");
    request_.target(uri);
    request_.method(boost::beast::http::verb::post);
    request_.body() = payload;
    request_.prepare_payload();

    auto endPoints = createEndpoint(“127.0.0.1”, 1234);  // 这里原本需要DNS解析得到endporint, 我简化了

    startConnect(endPoints);
}

boost::asio::ip::tcp::endpoint createEndpoint(const std::string& hostName,
        unsigned int serverPort)
{
    boost::system::error_code ec;
    boost::asio::ip::address ipAddr = boost::asio::ip::address::from_string(hostName, ec);
    boost::asio::ip::tcp::endpoint ep(ipAddr, serverPort);
    return ep;
}

void AsyncHttpClient::startMessageTimer()
{
    auto self = shared_from_this();
    sasMessageTimer_->expires_from_now(std::chrono::seconds(5));
    sasMessageTimer_->async_wait([self](boost::system::error_code ec)
        {
            if (ec && ec == boost::asio::error::operation_aborted)
            {
                std::cout << debug << "steady_timer was canceled";
                return;
            }

            self->cancelMessageTimer();
            std::cout << warning << "Timeout on and losing connection";
            // need add exception handle
        });
}

void AsyncHttpClient::cancelMessageTimer()
{
    logger_ << info << __FUNCTION__;
    if (sasMessageTimer_)
    {
        sasMessageTimer_->cancel();
    }
}

void AsyncHttpClient::startConnectTimer()
{
    std::cout << info << "start async Connect Timer with time 5s";
    auto self = shared_from_this();
    asyncConnectTimer_->expires_from_now(std::chrono::seconds(5));
    asyncConnectTimer_->async_wait([self](boost::system::error_code ec)
        {
            if (ec && ec == boost::asio::error::operation_aborted)
            {
                std::cout << info << "asyncConnectTimer_ was cancelled.";
                return;
            }

            self->dnsCachePtr_->clear(self->connectCtx_.hostname, self->connectCtx_.servicePort);
            self->cancelConnectTimer();
            self->sslSocket_.next_layer().cancel();

            std::cout << warning
                << "Tcp connection failed due to asyncConnectTimer timeout after 5 seconds or error occur";
            // need add exception handle
        });
}

void AsyncHttpClient::cancelConnectTimer()
{
    logger_ << info << __FUNCTION__;
    if (asyncConnectTimer_)
    {
        asyncConnectTimer_->cancel();
    }
}

void AsyncHttpClient::startConnect(std::vector<boost::asio::ip::tcp::endpoint>& results)
{
    std::cout << info << "Start to connect some endpoints, size: " << results.size();

    startConnectTimer();
    boost::asio::async_connect(
        sslSocket_.next_layer(),
        results,
        std::bind(
            &AsyncHttpClient::handleTcpConnect,
            shared_from_this(),
            std::placeholders::_1,
            std::placeholders::_2));
}

void AsyncHttpClient::handleTcpConnect(const boost::system::error_code& ec,
    const boost::asio::ip::tcp::endpoint& endpoint)
{
    std::cout << debug << __FUNCTION__;
    cancelConnectTimer();
    if (!ec)
    {
        std::cout << info << "TCP Connection to " << endpoint << " successfully.";

        boost::asio::ip::tcp::no_delay option(true);
        boost::system::error_code errc;
        sslSocket_.lowest_layer().set_option(option, errc);
        if (errc)
        {
            std::cout<< debug << "error code: " << errc.value() << ": " << errc.message();
        }
        logger_ << info << "socket has local address: " << sslSocket_.next_layer().local_endpoint();
        handShake();
    }
    else
    {
        std::cout << warning << "Connection to " << endpoint << " failed "
                << "with Error: msg is: " << ec.message();
        // need add exception handle
    }
}
/*
void AsyncHttpClient::setSslContextSNI()
{
    std::cout << debug << __FUNCTION__;
    boost::system::error_code ec;
    boost::asio::ip::make_address(connectCtx_.hostname, ec);
    if (!ec)
    {
        std::cout << info << " does not set SNI for numeric IP host";
        return;
    }

    if (!SSL_set_tlsext_host_name(sslSocket_.native_handle(), connectCtx_.hostname.c_str()))
    {
        std::cout << warning << "cbsd set SNI failed";
    }
}
*/
void AsyncHttpClient::handShake()
{
    logger_ << info << __FUNCTION__;

    auto self = shared_from_this();
    sslSocket_.async_handshake(
        boost::asio::ssl::stream_base::client,
        [self](const boost::system::error_code& ec)
        {
            self->logger_ << info << "async_handshake callback.";
            if (ec)
            {
                std::cout << info
                    << "SSL handshak failed with: " << ec.value() << ", msg=" << ec.message()
                    << " closing socket.";
                self->close();
                // need add exception handle
                return;
            }
            else
            {
                std::cout << info << "async_handshake success!";
                self->sendHttpRequest();
            }
        });
}

void AsyncHttpClient::sendHttpRequest()
{
    std::cout << debug << __FUNCTION__;
    startMessageTimer();

    boost::asio::async_write(sslSocket_,
        boost::asio::buffer(request_),  // 这里用成员变量避免异步的局部变量crash,
        boost::bind(
                &AsyncHttpClient::waitForResponse,
                shared_from_this(),
                boost::asio::placeholders::error));
}

void AsyncHttpClient::waitForResponse(const boost::system::error_code& ec)
{
    std::cout << debug << "waitForResponse";
    cancelMessageTimer();
    if (ec)
    {
        std::cout << warning
                << "Writing: error code: " << ec.value() << ": " << ec.message();
        if (ec == boost::asio::error::eof)
        {
            std::cout << warning << "Connection reset by peer in write";
        }
        close();
        // need add exception
        return;
    }

    std::cout << debug << "start reading message";
    readResponse();
}

void AsyncHttpClient::readResponse()
{
    std::cout << debug << __FUNCTION__ << " read-timeout will be set";
    startMessageTimer();

    responseParser_.body_limit(std::numeric_limits<std::uint64_t>::max());
    boost::beast::http::async_read(sslSocket_,
        buffer_,
        responseParser_,
        std::bind(
            &AsyncHttpClient::handleResponse,
            shared_from_this(),      // do not use "this", because it will go out of scope and the socket will be closed
            std::placeholders::_1,
            std::placeholders::_2));
}

void AsyncHttpClient::handleResponse(const boost::system::error_code& ec, std::size_t bytesTransferred)
{
    std::cout << debug << "handleResponse";
    cancelMessageTimer();
    close();
    if (ec)
    {
        std::cout << debug << "Failed to read HTTP response"
                << ", error code ==>" << ec.value() << ": " << ec.message();
        // need add exception handle
        return;
    }

    int resultCode = responseParser_.get().result_int();
    std::cout << info << "Http status code: " << resultCode << " String body: " << responseParser_.get().body();
    // need add successful handle
}

void AsyncHttpClient::close()
{
    logger_ << info << __FUNCTION__;
    cancelMessageTimer();
    cancelConnectTimer();
    boost::system::error_code shutdownEc;
    sslSocket_.shutdown(shutdownEc);
    boost::system::error_code ecClose;
    sslSocket_.lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ecClose);
    boost::system::error_code eclinkClose;
    sslSocket_.lowest_layer().close(eclinkClose);
}

}  // namespace
}  // namespace


#include <sstream>
#include <memory>
#include <string>
#include <vector>
#include <openssl/ssl.h>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/core.hpp>


namespace
{
namespace
{

class AsyncHttpClient : public std::enable_shared_from_this<AsyncHttpClient>
{
public:
    AsyncHttpClient(
        boost::asio::io_service &io_service,
        boost::asio::ssl::context& context);

    ~AsyncHttpClient();

    void send(const std::string& uri, const std::string& payload,
        std::function<void(Response)> responseCallback);
private:

    void startMessageTimer();
    void cancelMessageTimer();
    void startConnectTimer();
    void cancelConnectTimer();
    void startConnect(std::vector<boost::asio::ip::tcp::endpoint>& results);
    void handleTcpConnect(const boost::system::error_code& ec, const boost::asio::ip::tcp::endpoint& endpoint);

    void handShake();
    void sendHttpRequest();
    void waitForResponse(const boost::system::error_code& ec);
    void readResponse();
    void handleResponse(const boost::system::error_code& ec, std::size_t bytesTransferred);
    void close();

    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket_;
    std::unique_ptr<boost::asio::steady_timer> sasMessageTimer_;
    std::unique_ptr<boost::asio::steady_timer> asyncConnectTimer_;
    std::string request_;
    boost::beast::http::response_parser<boost::beast::http::string_body> responseParser_;
    boost::beast::flat_buffer buffer_;
    static uint32_t httpClientId_;
};

}  // namespace
}  // namespace

// 一个简单的UTcase,通过创建一个线程来模拟server端,通过端口 0 产生一个随机端口

ssl::context createSslcontext(const std::string caCert)
{
    ssl::context ctx{ssl::context::tlsv12};
    std::string tls12CipherSuite = "";
    SSL_CTX_set_cipher_list(ctx.native_handle(), tls12CipherSuite.c_str());// 设置cipher
       boost::system::error_code ec;
    ctx.add_certificate_authority(
    boost::asio::buffer(caCert.data(), caCert.size()), ec);  // 加入Ca证书
    ctx.set_verify_mode(ssl::verify_peer);  // 验证对方
    return ctx;
 }
void startHttpServer(unsigned short& serverPort, const std::string& response)
{
    httpserver_->start(serverPort, response);
}

TEST_F(AsyncHttpClientTest, sendRequestAndReceiveSuccessfully)
{
    Response callbackResponse;
    unsigned short serverPort = 0;
    auto responseCallback = [&](Response response)
    {
        callbackResponse = response;
    };

    std::thread t1(&AsyncHttpClientTest::startHttpServer, this, std::ref(serverPort), RESPONSE);
    sleep(1);
    auto ctx = createSslcontext(CACERT);
    httpclient_ = std::make_shared<AsyncHttpClient>(*io_, ctx);

    auto endPoint = createEndpoint("127.0.0.1", serverPort);
    std::vector<boost::asio::ip::tcp::endpoint> endpointVec{endPoint};

    std::string payload = "***";
    httpclient_->send("/v2.0/registration", payload, responseCallback);
    io_->run();
    t1.join();
}
// server.hpp

#include <stdio.h>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <boost/asio/ssl/context.hpp>

namespace ssl = boost::asio::ssl;
namespace
{
namespace 
{
class AsyncHttpServer
{
public:
    AsyncHttpServer();
    ~AsyncHttpServer();
    void start(unsigned short& serverPort, const std::string& response);
private:
    ssl::context createSslContext();
    int create_socket(int port, std::string addr);
    void init_openssl();
    void cleanup_openssl();

};

}  // namespace 
}  // namespace 

#endif


#include <string>
#include <iostream>
#include <unistd.h>
#include "AsyncHttpServer.hpp"

namespace 
{
namespace 
{
AsyncHttpServer::AsyncHttpServer()
{
    std::cout << info << "AsyncHttpServer construct";
}

AsyncHttpServer::~AsyncHttpServer()
{
}

void AsyncHttpServer::start(unsigned short& serverPort, const std::string& response)
{
    init_openssl();
    int sock = create_socket(0, "127.0.0.1");
    if (sock <= 0)
    {
        std::cout << info << "create_socket failed";
        return;
    }

    struct sockaddr_in sin;
    socklen_t len1 = sizeof(sin);
    if (getsockname(sock, (struct sockaddr *)&sin, &len1) == -1)
    {
        std::cout<< info << "Unable to get server port";
    }
    else
    {
        serverPort = ntohs(sin.sin_port);
        std::cout << info << "server port: " << serverPort;
    }

    struct sockaddr_in addr;
    uint len = sizeof(addr);
    const char* reply = response.c_str();
    int client = accept(sock, (struct sockaddr*)&addr, &len);
    if (client < 0)
    {
        std::cout << info << "Unable to accept";
        return;
    }

    auto ctx = createSslContext();
    std::cout << info << "accept success";
    SSL *ssl = SSL_new(ctx.native_handle());
    SSL_set_fd(ssl, client);
    logger_ << info << "ssl accept success";
    char buf[2048] = {0};
    int bytes;
    if (SSL_accept(ssl) <= 0)
    {
        std::cout << info << "ssl accept failed";
    }
    else
    {
        bytes = SSL_read(ssl, buf, sizeof(buf));
        buf[bytes] = '\0';
        std::cout << info << "read from CBSD" << bytes << "msg: " << buf;
        std::cout << info << "SSL_write << " << reply;
        SSL_write(ssl, reply, strlen(reply));
    }

    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(client);
    cleanup_openssl();
}

ssl::context AsyncHttpServer::createSslContext()
{
    ssl::context ctx{ssl::context::sslv23_server};
    std::string tls12CipherSuite = "AES128-GCM-SHA256:AES256-GCM-SHA384";
    SSL_CTX_set_cipher_list(ctx.native_handle(), tls12CipherSuite.c_str());
    boost::system::error_code ecCert;
    ctx.use_certificate_chain(boost::asio::buffer(CERT.data(), CERT.size()), ecCert);
    boost::system::error_code ecKey;
    ctx.use_private_key(boost::asio::buffer(PRIVATEKEY.data(), PRIVATEKEY.size()),
        boost::asio::ssl::context::pem, ecKey);
    ctx.set_verify_mode(ssl::verify_none);
    return ctx;
}

int AsyncHttpServer::create_socket(int port, std::string myaddr)
{
    int s;
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, myaddr.c_str(), &addr.sin_addr);

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0)
    {
        std::cout << info << "Unable to create socket";
        return -1;
    }

    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
        std::cout << info << "Unable to bind";
        return -1;
    }
    logger_ << info << "listening";
    if (listen(s, 1) < 0)
    {
        std::cout << info << "Unable to listen";
        return -1;
    }
    return s;
}

void AsyncHttpServer::init_openssl()
{
    SSL_load_error_strings();
    OpenSSL_add_ssl_algorithms();
}

void AsyncHttpServer::cleanup_openssl()
{
    EVP_cleanup();
}

}  // namespace 
}  // namespace 

这里再分享一下如何利用openssl里设置一些证书相关的参数

SslContext_Ptr SSLHandler::createSSLContext()
{
    logger_ << debug << "creating new ssl context with mode " << static_cast<int>(mode);
    auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23_client);
    setVerifyModeAndCallback(ctx);
    sslCtxSetOptions(ctx);  // 设置一下option
    setCiphers(ctx);
    setVerifyFile(ctx);
    setCertificateChainAndKey(ctx);
    setCRLs(ctx);

    return ctx;
}

ctx->set_verify_mode(SSL_VERIFY_PEER, ec);// 设置验证对方证书
ctx->set_verify_callback(&SSLHandler::certificateVerificationCallback, ec); // 设置证书验证回调  callback 是static的方法
ctx->set_options(boost::asio::ssl::context::default_workarounds
        | boost::asio::ssl::context::no_sslv2
        | boost::asio::ssl::context::no_sslv3
        | boost::asio::ssl::context::no_tlsv1
        | boost::asio::ssl::context::no_tlsv1_1
        | boost::asio::ssl::context::no_tlsv1_3
        | boost::asio::ssl::context::single_dh_use
        | SSL_OP_CIPHER_SERVER_PREFERENCE, ec);
SSL_CTX_set_cipher_list(ctx->native_handle(), ciphers.c_str()) != 1 // 设置cipher 密码套件

ctx->add_certificate_authority(boost::asio::buffer(trustAnchors.data(), trustAnchors.size()), ecWrite); // 设置一下ca 证书
ctx->use_certificate_chain(boost::asio::buffer(cert.data(), cert.size()), ec);// 设置证书链
ctx->use_private_key_file(privateKeyFile, boost::asio::ssl::context::pem, ec);// 设置私钥文件地址
最后需要设置一下crl 比较麻烦

这样就得到一个设置过整数的context

另外, 在学习boost io过程中,我觉得有以下特性

1. 调用io->run()以后会堵塞当前线程,这个当中的堵塞我觉的一种while的循环,当io当中没有task以后他就会跳出了,所以调用run方法太早会 导致什么也没做就结束了,之后放进去的任务会执行不了,这个时候可以用一个work 不让run方法跳出,没有任务一直等着任务井来 那就永远跳不出来了

2. 再多线称中会看到,哪个线程调用了run,回调函数就会到在哪个线程里掉,线程池就是多个线程调用了run 然后多个线程分配任务(回调函数),这时候会看到for循环线程掉run,一开始我以为会for完以后堵塞,其实循环堵塞

线程池可以参考这个连接 挺又意思的https://github.com/senlinzhan/code-for-blog/tree/master/boost_asio

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-13 17:16:52  更:2021-07-13 17:18:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 11:24:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码