错误信息
received fatal alert: HandshakeFailure
reqwest 把它打印在错误链的末尾,ureq 显示为“io: received fatal alert: HandshakeFailure”。连接只支持 TLS 1.0 的服务器(tls-v1-0.badssl.com)时,我们的 rustls 客户端收到的也是这个告警。其他告警会以各自的 rustls 名称出现在“received fatal alert: ”之后。
rustls (via reqwest, ureq, tonic) — rustls::Error::AlertReceived(HandshakeFailure)
含义
服务器用 TLS 告警 40(handshake_failure)响应了 rustls 的 ClientHello。rustls 有意只实现现代 TLS:采用 ECDHE 密钥交换和 AEAD 加密算法的 TLS 1.2 和 1.3。如果服务器要求 RSA 密钥交换、CBC 套件、TLS 1.0/1.1 或客户端证书,就会以这种方式拒绝它。
原因
服务器不提供 rustls 实现的任何密码套件
常见只支持 RSA 密钥交换、CBC-SHA 或 3DES 这几类套件的旧服务器,与 rustls 没有任何共同的套件。
服务器只支持 TLS 1.0 或 1.1
常见rustls 不实现低于 1.2 的 TLS 版本。
服务器要求客户端证书
偶见在 TLS 1.2 下,双向 TLS 端点会用 handshake_failure 响应没有提供证书的客户端。
修复方法
- 1.
检查服务器提供了什么
如果服务器不提供任何 ECDHE + AEAD 套件,就需要对它做现代化升级。
bash # What does the server accept? rustls offers TLS 1.2/1.3 with ECDHE + AES-GCM or ChaCha20 only nmap --script ssl-enum-ciphers -p 443 legacy.example.com - 2.
旧服务器无法维护时的最后手段:native-tls
用 reqwest 的 native-tls 特性构建一个专用客户端,会使用平台的 TLS 库(在 Linux 上是 OpenSSL,构建时需要它的开发头文件),而该库可能仍允许较旧的套件,具体取决于系统策略。只在访问该旧端点时使用它;服务器的弱配置依然是一个风险。
toml # A legacy server you cannot upgrade: build a separate client on the OS TLS library # (OpenSSL on Linux), which still implements older suites. Cargo.toml: reqwest = { version = "=0.13.5", default-features = false, features = ["native-tls", "blocking"] } - 3.
双向 TLS:提供客户端证书
用包含证书和私钥的 PEM 调用 reqwest::Identity::from_pem,并把得到的身份设置到构建器上。
rust // Mutual TLS with rustls: present a client certificate (PEM with certificate + private key) let identity = reqwest::Identity::from_pem(&std::fs::read("client-cert-and-key.pem")?)?; let client = reqwest::Client::builder().identity(identity).build()?;