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;  
                }  
            }  
        }  
    }
});
相关推荐
迷藏4944 小时前
**发散创新:基于Rust实现的开源合规权限管理框架设计与实践**在现代软件架构中,**权限控制(RBAC)** 已成为保障
java·开发语言·python·rust·开源
洒家肉山大魔王9 小时前
PKI/CA X.509证书的基础应用与解读
服务器·https·密码学·数字证书
迷藏49412 小时前
**发散创新:基于 Rust的模型保护机制设计与实践**在人工智能快速发
java·人工智能·python·rust·neo4j
love530love12 小时前
从零搭建本地版 Claurst:基于 Rust 重构的 Claude Code 终端编码助手 + LM Studio 模型接入测试
开发语言·人工智能·windows·重构·rust·lm studio·claude code
恋喵大鲤鱼12 小时前
如何理解 Rust 没有运行时(No Runtime)
rust
前端 贾公子12 小时前
Vite 开发环境配置 HTTPS
网络协议·http·https
懒大王952712 小时前
http和https的异同点和优缺点
网络协议·http·https
曲幽13 小时前
FastAPI自动生成的API文档太丑?我花了一晚上把它改成了客户愿意付费的样子
python·fastapi·web·swagger·openapi·scalar·docs
Tomhex2 天前
Rust数组与Vec的核心差异解析
rust
snow@li2 天前
协议:应用层开发都会涉及哪些协议 / 详细整理 / http、ws、https、wss
网络协议·http·https