rustls для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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?                    

Рекомендаций