You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Boost.asio+OpenSSL实现HTTPS代理握手失败问题求助

问题描述

我正在使用Boost/OpenSSL/VS2019学习Windows下的TCP Socket编程,编写了一个回显服务器示例,与自定义客户端测试时运行正常。但将Chrome或IE11的HTTPS代理设置为该服务器的IP和443端口后,握手环节总是失败,错误信息为:

handshake failed "https proxy request (SSL routines, ssl3_get_record) [asio.ssl:336130203]"

我使用OpenSSL生成了自签名证书,自定义客户端通过ssl_context.load_verify_file("cert.pem")验证证书后可正常通信,且已将该证书安装到IE和Chrome中,但浏览器连接代理时仍握手失败。

疑问

  1. 要让浏览器正常完成握手,自签名证书中的域名、IP等标识信息是否必须与代理服务器的实际信息一致?当前我使用的是与测试机器无关的虚拟证书,但自定义客户端可正常工作。
  2. 我想实现一个简单的HTTPS中继,目前HTTP代理已正常运行。为集成SSL,首先需要确保代理能接受浏览器的HTTPS代理请求并传输数据,是否浏览器的HTTPS代理请求有特殊要求,或是我的实现方式有误?

尝试过的操作

  • 使用OpenSSL生成自签名证书文件(cert.pem、key.pem、dh2048.pem)
  • 基于上述证书构建服务器和客户端,服务器监听443端口,同机/跨机客户端与服务器均可正常握手并通信
  • 将证书安装到IE和Chrome中
  • 浏览器通过HTTPS代理连接服务器,握手失败

预期与实际结果

  • 预期结果:浏览器与代理服务器可成功完成SSL握手
  • 实际结果:浏览器握手失败,报错信息如上

代码实现

class session
{
public:
    session(boost::asio::io_service& io_service,
        boost::asio::ssl::context& context)
        : socket_(io_service, context)
    {
    }

    ssl_socket::lowest_layer_type& socket()
    {
        return socket_.lowest_layer();
    }

    void start()
    {
        socket_.async_handshake(boost::asio::ssl::stream_base::server,
            boost::bind(&session::handle_handshake, this,
                boost::asio::placeholders::error));
    }

