简介

Actix-Web是Rust中比较著名的一个Web开发框架。它专为高性能、高并发的Web应用而设计。

下面简单介绍下如何使用Actix-Web搭建一个使用X.509 v3版本自签证书实现https双向认证的Web服务器

同时文章使用reqwest开发HTTP客户端进行双向认证请求会介绍在Rust中如何使用reqwest开发客户端进行双向认证的请求

Web服务器依赖

[dependencies]
actix-web = {version = "4.9.0", features = ["rustls-0_23"]}
rustls = "0.23.20"
rustls-pemfile = "2.2.0"
tokio = { version = "1.38.0", features = ["full"] }

以上为Cargo.toml中的依赖和配置的features

加载服务器自签v3版本证书和私钥

let mut certs_file = 
    BufReader::new(File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\server.crt").unwrap());
let mut key_file = 
	BufReader::new(File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\server.key").unwrap());

server.crt是Web服务的证书,其Subject为

Subject: C = CN, ST = guizhou, L = zunyi, O = boss, \
	OU = bosshi, CN = example.com, emailAddress = zuoluo@example.com

其中关键的字段为CN = example.com,该字段标识了这张证书的拥有主体是谁。我们这里随意指定其主体为example.com。客户端在进行https请求时,域名就需要填为example.com

这里还需要注意的是,server.crt需要是v3版本的证书,否则后面进行ServerConfig配置的时候会报错InvalidCertificate(Other(OtherError(UnsupportedCertVersion)))。可以通过openssl命令查看证书是否是v3版本

openssl x509 -text -noout -in server.crt

该命令会将base64编码的证书转换为文本格式进行输出

在这里插入图片描述

可以看到v3版本证书的Version字段为3

服务器证书和私钥准备好后需要对证书和私钥进行加载

    let tls_certs = rustls_pemfile::certs(&mut certs_file)
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let tls_key = rustls_pemfile::private_key(&mut key_file).unwrap().unwrap();

通过证书文件certs_file和私钥文件key_file获取到对应的证书和私钥tls_certs和tls_key。因为我们这里的服务器证书server.crt是使用自签CA证书签署的服务器证书,所以证书文件server.crt中只会有一个证书内容,就是它本身

但是如果server.crt不是由CA证书签署的而是由另一张证书A签署的,那么server.crt文件中除了有其自身证书的内容外,还应该包含签署它的证书A的内容,所以这里tls_certs是一个vec结构的证书链

客户端验证信息配置

如果不需要进行双向认证,那么前面的内容已经能够支持客户端对web服务器进行https单向验证的请求了。双向验证的话还需要为web服务器添加验证客户端的信息

服务端验证客户端和客户端https验证服务端原理相同,服务端需要加载验证客户端证书的证书。我这里是使用了一张自签CA证书给客户端签署了客户端证书,所以这里的Web服务只需要加载这张自签CA证书即可

    let mut client_ca_certs_file = 
    	BufReader::new(File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\ca.crt").unwrap());
    let client_ca_tls_certs = rustls_pemfile::certs(&mut client_ca_certs_file)
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let mut root_sert_store = RootCertStore::empty();
    for item in client_ca_tls_certs {
        _ = root_sert_store.add(item).unwrap();
    }
    let client_ca_cert = WebPkiClientVerifier::builder(Arc::new(root_sert_store))
        .build()
        .unwrap();

ca.crt就是为客户端签署客户端证书的CA证书。通过WebPkiClientVerifier::builder得到验证客户端证书的对象client_ca_cert

这样一来,服务端就有了建立双向认证所需的所有证书信息了,接下来就可以对ServerConfig进行配置然后在某个端口上建立监听了

ServerConfig配置

    // set up TLS config options
    let tls_config: rustls::ServerConfig = rustls::ServerConfig::builder()
        .with_client_cert_verifier(client_ca_cert)
        // .with_no_client_auth()
        .with_single_cert(tls_certs, tls_key)
        .unwrap();

通过with_single_cert将服务端证书和私钥tls_certs&tls_key配置到ServerConfig;通过with_client_cert_verifier将客户端认证信息client_ca_cert配置到ServerConfig。到这里就得到了一个双向认证的ServerConfig

如果我们不需要双向认证,那么可以打开with_no_client_auth,然后将with_client_cert_verifier注释,这样就变成了一个普通的Web服务

监听

    HttpServer::new(|| App::new().service(index).service(hello))
        .bind_rustls_0_23(("0.0.0.0", 9999), tls_config)?
        .run()
        .await

建立监听时需要传入ServerConfig配置tls_config。这样一来,双向认证的Web服务端我们就搭建好了

demo的完整代码在下面

服务端demo完整代码

use std::{fs::File, io::BufReader, sync::Arc};

use actix_web::{get, web, App, HttpServer, Responder};
use rustls::{server::WebPkiClientVerifier, RootCertStore};

#[get("/")]
async fn index() -> impl Responder {
    println!("Hello, World!");
    "Hello, World!"
}

#[get("/{name}")]
async fn hello(name: web::Path<String>) -> impl Responder {
    println!("{name}");
    format!("Hello {}!", &name)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let mut certs_file = BufReader::new(File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\server.crt").unwrap());
    let mut key_file = BufReader::new(File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\server.key").unwrap());

    let tls_certs = rustls_pemfile::certs(&mut certs_file)
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let tls_key = rustls_pemfile::private_key(&mut key_file).unwrap().unwrap();

    let mut client_ca_certs_file = BufReader::new(File::open("C:\\Users\\zuolu\\SourceCode\\rust_client\\ca.crt").unwrap());
    let client_ca_tls_certs = rustls_pemfile::certs(&mut client_ca_certs_file)
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let mut root_sert_store = RootCertStore::empty();
    for item in client_ca_tls_certs {
        _ = root_sert_store.add(item).unwrap();
    }
    let client_ca_cert = WebPkiClientVerifier::builder(Arc::new(root_sert_store))
        .build()
        .unwrap();

    // set up TLS config options
    let tls_config = rustls::ServerConfig::builder()
        .with_client_cert_verifier(client_ca_cert)
        // .with_no_client_auth()
        .with_single_cert(tls_certs, tls_key)
        .unwrap();

    HttpServer::new(|| App::new().service(index).service(hello))
        .bind_rustls_0_23(("0.0.0.0", 9999), tls_config)?
        .run()
        .await
}

公众号名称:zl.rs

更多推荐