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;  
                }  
            }  
        }  
    }
});
相关推荐
RustFS8 小时前
操作 MinIO 平替之 RustFS 存储桶的三种方法
rust·aws
白雾茫茫丶8 小时前
Certimate + Let’s Encrypt:零干预的 HTTPS 永续方案
https
YGY Webgis糕手之路11 小时前
Cesium 快速入门(三)Viewer:三维场景的“外壳”
前端·经验分享·笔记·vue·web
林太白11 小时前
Rust-搞定图片上传功能
前端·后端·rust
dongowu12 小时前
Rust 大白话- <所有权、借用、引用>
rust
YGY Webgis糕手之路14 小时前
Cesium 快速入门(七)材质详解
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路17 小时前
Cesium 快速入门(八)Primitive(图元)系统深度解析
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路17 小时前
Cesium 快速入门(四)相机控制完全指南
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路18 小时前
Cesium 快速入门(六)实体类型介绍
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路18 小时前
Cesium 快速入门(一)快速搭建项目
前端·经验分享·笔记·vue·web