    void handle_handshake(const boost::system::error_code& error)
    {
        if (!error)
        {
            std::cout << "handshake good" << std::endl;

            socket_.async_read_some(boost::asio::buffer(data_, max_length),
                boost::bind(&session::handle_read, this,
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
        }
        else
        {
            std::cout << "handshake failed " + error.what() << std::endl;

            delete this;
        }
    }

    void handle_read(const boost::system::error_code& error,
        size_t bytes_transferred)
    {
        if (!error)
        {
            boost::asio::async_write(socket_,
                boost::asio::buffer(data_, bytes_transferred),
                boost::bind(&session::handle_write, this,
                    boost::asio::placeholders::error));
        }
        else
        {
            delete this;
        }
    }

    void handle_write(const boost::system::error_code& error)
    {
        if (!error)
        {
            socket_.async_read_some(boost::asio::buffer(data_, max_length),
                boost::bind(&session::handle_read, this,
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
        }
        else
        {
            delete this;
        }
    }

private:
    ssl_socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};

class server
{
 
public:
    server(boost::asio::io_service& io_service, unsigned short port)
        : io_service_(io_service),
        acceptor_(io_service,
            boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
        context_(boost::asio::ssl::context::sslv23)
    {
        context_.set_options(
            boost::asio::ssl::context::default_workarounds
            | boost::asio::ssl::context::no_sslv2
            | boost::asio::ssl::context::single_dh_use);
        context_.set_password_callback(boost::bind(&server::get_password, this));
        context_.use_certificate_chain_file("cert.pem");
        context_.use_private_key_file("key.pem", boost::asio::ssl::context::pem);
        context_.use_tmp_dh_file("dh2048.pem");

        start_accept();
    }

    std::string get_password() const
    {
        return "test";
    }

    void start_accept()
    {
        session* new_session = new session(io_service_, context_);
        acceptor_.async_accept(new_session->socket(),
            boost::bind(&server::handle_accept, this, new_session,
                boost::asio::placeholders::error));
    }

    void handle_accept(session* new_session,
        const boost::system::error_code& error)
    {
        if (!error)
        {
            std::cout << "accept good" << std::endl;

            new_session->start();
        }
        else
        {
            delete new_session;
        }

        start_accept();
    }

private:
    boost::asio::io_service& io_service_;
    boost::asio::ip::tcp::acceptor acceptor_;
    boost::asio::ssl::context context_;
};

解决方案

针对疑问1:证书标识信息的要求

浏览器对证书的校验比自定义客户端严格得多,自签名证书的域名/IP必须与代理服务器的实际访问地址完全一致。自定义客户端仅做了证书存在性验证,没有校验主题标识(SAN)或通用名称(CN)与访问地址的匹配性,所以虚拟证书能正常工作。但浏览器会强制校验这一点,哪怕证书已安装到信任列表,只要标识不匹配,握手就会触发错误(虽当前报错是记录读取阶段,但标识不匹配是常见隐性阻碍)。

生成证书时,需在OpenSSL配置中添加与服务器IP/域名匹配的SAN字段,或把CN设置为服务器的IP/域名。比如:

  • 生成证书时指定CN:openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -subj "/CN=192.168.1.100"
  • 或在openssl.cnf中配置SAN:subjectAltName = IP:192.168.1.100,DNS:yourdomain.com

针对疑问2:HTTPS代理的请求流程错误

你的核心逻辑完全错误——浏览器使用HTTPS代理时,不会直接与代理建立SSL连接,而是先建立普通TCP连接,发送CONNECT命令告知代理目标服务器的地址和端口,代理返回200 Connection Established后,浏览器才会通过代理与目标服务器建立SSL连接。

当前代码一接受连接就启动SSL握手,但此时浏览器正在发送明文的CONNECT请求,服务器把明文当成SSL记录解析,自然会报ssl3_get_record错误(SSL记录有固定格式,明文不符合要求)。

简单HTTPS中继的正确流程

  1. 接受客户端的普通TCP连接,不启动SSL握手
  2. 读取客户端发送的CONNECT请求(格式示例:CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n)
  3. 代理主动连接目标服务器(如example.com:443)
  4. 向客户端返回HTTP/1.1 200 Connection Established\r\n\r\n
  5. 启动双向数据转发:将客户端的所有数据中继到目标服务器,同时将目标服务器的数据中继回客户端——此阶段代理无需处理SSL握手,仅做透明TCP转发即可

代码修改示例

将session类改为先处理明文CONNECT请求,再做中继:

class session : public std::enable_shared_from_this<session>
{
public:
    session(boost::asio::io_service& io_service)
        : client_socket_(io_service), target_socket_(io_service)
    {
    }

    boost::asio::ip::tcp::socket& socket()
    {
        return client_socket_;
    }

    void start()
    {
        auto self(shared_from_this());
        boost::asio::async_read_until(client_socket_, request_buf_, "\r\n\r\n",
            [this, self](const boost::system::error_code& error, std::size_t bytes_transferred)
            {
                if (!error)
                {
                    // 解析CONNECT请求
                    std::string request(boost::asio::buffers_begin(request_buf_.data()),
                        boost::asio::buffers_begin(request_buf_.data()) + bytes_transferred);
                    size_t connect_pos = request.find("CONNECT ");
                    size_t port_pos = request.find(':');
                    size_t http_pos = request.find(" HTTP/");
                    std::string target_host = request.substr(connect_pos + 8, port_pos - (connect_pos + 8));
                    std::string target_port_str = request.substr(port_pos + 1, http_pos - (port_pos + 1));
                    unsigned short target_port = std::stoi(target_port_str);

                    // 连接目标服务器
                    boost::asio::ip::tcp::resolver resolver(client_socket_.get_io_service());
                    boost::asio::ip::tcp::resolver::query query(target_host, std::to_string(target_port));
                    resolver.async_resolve(query,
                        [this, self](const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
                        {
                            if (!error)
                            {
                                boost::asio::async_connect(target_socket_, endpoint_iterator,
                                    [this, self](const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator)
                                    {
                                        if (!error)
                                        {
                                            // 返回连接成功响应
                                            std::string response = "HTTP/1.1 200 Connection Established\r\n\r\n";
                                            boost::asio::async_write(client_socket_, boost::asio::buffer(response),
                                                [this, self](const boost::system::error_code& error, std::size_t)
                                                {
                                                    if (!error)
                                                    {
                                                        // 启动双向数据转发
                                                        start_client_to_target();
                                                        start_target_to_client();
                                                    }
                                                });
                                        }
                                    });
                            }
                        });
                }
            });
    }

    void start_client_to_target()
    {
        auto self(shared_from_this());
        client_socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](const boost::system::error_code& error, std::size_t bytes_transferred)
            {
                if (!error)
                {
                    boost::asio::async_write(target_socket_, boost::asio::buffer(data_, bytes_transferred),
                        [this, self](const boost::system::error_code& error, std::size_t)
                        {
                            if (!error)
                            {
                                start_client_to_target();
                            }
                        });
                }
            });
    }

    void start_target_to_client()
    {
        auto self(shared_from_this());
        target_socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](const boost::system::error_code& error, std::size_t bytes_transferred)
            {
                if (!error)
                {
                    boost::asio::async_write(client_socket_, boost::asio::buffer(data_, bytes_transferred),
                        [this, self](const boost::system::error_code& error, std::size_t)
                        {
                            if (!error)
                            {
                                start_target_to_client();
                            }
                        });
                }
            });
    }

private:
    boost::asio::ip::tcp::socket client_socket_;
    boost::asio::ip::tcp::socket target_socket_;
    enum { max_length = 1024 };
    char data_[max_length];
    boost::asio::streambuf request_buf_;
};

同时,server类需改为使用普通TCP Socket,移除SSL上下文相关逻辑。


内容的提问来源于stack exchange,提问作者Gordon Mitch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:51:32