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

PEM转DER后Hyper API的HTTPS连接SSL错误排查

Hyper API迁移HTTPS后的SSL证书验证问题排查

问题描述

将基于Hyper的API迁移到HTTPS后,使用Python requests调用时始终抛出以下错误:
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)'

服务端则输出:Bad connection: cannot decrypt peer's message

已按tokio_rustls文档要求,将Certbot生成的PEM证书转换为DER格式:

  • 证书转换命令:openssl x509 -outform der -in /etc/letsencrypt/live/mysite.com/cert.pem -out /etc/letsencrypt/live/mysite.com/cert.der
  • 私钥转换命令:openssl pkcs8 -topk8 -inform PEM -outform DER -in /etc/letsencrypt/live/mysite.com/privkey.pem -out /etc/letsencrypt/live/mysite.com/privkey.der -nocrypt

通过include_bytes!()加载证书和私钥到PKEY、CERT变量,代码可编译运行,但请求时出现上述错误,且证书在Apache2中可正常使用。

服务端核心代码

fn tls_acceptor_impl(cert_der: &[u8], key_der: &[u8]) -> tokio_rustls::TlsAcceptor {
    let key = PrivateKey(cert_der.into());
    let cert = Certificate(key_der.into());
    Arc::new(
        ServerConfig::builder()
            .with_safe_defaults()
            .with_no_client_auth()
            .with_single_cert(vec![cert], key)
            .unwrap(),
    )
    .into()
}

fn tls_acceptor() -> tokio_rustls::TlsAcceptor {
    tls_acceptor_impl(PKEY, CERT)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(...);
    let mut listener = tls_listener::builder(tls_acceptor())
        .max_handshakes(10)
        .listen(AddrIncoming::bind(&addr).unwrap());

    let (tx, mut rx) = mpsc::channel::<tokio_rustls::TlsAcceptor>(1);

    let http = Http::new();
    loop {
        tokio::select! {
            conn = listener.accept() => {
                match conn.expect("Tls listener stream should be infinite") {
                    Ok(conn) => {
                        let http = http.clone();
                        tokio::spawn(async move {
                            if let Err(err) = http.serve_connection(conn, service_fn(my_query_handler)).await {
                                eprintln!("Application error: {}", err);
                            }
                        });
                    },
                    Err(e) => {
                        eprintln!("Bad connection: {}", e);
                    }
                }
            },
            message = rx.recv() => {
                let acceptor = message.expect("Channel should not be closed");
            }
        }
    }
}

排查与修复步骤

1. 修复证书与私钥的参数传递错误

你的tls_acceptor_impl函数把证书和私钥的参数完全搞反了,这是核心错误:

// 错误写法:将证书DER传给私钥,私钥DER传给证书
let key = PrivateKey(cert_der.into());
let cert = Certificate(key_der.into());

正确写法:

fn tls_acceptor_impl(cert_der: &[u8], key_der: &[u8]) -> tokio_rustls::TlsAcceptor {
    let key = PrivateKey(key_der.into()); // 私钥用key_der
    let cert = Certificate(cert_der.into()); // 证书用cert_der
    Arc::new(
        ServerConfig::builder()
            .with_safe_defaults()
            .with_no_client_auth()
            .with_single_cert(vec![cert], key)
            .unwrap(),
    )
    .into()
}

参数颠倒会导致服务端无法正确解密客户端消息,同时客户端无法验证证书有效性。

2. 确保使用完整证书链

Certbot生成的单独cert.pem仅包含叶子证书,缺少中间证书,会导致客户端验证失败。应使用fullchain.pem转换为DER格式:

openssl x509 -outform der -in /etc/letsencrypt/live/mysite.com/fullchain.pem -out /etc/letsencrypt/live/mysite.com/fullchain.der

然后在代码中加载fullchain.der,因为with_single_cert需要传入完整的证书链(叶子证书+中间证书)。

3. 验证证书与私钥的匹配性

用OpenSSL确认转换后的DER文件是否匹配:

# 查看证书公钥
openssl x509 -in /etc/letsencrypt/live/mysite.com/cert.der -inform der -noout -pubkey
# 查看私钥对应的公钥
openssl rsa -in /etc/letsencrypt/live/mysite.com/privkey.der -inform der -pubout

对比两个输出的公钥,必须完全一致,否则说明转换过程出错。

4. 排查客户端信任问题

若服务端修复后客户端仍报错,可能是客户端未信任Let's Encrypt根证书:

  • 对于Python requests,可指定CA证书路径:
    import requests
    response = requests.get('https://mysite.com', verify='/etc/letsencrypt/live/mysite.com/fullchain.pem')
    
  • 确保客户端系统的CA证书库包含Let's Encrypt的根证书(多数现代系统已默认包含)。

5. 启用TLS调试日志

在服务端添加rustls调试日志,查看握手过程细节:

  1. 在Cargo.toml添加依赖:
    [dependencies]
    env_logger = "0.10"
    
  2. 在main函数开头初始化日志:
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("rustls=debug")).init();
    
  3. 运行时设置环境变量:RUST_LOG=rustls=debug,查看详细的TLS握手日志定位错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 23:15:29