Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第五十章 TLS/SSL 协议

互联网的安全基石:从握手到加密通信

TLS(Transport Layer Security,传输层安全协议)及其前身 SSL(Secure Sockets Layer)是互联网上最广泛使用的安全通信协议。每当你在浏览器中看到那把绿色的小锁,背后就是 TLS 在保护你的数据不被窃听和篡改。本章将深入 TLS 协议的核心机制,并展示如何在 Rust 中构建安全的网络通信。

50.1 TLS 概述

为什么需要 TLS

在 TLS 出现之前,HTTP、SMTP、FTP 等协议以明文传输数据,面临三大安全威胁:

  • 窃听(Eavesdropping):攻击者可以读取传输中的敏感信息
  • 篡改(Tampering):攻击者可以修改传输中的数据
  • 冒充(Impersonation):攻击者可以伪装成通信的另一方

TLS 通过以下机制解决这些问题:

  • 加密:使用对称加密保护数据传输的机密性
  • 消息认证码(MAC):确保数据未被篡改
  • 数字证书:验证通信双方的身份

TLS 与 SSL 的版本演进

版本年份状态说明
SSL 2.01995已废弃存在严重安全漏洞
SSL 3.01996已废弃POODLE 攻击使其不再安全
TLS 1.01999已废弃等同于 SSL 3.1
TLS 1.12006已废弃增强了 CBC 模式安全性
TLS 1.22008广泛使用当前主流版本,支持 AEAD 加密
TLS 1.32018推荐使用大幅简化握手,提升性能和安全性

现代应用应使用 TLS 1.2 或 TLS 1.3,并禁用所有早期版本。

TLS 协议栈位置

应用层(HTTP、SMTP、MQTT...)
    ↑
TLS 层(握手、加密、认证)
    ↑
传输层(TCP)
    ↑
网络层(IP)

TLS 位于应用层和传输层之间,对应用层透明。HTTP + TLS = HTTPS,SMTP + TLS = SMTPS,以此类推。

50.2 TLS 握手过程

TLS 握手是建立安全连接的关键步骤,客户端和服务器在此过程中协商加密参数、验证身份并生成会话密钥。

TLS 1.2 握手(完整版)

TLS 1.2 握手通常需要 2-RTT(两个往返时延):

客户端                                    服务器
  |                                         |
  |  -------- ClientHello -------->        |
  |  [支持的 TLS 版本、密码套件列表、        |
  |   随机数、会话 ID、扩展]                |
  |                                         |
  |  <------- ServerHello ---------        |
  |  [选定的 TLS 版本、密码套件、           |
  |   服务器随机数]                         |
  |                                         |
  |  <------- Certificate -----------      |
  |  [服务器证书链]                         |
  |                                         |
  |  <------- ServerKeyExchange ----       |
  |  [密钥交换参数,如 DH/ECDH 公钥]        |
  |                                         |
  |  <------- ServerHelloDone ------       |
  |                                         |
  |  -------- ClientKeyExchange --->       |
  |  [预主密钥,用服务器公钥加密]            |
  |                                         |
  |  -------- ChangeCipherSpec --->        |
  |  [通知后续消息将加密]                   |
  |                                         |
  |  -------- Finished ------------->      |
  |  [加密的消息认证码,验证握手完整性]      |
  |                                         |
  |  <------- ChangeCipherSpec -----       |
  |                                         |
  |  <------- Finished --------------      |
  |                                         |
  |========= 加密应用数据传输 ==========|

关键步骤解析

  1. ClientHello:客户端发起握手,提供支持的协议版本、密码套件列表、压缩方法、客户端随机数(32 字节)
  2. ServerHello:服务器选择协议版本和密码套件,发送服务器随机数
  3. Certificate:服务器发送证书链,客户端验证证书
  4. ServerKeyExchange:对于 DHE/ECDHE 密钥交换,发送临时公钥
  5. ClientKeyExchange:客户端生成预主密钥(Pre-Master Secret),用服务器公钥加密发送
  6. Finished:双方用协商的密钥发送验证消息,确认握手成功

TLS 1.3 握手(简化版)

TLS 1.3 将握手优化到 1-RTT,甚至在会话恢复时实现 0-RTT

客户端                                    服务器
  |                                         |
  |  -------- ClientHello -------->        |
  |  [支持的密钥共享、协议版本、             |
  |   客户端随机数、扩展]                   |
  |                                         |
  |  <------- ServerHello ---------        |
  |  [选定的密钥共享、服务器随机数]          |
  |                                         |
  |  <------- {EncryptedExtensions}        |
  |                                         |
  |  <------- {Certificate} -----------    |
  |                                         |
  |  <------- {CertificateVerify} ----     |
  |  [服务器签名验证]                       |
  |                                         |
  |  <------- {Finished} --------------    |
  |                                         |
  |  -------- {Finished} ------------->    |
  |                                         |
  |========= 加密应用数据传输 ==========|

