rustls для Rust: полное руководство с примерами
1. Что это и зачем нужна
rustls — это современная, безопасная и высокопроизводительная реализация протокола TLS (Transport Layer Security) на языке Rust. В отличие от таких монстров, как OpenSSL или native-tls, rustls написан полностью на Rust без привязок к C-библиотекам, что делает его идеальным выбором для проектов, где важны безопасность памяти, отсутствие неопределённого поведения (undefined behavior) и лёгкость сборки.
Зачем нужна rustls? Во-первых, она решает проблему «dependency hell»: вам не нужно устанавливать OpenSSL на сервер или бороться с версиями libssl-dev. Во-вторых, rustls по умолчанию использует только безопасные криптографические алгоритмы (TLS 1.2 и 1.3, исключая устаревшие шифры). В-третьих, она идеально интегрируется с экосистемой Rust через async/await и такие фреймворки, как Tokio и async-std.
Основные сценарии использования: создание HTTPS-серверов и клиентов, защищённое WebSocket-соединение, работа с gRPC, а также любые другие случаи, где требуется шифрование трафика поверх TCP. rustls активно используется в таких проектах, как curl, Firefox (через Neqo), и многих промышленных Rust-приложениях.
2. Установка
Добавьте rustls в ваш проект с помощью менеджера пакетов Cargo. Для базового использования достаточно двух зависимостей:
# Cargo.toml
[dependencies]
rustls = "0.23" # Основная библиотека
webpki-roots = "0.26" # Доверенные корневые сертификаты (для клиента)
tokio = { version = "1", features = ["full"] } # Для асинхронного примера
tokio-rustls = "0.26" # Асинхронная обёртка для Tokio
Если вам нужен только синхронный (блокирующий) режим, можно обойтись без tokio-rustls. Для работы с сертификатами также может понадобиться rcgen (генерация самоподписанных сертификатов) и rustls-pemfile (чтение PEM-файлов).
# Установка через cargo add (если используете cargo-edit)
cargo add rustls webpki-roots tokio tokio-rustls
3. Быстрый старт — минимальный рабочий пример
Создадим простой HTTPS-клиент, который загружает страницу с example.com. Этот пример демонстрирует базовую настройку rustls с использованием системных корневых сертификатов.
// src/main.rs
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
use rustls::pki_types::ServerName;
use webpki_roots::TLS_SERVER_ROOTS;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Создаём конфигурацию клиента с доверенными корнями
let config = rustls::ClientConfig::builder()
.with_root_certificates(TLS_SERVER_ROOTS.clone())
.with_no_client_auth();
// 2. Создаём TLS-коннектор
let connector = TlsConnector::from(Arc::new(config));
// 3. Устанавливаем TCP-соединение
let stream = TcpStream::connect("example.com:443").await?;
// 4. Оборачиваем в TLS
let server_name = ServerName::try_from("example.com")?;
let mut tls_stream = connector.connect(server_name, stream).await?;
// 5. Отправляем HTTP-запрос
use tokio::io::AsyncWriteExt;
tls_stream.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n").await?;
// 6. Читаем ответ
use tokio::io::AsyncReadExt;
let mut response = String::new();
tls_stream.read_to_string(&mut response).await?;
println!("Ответ сервера:\n{}", response);
Ok(())
}
Этот код устанавливает защищённое соединение с example.com, отправляет HTTP-запрос и выводит ответ. Обратите внимание: мы используем webpki-roots для проверки сертификата сервера — это безопасно и не требует установки OpenSSL.
4. Основные методы/функции/классы
4.1. rustls::ClientConfig::builder()
Сигнатура: pub fn builder() -> ConfigBuilder<ClientConfig, WantsCipherSuites>
Что делает: Создаёт строителя (builder) для конфигурации TLS-клиента. Позволяет настроить cipher suites, версии протоколов, корневые сертификаты и другие параметры.
let config = rustls::ClientConfig::builder()
.with_root_certificates(webpki_roots::TLS_SERVER_ROOTS.clone())
.with_no_client_auth();
// config: rustls::ClientConfig
4.2. rustls::ServerConfig::builder()
Сигнатура: pub fn builder() -> ConfigBuilder<ServerConfig, WantsCipherSuites>
Что делает: Создаёт конфигурацию для TLS-сервера. Требует указания сертификата и приватного ключа сервера.
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
let certs = vec![CertificateDer::from(...)]; // Загрузите сертификат
let key = PrivateKeyDer::from(...); // Загрузите ключ
let config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
// config: rustls::ServerConfig
4.3. rustls::ClientConnection::new()
Сигнатура: pub fn new(config: Arc<ClientConfig>, server_name: ServerName) -> Result<ClientConnection, Error>
Что делает: Создаёт новое TLS-соединение со стороны клиента. Принимает конфигурацию и имя сервера (SNI).
use std::sync::Arc;
use rustls::pki_types::ServerName;
let config = Arc::new(client_config);
let server_name = ServerName::try_from("example.com").unwrap();
let mut client = rustls::ClientConnection::new(config, server_name)?;
// client: rustls::ClientConnection
4.4. rustls::ServerConnection::new()
Сигнатура: pub fn new(config: Arc<ServerConfig>) -> Result<ServerConnection, Error>
Что делает: Создаёт новое TLS-соединение со стороны сервера. Использует серверную конфигурацию с сертификатом.
let config = Arc::new(server_config);
let mut server = rustls::ServerConnection::new(config)?;
// server: rustls::ServerConnection
4.5. Connection::read_tls()
Сигнатура: pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result<usize, io::Error>
Что делает: Читает необработанные TLS-данные из переданного потока (например, TcpStream) и передаёт их внутрь соединения для дешифровки.
let mut tcp_stream = TcpStream::connect("127.0.0.1:4433")?;
let n = client.read_tls(&mut tcp_stream)?;
// n: количество прочитанных байт
4.6. Connection::write_tls()
Сигнатура: pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result<usize, io::Error>
Что делает: Записывает зашифрованные TLS-данные в переданный поток. Вызывается после того, как вы передали данные для отправки через writer.
let mut tcp_stream = TcpStream::connect("127.0.0.1:4433")?;
let n = client.write_tls(&mut tcp_stream)?;
// n: количество записанных байт
4.7. Connection::writer()
Сигнатура: pub fn writer(&mut self) -> &mut dyn io::Write
Что делает: Возвращает ссылку на объект, реализующий Write, в который можно писать открытые (plaintext) данные. Эти данные будут зашифрованы и отправлены при следующем вызове write_tls.
client.writer().write_all(b"Hello, TLS!")?;
client.write_tls(&mut tcp_stream)?; // Фактическая отправка
4.8. Connection::reader()
Сигнатура: pub fn reader(&mut self) -> &mut dyn io::Read
Что делает: Возвращает ссылку на объект, реализующий Read, из которого можно читать расшифрованные данные. Перед вызовом необходимо сначала прочитать зашифрованные данные через read_tls.
client.read_tls(&mut tcp_stream)?; // Сначала читаем шифротекст
client.process_new_packets()?; // Обрабатываем протокол
let mut buf = [0u8; 1024];
let n = client.reader().read(&mut buf)?; // Читаем открытый текст
4.9. Connection::process_new_packets()
Сигнатура: pub fn process_new_packets(&mut self) -> Result<IOCallback, Error>
Что делает: Обрабатывает все накопленные TLS-записи (handshake, данные, alert). Должен вызываться после каждого read_tls и перед чтением данных через reader().
client.read_tls(&mut tcp_stream)?;
let io_callback = client.process_new_packets()?;
// io_callback содержит информацию о состоянии соединения
4.10. rustls::pki_types::CertificateDer
Сигнатура: pub struct CertificateDer<'a>(pub &'a [u8]) (упрощённо)
Что делает: Представляет сертификат X.509 в DER-формате. Используется для загрузки и передачи сертификатов в конфигурацию.
use rustls_pemfile::certs;
use std::io::BufReader;
let mut reader = BufReader::new(File::open("cert.pem")?);
let cert_der = certs(&mut reader).next().unwrap()?;
// cert_der: CertificateDer
5. Реальный практический пример (100-200 строк кода)
Создадим полноценный HTTPS-сервер, который обслуживает статические файлы и использует самоподписанный сертификат. Этот пример демонстрирует работу с асинхронным вводом-выводом, обработку ошибок и базовую маршрутизацию.
// src/main.rs (полный пример сервера)
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
/// Генерирует самоподписанный сертификат и ключ (для демонстрации)
fn generate_self_signed_cert() -> (Vec>, PrivateKeyDer<'static>) {
use rcgen::{CertificateParams, KeyPair, IsCa, BasicConstraints};
use time::OffsetDateTime;
let key_pair = KeyPair::generate().unwrap();
let mut params = CertificateParams::new(vec!["localhost".to_string()]).unwrap();
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
params.not_before = OffsetDateTime::now_utc();
params.not_after = OffsetDateTime::now_utc() + time::Duration::days(365);
let cert = params.self_signed(&key_pair).unwrap();
let cert_der = CertificateDer::from(cert.der().to_vec());
let key_der = PrivateKeyDer::try_from(key_pair.serialize_der()).unwrap();
(vec![cert_der], key_der)
}
#[tokio::main]
async fn main() -> Result<(), Box> {
// 1. Генерируем или загружаем сертификат
let (certs, key) = generate_self_signed_cert();
// 2. Создаём конфигурацию сервера
let config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
let acceptor = TlsAcceptor::from(Arc::new(config));
// 3. Запускаем TCP-слушатель
let listener = TcpListener::bind("127.0.0.1:4433").await?;
println!("Сервер запущен на https://127.0.0.1:4433");
loop {
let (tcp_stream, addr) = listener.accept().await?;
let acceptor = acceptor.clone();
tokio::spawn(async move {
if let Err(e) = handle_connection(acceptor, tcp_stream, addr).await {
eprintln!("Ошибка при обработке {}: {}", addr, e);
}
});
}
}
async fn handle_connection(
acceptor: TlsAcceptor,
tcp_stream: tokio::net::TcpStream,
addr: std::net::SocketAddr,
) -> Result<(), Box> {
println!("Новое соединение от: {}", addr);
// 4. Выполняем TLS handshake
let mut tls_stream = acceptor.accept(tcp_stream).await?