简介
reqwest
是一个用于Rust的HTTP客户端库,它提供了简单易用的API来发送HTTP请求和处理响应
接下来介绍下如何使用reqwest进行https的双向认证请求
同时文章使用Rust中的actix_web框架搭建双向认证的Web服务器介绍了使用actix_web如何搭建一个双向认证的Web服务器
依赖
[dependencies]
reqwest = {version = "0.12.9", features = ["native-tls"]}
tokio = {version = "1.42.0", features = ["full"]}
以上为Cargo.toml文件中添加的依赖和配置的features
加载验证服务端的CA证书
let mut buf = Vec::new();
_ = File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\ca.crt")
.unwrap()
.read_to_end(&mut buf)
.unwrap();
let ca_cert = Certificate::from_pem(&buf).unwrap();
要对服务端证书进行验证只需要加载为服务端签署证书的CA证书即可,非常简单直观。注意这里需要v3版本的证书
加载服务端验证客户端的证书
let mut pem = Vec::new();
let mut pkcs8 = Vec::new();
_ = File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\client.crt")
.unwrap()
.read_to_end(&mut pem)
.unwrap();
_ = File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\client_pkcs8.key")
.unwrap()
.read_to_end(&mut pkcs8)
.unwrap();
let identity: Identity = Identity::from_pkcs8_pem(&pem, &pkcs8).unwrap();
因为需要进行双向认证,所以客户端也需要证书来告知服务端自己的身份。这里client.crt
和client_pkcs8.key
分别是客户端的证书和私钥。对应的,在服务端你需要有能够验证client.crt的CA证书
构造ClientBuilder和Client
let client_build = ClientBuilder::new()
// .danger_accept_invalid_certs(true)
// .danger_accept_invalid_hostnames(true)
// .use_rustls_tls()
.identity(identity)
.add_root_certificate(ca_cert);
let client = client_build.build().unwrap();
使用前面得到的ca_cert
和identity
构造一个ClientBuilder
这个地方我遇到了几个坑。首先是最初在指定reqwest 的features 时没有采用native-tls
,而是使用的rustls-tls
。然后在通过ClientBuilder
生成Client
时出现错误kind: Builder, source: "incompatible TLS identity type"
。一番查找之后发现issues中有提到构造ClientBuilder时.use_rustls_tls()
可以解决。添加后确实也成功通过ClientBuilder构造出了Client
但是在通过Client发送数据时出现kind: Other, error: Custom { kind: InvalidData, error: InvalidCertificate(NotValidForName) } }
的错误。最后是通过将features 指定为native-tls
解决
请求
let res = client.get("https://example.com:9999/zuoluo_windows")
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
println!("{}", body);
最后,通过构造的client对服务端进行请求,值得注意的是因为服务端没有监听在443上,所以这里需要明确指定一下服务端监听的端口
同时不要忘了把你电脑上的hosts改一下,将域名example.com
指向127.0.0.1即可,windows电脑的hosts位置为C:\Windows\System32\drivers\etc\hosts
,在文件中添加
127.0.0.1 example.com
最后运行抓包
可以看到成功的建立了加密连接
最后客户端完整的代码贴在下面
客户端demo完整代码
use std::{fs::File, io::Read};
use reqwest::{Certificate, ClientBuilder, Identity};
#[tokio::main]
async fn main() {
let mut buf = Vec::new();
_ = File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\ca.crt")
.unwrap()
.read_to_end(&mut buf)
.unwrap();
let ca_cert = Certificate::from_pem(&buf).unwrap();
let mut pem = Vec::new();
let mut pkcs8 = Vec::new();
_ = File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\client.crt")
.unwrap()
.read_to_end(&mut pem)
.unwrap();
_ = File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\client_pkcs8.key")
.unwrap()
.read_to_end(&mut pkcs8)
.unwrap();
let identity: Identity = Identity::from_pkcs8_pem(&pem, &pkcs8).unwrap();
let client_build = ClientBuilder::new()
// .danger_accept_invalid_certs(true)
// .danger_accept_invalid_hostnames(true)
// .use_rustls_tls()
.identity(identity)
.add_root_certificate(ca_cert);
let client = client_build.build().unwrap();
let res = client.get("https://example.com:9999/zuoluo_windows")
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
println!("{}", body);
// println!("body = {body:?}");
}