TLS 1.3 的关键改进:

  • 移除过时算法:MD5、SHA-1、RC4、CBC 模式、RSA 密钥交换等全部移除
  • 1-RTT 握手:客户端在 ClientHello 中猜测密钥交换参数,减少一次往返
  • 0-RTT 会话恢复:基于 PSK(Pre-Shared Key)实现零往返恢复
  • 加密更多握手消息:Certificate 等消息在 TLS 1.3 中已加密
  • 前向安全性:仅支持提供前向安全的密钥交换(ECDHE)

会话恢复与会话票证

完整握手涉及公钥运算,计算开销较大。TLS 支持会话恢复机制:

TLS 1.2 会话 ID

第一次握手:
  ClientHello (session_id = []) →
  ← ServerHello (session_id = 0x1234...)

后续连接:
  ClientHello (session_id = 0x1234...) →
  ← ServerHello (session_id = 0x1234...)
  ← ChangeCipherSpec
  ← Finished
  Finished →

TLS 1.2/1.3 会话票证(Session Ticket)

服务器将会话状态加密后作为“票证“发给客户端,客户端在后续连接中出示票证即可恢复会话。这减轻了服务器的状态存储负担。

50.3 密码套件协商

什么是密码套件

密码套件(Cipher Suite)定义了 TLS 连接使用的完整加密方案,包括:

  • 密钥交换算法:如何协商共享密钥(RSA、DHE、ECDHE)
  • 身份验证算法:如何验证身份(RSA、ECDSA、Ed25519)
  • 对称加密算法:如何加密数据(AES-GCM、ChaCha20-Poly1305)
  • 消息认证算法:如何验证消息完整性(AEAD 内置、HMAC)

TLS 1.2 密码套件示例:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

  • ECDHE:密钥交换使用椭圆曲线 Diffie-Hellman(临时)
  • RSA:身份验证使用 RSA 签名
  • AES_128_GCM:对称加密使用 AES-128 GCM 模式
  • SHA256:HKDF 和握手消息哈希使用 SHA-256

TLS 1.3 简化了密码套件表示:TLS_AES_256_GCM_SHA384TLS_CHACHA20_POLY1305_SHA256

推荐的密码套件

TLS 1.3(全部安全,按优先级排序)

  1. TLS_AES_256_GCM_SHA384
  2. TLS_CHACHA20_POLY1305_SHA256
  3. TLS_AES_128_GCM_SHA256

TLS 1.2(仅推荐以下)

  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
  • TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256

应避免:任何使用 RSA 密钥交换(无前向安全)、CBC 模式、RC4、DES/3DES 的密码套件。

前向安全性(Forward Secrecy)

前向安全性确保即使服务器的长期私钥在未来泄露,过去的通信记录也不会被解密。

无前向安全(RSA 密钥交换):
  预主密钥 = 用服务器 RSA 公钥加密
  如果服务器私钥泄露 → 所有历史会话可解密

有前向安全(ECDHE 密钥交换):
  临时 ECDH 密钥对,握手后即丢弃
  即使服务器私钥泄露 → 历史会话仍安全

现代 TLS 应始终使用 ECDHE 或 DHE 密钥交换。

50.4 证书验证

TLS 中的证书验证流程

1. 服务器发送证书链
2. 客户端验证:
   a. 证书链完整性(每个证书由上一级签名)
   b. 根证书在信任库中
   c. 证书未过期
   d. 证书未被吊销(CRL/OCSP)
   e. 证书中的域名匹配访问的域名
   f. 证书用途适合 TLS 服务器认证
3. 验证服务器签名(CertificateVerify)

主机名验证

证书中的主体备用名称(SAN)必须包含访问的域名:

访问 https://www.example.com

证书必须包含:
  DNS:www.example.com
或
  DNS:*.example.com(通配符)

证书固定(Certificate Pinning)

为了防止 rogue CA 签发伪造证书,应用可以“固定“预期的证书或公钥:

#![allow(unused)]
fn main() {
// 概念:只信任特定的证书指纹
const EXPECTED_CERT_FINGERPRINT: &[u8] = b"SHA256:abc123...";

fn verify_pinned_cert(cert: &Certificate) -> Result<(), Error> {
    let fingerprint = sha256(cert.raw_bytes());
    if fingerprint == EXPECTED_CERT_FINGERPRINT {
        Ok(())
    } else {
        Err(Error::UntrustedCertificate)
    }
}
}

HTTP Public Key Pinning(HPKP)曾用于 Web,但因风险过高已被废弃。移动应用仍常用证书固定。

50.5 Rust 实现:TLS 安全连接

使用 rustls 建立 TLS 客户端连接

