Rust使用tokio(二)HTTPS相关

reqwest

在tokio中的HTTP客户端,可以使用reqwest。

reqwest的feature中,有native-tls,可以直接支持证书校验。

在Cargo.toml中加入依赖:

toml 复制代码
[dependencies]  
tokio = { version = "1.36.0", features = ["full"] }    
reqwest = { version = "0.11", features = ["native-tls"] }

即可以在初始化HTTP Client的时候,加入证书。方法是把证书读入一个Identity中,之后使用Client::Builder的identity函数。

如:

rust 复制代码
fn client_new(path: &str) -> Client {
    let p12_data = fs::read(path).expect("Failed to read certificate file");  
    let identity = Identity::from_pkcs12_der(&p12_data, "")  
        .expect("Failed to create identity from PKCS12");  
    let client_builder = Client::builder()  
        .identity(identity)  
    client_builder.build().expect("Failed to create HTTP client")
}

另外,Client::Builder还有一个danger_accept_invalid_certs函数,可以控制在服务端证书有问题的情况下,是否接受。

如以下代码,就忽略了服务端证书错误:

rust 复制代码
let client = client_builder  
    .identity(identity)  
    .danger_accept_invalid_certs(true).build().expect("Failed to create HTTP client")

使用Client的get方法:

rust 复制代码
async fn get_request(client: &Client, url: &str) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {  
    let response = client.get(url).send().await?;  
    if !response.status().is_success() {  
        return Err(format!("HTTP get failed with status: {}", response.status()).into());  
    }
    Ok(response)
}```

使用Client的post方法:
```rust
async fn post_request(
    client: &Client,
    url: &str,  
    data: serde_json::Value,  
) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {    
    let response = client.post(url).json(&data).send().await?;  
  
    if !response.status().is_success() {  
        return Err(format!("HTTP post failed with status: {}", response.status()).into());  
    }  
  
    Ok(response)
}

hyper作为Web服务端

hyper是Rust中开发Web服务的crate。

创建一个SocketAddr,之后使用hyper::Server的bind方法绑定到地址上,之后即可以在之上挂接一个回调函数来处理Web请求。

如:

rust 复制代码
async fn start_http(port: u16) {
    let addr = SocketAddr::from(([0, 0, 0, 0], port));  
    let make_svc = make_service_fn(|conn: &AddrStream| {  
        let client_addr = conn.remote_addr();  
        async move { Ok::<_, Infallible>(service_fn(move |req| handle_request(req, client_addr))) }  
});

    let server = Server::bind(&addr).serve(make_svc);
    server.await.expect("Failed to start HTTP server");
}

openssl工具

我们可以使用OpenSSL套件中的openssl命令,生成测试证书。

如:

shell 复制代码
# 创建 CA  
openssl req -x509 -newkey rsa:4096 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes -subj "/CN=MyCA"  
  
# 创建服务器证书  
openssl req -newkey rsa:4096 -keyout server_key.pem -out server_csr.pem -nodes -subj "/CN=localhost"  
openssl x509 -req -in server_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out server_cert.pem  
-days 365  
  
# 创建客户端证书  
openssl req -newkey rsa:4096 -keyout client_key.pem -out client_csr.pem -nodes -subj "/CN=TestClient"  
openssl x509 -req -in client_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out client_cert.pem  
-days 365

还可以把证书、key以及ca导出到一个p12格式的证书文件中:

shell 复制代码
# 服务端P12证书
openssl pkcs12 -export -in server_cert.pem -inkey server_key.pem -CAfile ca_cert.pem -out server.p12

# 客户端测试证书
openssl pkcs12 -export -in client_cert.pem -inkey client_key.pem -CAfile ca_cert.pem -out client.p12

Rust的openssl::Pkcs12

Rust中也有openssl的crate,可以使用openssl,解析P12格式的文件,取出其中的证书、key以及ca:

rust 复制代码
let p12_data = std::fs::read("cert.p12").expect("Failed to read P12 certificate");  
let p12 = Pkcs12::from_der(&p12_data).expect("Failed to load P12 certificate");  
let p12_parsed = p12.parse2("").expect("Failed to parse P12 certificate");  
  
let cert_chain = if let Some(cert) = p12_parsed.cert {  
    vec![Certificate(  
        cert.to_der().expect("Failed to serialize certificate"),  
    )]  
} else {  
    panic!("No certificate found in P12");  
};  
  
