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

求助:基于Hyper实现支持mTLS连接的HTTP代理

基于Hyper实现支持mTLS的HTTP代理问题

问题现象

我基于Hyper的HTTP代理示例,添加tokio-rustls实现支持mTLS的代理,处理HTTP隧道的代码如下。测试时用Kotlin代码通过该代理访问要求mTLS认证的本地Nginx服务器,抛出Unsupported or unrecognized SSL message错误,Nginx日志显示收到的是加密格式的请求数据。

Rust代理隧道处理代码

async fn tunnel(
    mut upgraded: Upgraded,
    destination: (String, u16),
    certificates: Certificates,
) -> std::io::Result<()> {
    let root_cert_store = prepare_cert_store(&certificates);
    let tls_client_config = tls_config(certificates, root_cert_store);
    let tls_connector = TlsConnector::from(Arc::new(tls_client_config));

    let target = TcpStream::connect(target_address(&destination)).await?;

    let domain = rustls::ServerName::try_from(destination.0.as_str()).expect("Invalid DNSName");

    let mut tls_target = tls_connector.connect(domain, target).await?;
    debug!("TlS Connection ready");
    let (wrote, recvd) = tokio::io::copy_bidirectional(&mut upgraded, &mut tls_target).await?;
    debug!("Client wrote {} and received {} bytes", wrote, recvd);
    Ok(())
}

Kotlin测试代码

fun main() {
    val fm = FuelManager.instance
    fm.proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("127.0.0.1", 8100))

    repeat(1) {
        val response = "https://nginx-test".httpGet()
            .responseString()
        println("Response: $response")
        println("Response Data: ${response.second.data.toString(Charset.defaultCharset())}")
    }
}

Nginx配置

server {
    listen                  80;

    location / {
        return              301 https://$host$request_uri;
    }
}

server {
    listen                  443 ssl;
    server_name             nginx.test;

    ssl_certificate         /etc/ssl/server.crt;
    ssl_certificate_key     /etc/ssl/server.key;
    ssl_protocols           TLSv1.2 TLSv1.3;
    ssl_ciphers             HIGH:!aNULL:!MD5;

    ssl_client_certificate  /etc/nginx/client_certs/ca.crt;
    ssl_verify_client       optional;
    ssl_verify_depth        2;

    location / {
        if ($ssl_client_verify != SUCCESS) { return 403; }

        proxy_set_header     SSL_Client_Issuer $ssl_client_i_dn;
        proxy_set_header     SSL_Client $ssl_client_s_dn;
        proxy_set_header     SSL_Client_Verify $ssl_client_verify;

        return 200 "yay";
    }
}

问题根源

你的代码逻辑混淆了HTTP隧道的工作模式:

  • 当客户端通过HTTP代理访问HTTPS站点时,会先发送CONNECT请求给代理,代理与目标服务器建立TCP连接(而非TLS连接),然后返回200 Connection Established给客户端。
  • 之后客户端会直接与目标服务器通过代理的隧道进行TLS握手(包括mTLS所需的客户端证书验证),代理仅做透明的字节转发。

但你现在的代码中,代理主动与目标服务器建立了TLS连接,随后把客户端发送的TLS握手加密数据直接转发到这个已加密的连接中。此时Nginx已经和代理处于TLS会话中,会把收到的客户端TLS数据当成应用层HTTP请求,自然无法识别,从而抛出Unsupported or unrecognized SSL message错误。

解决方案

根据你的需求,有两种正确的实现方式:

方案1:代理作为透明隧道,客户端直接与目标做mTLS

如果你的需求是让客户端自己完成与目标服务器的mTLS认证,代理仅做流量转发,那么只需要去掉代理端的TLS连接逻辑,直接转发TCP流即可:

async fn tunnel(
    mut upgraded: Upgraded,
    destination: (String, u16),
) -> std::io::Result<()> {
    let mut target = TcpStream::connect(target_address(&destination)).await?;
    debug!("TCP Connection ready");
    let (wrote, recvd) = tokio::io::copy_bidirectional(&mut upgraded, &mut target).await?;
    debug!("Client wrote {} and received {} bytes", wrote, recvd);
    Ok(())
}

此时需要确保客户端配置了正确的客户端证书,以便和Nginx完成mTLS握手。

方案2:代理作为mTLS客户端,与目标服务器完成认证

如果你的需求是代理代表客户端与目标服务器进行mTLS认证(客户端与代理之间用普通HTTP通信),那么不能使用CONNECT隧道模式,而是需要代理解析客户端的HTTP请求,然后作为mTLS客户端向目标服务器发起请求,再将响应返回给客户端:

  1. 放弃CONNECT隧道的处理逻辑,改为直接处理客户端的HTTP请求。
  2. 用tokio-rustls创建TLS连接器,向目标服务器发起HTTPS请求(携带客户端证书)。
  3. 将目标服务器的响应转发给客户端。

这种模式下,客户端不需要配置任何证书,所有mTLS逻辑都在代理端完成。

内容的提问来源于stack exchange,提问作者Bruno Ortiz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 07:25:25