rustls 是 Rust 生态中最成熟、最安全的 TLS 实现,默认只启用安全的协议版本和密码套件。

# Cargo.toml
[dependencies]
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
rustls-pemfile = "2"
webpki-roots = "0.26"
#![allow(unused)]
fn main() {
use rustls::{ClientConfig, RootCertStore, ServerName};
use rustls_pemfile;
use std::net::TcpStream;
use std::sync::Arc;
use std::io::{Read, Write};

fn create_https_client() -> Arc<ClientConfig> {
    // 加载 Mozilla 根证书
    let mut root_store = RootCertStore::empty();
    root_store.extend(
        webpki_roots::TLS_SERVER_ROOTS
            .iter()
            .cloned()
    );

    // 创建客户端配置
    let config = ClientConfig::builder()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    Arc::new(config)
}

fn fetch_https(url: &str) -> Result<String, Box<dyn std::error::Error>> {
    let config = create_https_client();

    // 解析域名和端口
    let server_name = "www.rust-lang.org".try_into()?;
    let server_name = ServerName::try_from("www.rust-lang.org")?;

    // 建立 TCP 连接
    let tcp_stream = TcpStream::connect("www.rust-lang.org:443")?;

    // 包装为 TLS 连接
    let mut tls_stream = rustls::ClientConnection::new(config, server_name)?;
    let mut stream = rustls::Stream::new(&mut tls_stream, &mut tcp_stream);

    // 发送 HTTP 请求
    let request = "GET / HTTP/1.1\r\n\
                   Host: www.rust-lang.org\r\n\
                   Connection: close\r\n\r\n";
    stream.write_all(request.as_bytes())?;

    // 读取响应
    let mut response = String::new();
    stream.read_to_string(&mut response)?;

    Ok(response)
}
}

使用 tokio-rustls 实现异步 TLS

在生产环境中,我们通常使用异步 I/O。tokio-rustlsrustls 与 Tokio 集成:

[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-rustls = "0.26"
rustls-pemfile = "2"
webpki-roots = "0.26"
use tokio::net::TcpStream;
use tokio_rustls::{TlsConnector, rustls::{ClientConfig, RootCertStore, ServerName}};
use std::sync::Arc;

async fn https_get(host: &str, path: &str) -> Result<String, Box<dyn std::error::Error>> {
    // 创建 TLS 配置
    let mut root_store = RootCertStore::empty();
    root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());

    let config = ClientConfig::builder()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    let connector = TlsConnector::from(Arc::new(config));

    // 建立 TCP 连接
    let tcp_stream = TcpStream::connect(format!("{}:443", host)).await?;

    // TLS 握手
    let server_name = ServerName::try_from(host)?;
    let mut tls_stream = connector.connect(server_name, tcp_stream).await?;

    // 发送 HTTP 请求
    let request = format!(
        "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
        path, host
    );
    tokio::io::AsyncWriteExt::write_all(&mut tls_stream, request.as_bytes()).await?;

    // 读取响应
    let mut response = Vec::new();
    tokio::io::AsyncReadExt::read_to_end(&mut tls_stream, &mut response).await?;

    Ok(String::from_utf8_lossy(&response).to_string())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = https_get("www.rust-lang.org", "/").await?;
    println!("响应长度: {} 字节", response.len());
    println!("前 500 字符:\n{}", &response[..response.len().min(500)]);
    Ok(())
}

构建 TLS 服务器

#![allow(unused)]
fn main() {
use tokio::net::TcpListener;
use tokio_rustls::{TlsAcceptor, rustls::{ServerConfig, Certificate, PrivateKey}};
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;

fn load_certs(path: &str) -> Vec<Certificate> {
    let file = File::open(path).expect("无法打开证书文件");
    let mut reader = BufReader::new(file);
    rustls_pemfile::certs(&mut reader)
        .filter_map(|r| r.ok())
        .collect()
}

fn load_key(path: &str) -> PrivateKey {
    let file = File::open(path).expect("无法打开密钥文件");
    let mut reader = BufReader::new(file);
    rustls_pemfile::pkcs8_private_keys(&mut reader)
        .filter_map(|r| r.ok())
        .next()
        .expect("未找到私钥")
        .into()
}

async fn run_tls_server() -> Result<(), Box<dyn std::error::Error>> {
    let certs = load_certs("server.crt");
    let key = load_key("server.key");

    let config = ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_single_cert(certs, key)?;

    let acceptor = TlsAcceptor::from(Arc::new(config));
    let listener = TcpListener::bind("127.0.0.1:8443").await?;

    println!("TLS 服务器监听于 https://127.0.0.1:8443");

    loop {
        let (stream, peer_addr) = listener.accept().await?;
        let acceptor = acceptor.clone();

        tokio::spawn(async move {
            match acceptor.accept(stream).await {
                Ok(mut tls_stream) => {
                    println!("来自 {} 的安全连接已建立", peer_addr);

                    // 简单的 HTTP 响应
                    let response = "HTTP/1.1 200 OK\r\n\
                                    Content-Type: text/plain\r\n\
                                    Content-Length: 13\r\n\r\n\
                                    Hello, TLS!\n";
                    let _ = tokio::io::AsyncWriteExt::write_all(
                        &mut tls_stream, response.as_bytes()
                    ).await;
                }
                Err(e) => {
                    eprintln!("TLS 握手失败 ({}): {}", peer_addr, e);
                }
            }
        });
    }
}
}