let private_key = if let Some(pkey) = p12_parsed.pkey {  
    PrivateKey(  
        pkey.private_key_to_der()  
            .expect("Failed to load private key"),  
    )  
} else {  
    panic!("No private key found in P12");  
};  
  
let mut roots = RootCertStore::empty();  
if let Some(ca) = p12_parsed.ca {  
    for cert in ca {  
        roots.add(&Certificate(  
                cert.to_der().expect("Failed to serialize certificate"),  
            ))  
            .expect("Failed to add certificate");  
    }  
}

hyper-rustls

前面提到的hyper,没有支持HTTPS。我们可以使用hyper-rustls,来支持HTTPS的服务。

首先,使用Pcs12导出的证书、key以及ca链,生成ServerConfig:

rust 复制代码
let client_auth = AllowAnyAuthenticatedClient::new(roots);  
  
let server_config = ServerConfig::builder()  
    .with_safe_defaults()  
    .with_client_cert_verifier(Arc::new(client_auth))  
    .with_single_cert(cert_chain, private_key)  
    .expect("Failed to set certificate")

使hyper应用Rustls的ServerConfig:

rust 复制代码
let addr = SocketAddr::from(([0, 0, 0, 0], port));  
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");  
let acceptor = TlsAcceptor::from(Arc::new(server_config));  
let make_svc = make_service_fn(|conn: &TlsStream<TcpStream>| {  
    let client_addr = conn.get_ref().0.peer_addr().unwrap();  
    async move { Ok::<_, Infallible>(service_fn(move |req| handle_request(req, client_addr))) }  
});
  
let incoming_tls_stream = futures_util::stream::unfold(listener, move |listener| {  
    let acceptor = acceptor.clone();  
    async move {  
        match listener.accept().await {  
            Ok((stream, _)) => {  
                let acceptor = acceptor.clone();  
                match acceptor.accept(stream).await {  
                    Ok(tls_stream) => Some((Ok(tls_stream), listener)),  
                    Err(e) => Some((  
                        Err(std::io::Error::new(std::io::ErrorKind::Other, e)),  
                        listener,  
                    )),  
                }  
            }  
            Err(e) => Some((Err(e), listener)),  
        }  
    }  
});
  
let server = Server::builder(from_stream(incoming_tls_stream)).serve(make_svc);  
info!("HTTPS running on https://{}", addr);  
server.await.expect("Failed to run HTTPS server");

主要的逻辑,就是使用hyper的Server::builder从TLS的Stream,来执行serve函数,挂接make_svc过程。

上面的incoming_tls_stream,在失败的情况下会退出。可以把上面的过程,包在一个循环中,出错就只是打印错误,之后继续等待连接。

如:

rust 复制代码
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");  
let incoming_tls_stream = futures_util::stream::unfold(listener, move |listener| {  
    let acceptor = acceptor.clone();  
    async move {  
        loop {  
            match listener.accept().await {  
                Ok((stream, _)) => match acceptor.accept(stream).await {  
                    Ok(tls_stream) => {  
                        return Some((  
                            Ok::<_, Box<dyn std::error::Error + Send + Sync>>(tls_stream),  
                            listener,  
                        ));  
                    }
                    
                    // TLS层接受连接失败,则报错,之后继续循环 
                    Err(e) => {  
                        warn!("tls accept error: {}", e);  
                        continue;  
                    }  
                },

                // TCP层接受连接失败,也报错,之后继续循环
                Err(e) => {  
                    warn!("tcp accept error: {}", e);  
                    continue;  
                }  
            }  
        }  
    }
});
相关推荐
三体世界5 小时前
HTTPS加密原理
linux·开发语言·网络·c++·网络协议·http·https
墨雪遗痕5 小时前
中文Windows系统下程序输出重定向乱码问题解决方案
rust·编码·powershell
starstarzz17 小时前
解决idea无法正常加载lombok包
java·ide·spring·intellij-idea·springboot·web
susnm18 小时前
Dioxus 互动性
rust·全栈
xiaoyan201518 小时前
基于tauri2.0+vue3.5+deepseek+arco搭建wins版流式输出AI系统
vue.js·rust·deepseek
Starduster18 小时前
smol源码解析1 序
后端·rust·源码
好奇的菜鸟18 小时前
Rust 中的宏与函数
开发语言·后端·rust
好奇的菜鸟19 小时前
Rust 项目文档生成之旅:cargo doc
开发语言·后端·rust
2501_9160074719 小时前
定位接口偶发超时的实战分析:iOS抓包流程的完整复现
websocket·网络协议·tcp/ip·http·网络安全·https·udp