エラーメッセージ
received fatal alert: HandshakeFailure
reqwest はエラーの連鎖の末尾にこれを出力し、ureq は「io: received fatal alert: HandshakeFailure」と出力します。TLS 1.0 のみに対応したサーバー(tls-v1-0.badssl.com)に対しても、ecdsa.com のテストで使った rustls クライアントは同じアラートを受け取りました。ほかのアラートは、「received fatal alert: 」の後にそれぞれの rustls での名前で表示されます。
rustls (via reqwest, ureq, tonic) — rustls::Error::AlertReceived(HandshakeFailure)
このエラーの意味
サーバーは rustls の ClientHello に TLS アラート 40(handshake_failure)で応答しました。rustls は意図的に最新の TLS だけを実装しています。つまり、ECDHE 鍵交換と AEAD 暗号を使う TLS 1.2 と 1.3 です。RSA 鍵交換、CBC の暗号スイート、TLS 1.0/1.1、クライアント証明書のいずれかを必要とするサーバーは、このようにして ClientHello を拒否します。
発生する理由
サーバーに rustls が実装する暗号スイートがない
よくあるRSA 鍵交換、CBC-SHA、3DES のいずれかを使う暗号スイートしか使えない古いサーバーは、rustls と共通するものがありません。
サーバーが TLS 1.0 または 1.1 にしか対応していない
よくあるrustls は、1.2 未満の TLS バージョンを実装していません。
サーバーがクライアント証明書を要求している
ときどき相互 TLS のエンドポイントは、TLS 1.2 では、証明書を持たないクライアントに handshake_failure で応答します。
修正方法
- 1.
サーバーが何を提供しているかを確認する
ECDHE + AEAD の暗号スイートを 1 つも提供していない場合は、サーバーを最新の構成に更新する必要があります。
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()?;