配置客户端证书(mTLS)

双向 TLS(mutual TLS,mTLS)要求客户端也提供证书,服务器验证客户端身份:

#![allow(unused)]
fn main() {
use rustls::{ClientConfig, RootCertStore};

fn create_mtls_client(
    ca_cert_path: &str,
    client_cert_path: &str,
    client_key_path: &str,
) -> Result<Arc<ClientConfig>, Box<dyn std::error::Error>> {
    // 加载 CA 证书(用于验证服务器)
    let mut root_store = RootCertStore::empty();
    let ca_file = std::fs::File::open(ca_cert_path)?;
    let mut ca_reader = std::io::BufReader::new(ca_file);
    let ca_certs: Vec<_> = rustls_pemfile::certs(&mut ca_reader)
        .filter_map(|r| r.ok())
        .collect();
    for cert in ca_certs {
        root_store.add(cert)?;
    }

    // 加载客户端证书和私钥
    let client_certs = load_certs(client_cert_path);
    let client_key = load_key(client_key_path);

    let config = ClientConfig::builder()
        .with_root_certificates(root_store)
        .with_client_auth_cert(client_certs, client_key)?;

    Ok(Arc::new(config))
}
}

检查 TLS 连接信息

#![allow(unused)]
fn main() {
use rustls::ClientConnection;

fn inspect_connection(conn: &ClientConnection) {
    if let Some(cipher) = conn.negotiated_cipher_suite() {
        println!("协商的密码套件: {:?}", cipher.suite());
    }

    if let Some(version) = conn.protocol_version() {
        println!("TLS 版本: {:?}", version);
    }

    if let Some(peer_certs) = conn.peer_certificates() {
        println!("对等端证书数量: {}", peer_certs.len());
        for (i, cert) in peer_certs.iter().enumerate() {
            println!("  证书 {}: {} 字节", i, cert.len());
        }
    }

    println!("握手完成: {}", conn.is_handshaking());
}
}

50.6 TLS 安全最佳实践

服务器配置检查清单

  • 仅启用 TLS 1.2 和 TLS 1.3
  • 禁用所有不安全的密码套件
  • 优先使用 ECDHE 密钥交换(提供前向安全)
  • 使用 AES-GCM 或 ChaCha20-Poly1305(AEAD 模式)
  • 配置完整的证书链(包含中间证书)
  • 启用 OCSP Stapling
  • 配置 HSTS(HTTP Strict Transport Security)
  • 定期更新证书(Let’s Encrypt 90 天周期)

常见工具

# 检查服务器 TLS 配置
openssl s_client -connect example.com:443 -tls1_3

# 详细分析 TLS 配置
nmap --script ssl-enum-ciphers -p 443 example.com

# 测试 SSL/TLS 安全性
https://www.ssllabs.com/ssltest/

50.7 本章总结

概念说明Rust 实现
TLS 握手协商加密参数、验证身份、生成密钥rustls 自动处理
密码套件定义密钥交换、加密、认证算法组合rustls 默认仅启用安全套件
前向安全长期密钥泄露不暴露历史会话使用 ECDHE,rustls 默认开启
证书验证验证证书链、主机名、有效期rustls + webpki 自动验证
会话恢复避免重复完整握手开销rustls 支持会话票证
mTLS双向证书认证rustls 支持客户端证书

练习建议

  1. 基础练习:使用 tokio-rustls 编写一个 HTTPS 客户端,访问 https://httpbin.org/get 并打印响应。

  2. 中级练习:搭建一个支持 TLS 1.3 的异步服务器,使用 rcgen 生成自签名证书,用 curl -v --insecure https://localhost:8443 测试连接。

  3. 高级练习:实现 mTLS 通信:生成 CA、服务器证书和客户端证书,配置服务器要求客户端证书,编写客户端提供证书连接。

  4. 实践项目:为你的 Rust Web 服务添加 HTTPS 支持,配置 HTTP/2 和 TLS 1.3,使用 Let’s Encrypt 自动获取证书(可结合 acme-microinstant-acme 库)。


密码学箴言:TLS 不是可选项,而是现代网络通信的底线。配置 TLS 时,默认安全比灵活配置更重要——rustls 的设计哲学正是如此。