我正在使用 Rust 从头开始构建自己的邮件服务器。我使用 tokio::new::TcpListener 设置了简单的 TCP 服务器,并使用 tokio::spawn 来处理每个连接。我用 BufReader 包装了流。然后我向客户端发送 220 响应。所以现在在客户端发送 EHLO 然后 STARTTLS 之后,我想升级流以使用 TLS。我知道我可以实现自己的 TLS,但这不是最好的主意。所以我的问题是:在命令之后如何升级到 TLS。
这是我生成证书的代码:
rm *.pem
openssl req -x509 -newkey rsa:4096 -days 3650 -keyout ca-key.pem -out ca-cert.pem -subj "..."
echo "CA's self-signed certificate"
openssl x509 -in ca-cert.pem -noout -text
openssl req -newkey rsa:4096 -keyout server-key.pem -out server-req.pem -subj "..."
openssl x509 -in ca-cert.pem -noout -text
openssl x509 -req -in server-req.pem -days 3650 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf
echo "Servers's self-signed certificate"
openssl x509 -in server-cert.pem -noout -text
openssl pkcs12 -export -out cert.pfx -inkey server-key.pem -in server-cert.pem -certfile ca-cert.pem
这是我对
native_tls
的基本实现,但是accept
功能不起作用。
// server.rs
loop {
match server.accept().await {
Ok((stream, address)) => {
tokio::spawn(async {
Connection::handle(stream).await;
});
}
Err(e) => {
error!("{e}");
}
}
}
// connection.rs fn handle
let mut buffer = String::new();
self.write("220 swiftmail.app ESMTP SwiftMail\r\n").await;
loop {
match self.stream.read_line(&mut buffer).await {
Ok(0) => break,
Ok(_bytes) => {
let parts = buffer.split_ascii_whitespace().collect::<Vec<&str>>();
let command = buffer.clone();
// STARTTLS / EHLO
let command_name = parts.get(0).unwrap();
match command_name {
"EHLO" => self.ehlo(command).await,
"STARTTLS" => self.starttls(command).await,
_ => {}
};
},
_ => {}
}
buffer.clear();
}
// starttls.rs
conn.write("220 GO HEAD\r\n").await;
let stream = conn.stream.get_mut(); // &mut TcpStream
let mut file = File::open("C:/Dev/mail-server/src/cert/cert.pfx").unwrap();
let mut identity = vec![];
file.read_to_end(&mut identity).unwrap();
let identity = Identity::from_pkcs12(&identity, "password").unwrap();
let acceptor = TlsAcceptor::new(identity).unwrap();
let acceptor = tokio_native_tls::TlsAcceptor::from(acceptor);
要在收到
STARTTLS
命令后将 Rust 中的 TCP 连接升级到 TLS,您可以使用 tokio_native_tls
crate,它提供了与 Tokio 一起使用的异步 TLS/SSL 流。您已经掌握了问题的基础知识,但让我们对其进行改进并将其适应您的应用程序的上下文。
使用
tokio-native-tls
并将 native-tls
添加到您的 Cargo.toml
。
您应该重构您的
starttls
函数来升级流:
use tokio::net::TcpStream;
use tokio_native_tls::TlsAcceptor;
use native_tls::Identity;
use std::fs::File;
use std::io::Read;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
async fn upgrade_to_tls(tcp_stream: TcpStream) -> tokio::io::Result<()> {
let mut file = File::open("path_to_cert/cert.pfx").unwrap();
let mut identity_data = vec![];
file.read_to_end(&mut identity_data).unwrap();
let identity = Identity::from_pkcs12(&identity_data, "password").unwrap();
let tls_acceptor = native_tls::TlsAcceptor::new(identity).unwrap();
let acceptor = TlsAcceptor::from(tls_acceptor);
let mut tls_stream = acceptor.accept(tcp_stream).await.unwrap();
// You can now read and write to `tls_stream` as your TLS-secured connection
tls_stream.write_all("220 Ready for secure communication\r\n".as_bytes()).await?;
// Example of reading from the stream
let mut buffer = vec![0; 1024];
let n = tls_stream.read(&mut buffer).await?;
println!("Received: {}", String::from_utf8_lossy(&buffer[..n]));
Ok(())
}
这意味着您必须先准备 TLS Identity,例如从包含证书和私钥的 PKCS12 (
.pfx
) 文件创建一个 native_tls::Identity
实例。 TLS 接受方使用此身份来执行 TLS 握手。
在主连接处理循环中,收到
STARTTLS
命令后,您将调用此 upgrade_to_tls
函数,并将 TCP 流 (TcpStream
) 作为参数传递。