第 10 章:TLS/SSL 安全
第 10 章:TLS/SSL 安全
TLS(Transport Layer Security)是 HTTPS 的安全基石。正确配置 TLS 不仅关乎安全,还影响兼容性和性能。
10.1 TLS/SSL 基础
TLS 版本对比
| 版本 | 状态 | curl 选项 | 特点 |
|---|---|---|---|
| SSLv2 | ❌ 已废弃(不安全) | --sslv2 | 不应使用 |
| SSLv3 | ❌ 已废弃(POODLE 攻击) | --sslv3 | 不应使用 |
| TLS 1.0 | ⚠️ 过时 | --tlsv1.0 | PCI DSS 不再允许 |
| TLS 1.1 | ⚠️ 过时 | --tlsv1.1 | PCI DSS 不再允许 |
| TLS 1.2 | ✅ 广泛支持 | --tlsv1.2 | 最低安全标准 |
| TLS 1.3 | ✅ 推荐 | --tlsv1.3 | 更快、更安全 |
# 强制使用 TLS 1.3
curl --tlsv1.3 https://example.com
# 强制使用 TLS 1.2
curl --tlsv1.2 https://example.com
# 最低使用 TLS 1.2(curl 7.34+)
curl --tlsv1.2 https://example.com
# 查看服务器支持的 TLS 版本
curl -v https://example.com 2>&1 | grep "SSL connection"
# SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
10.2 证书验证
默认验证行为
curl 默认使用系统的 CA 证书存储来验证服务器证书:
# 正常验证(默认行为)
curl https://example.com
# 查看证书验证结果
curl -v https://example.com 2>&1 | grep -E "certificate|verify|SSL"
# * SSL certificate verify ok.
跳过证书验证
# 跳过证书验证(仅用于测试!)
curl -k https://self-signed.example.com
# 或
curl --insecure https://self-signed.example.com
# ⚠️ 绝对不要在生产环境中使用 -k
# 它会禁用所有证书验证,容易遭受中间人攻击
自定义 CA 证书
# 使用自定义 CA 证书
curl --cacert /path/to/ca.crt https://internal.example.com
# 使用自定义 CA 证书目录
curl --capath /path/to/ca-certs/ https://internal.example.com
# 指定系统 CA 证书路径(编译时设置)
# ./configure --with-ca-bundle=/etc/ssl/certs/ca-certificates.crt
查看服务器证书信息
# 查看完整证书链
curl -vI https://example.com 2>&1 | grep -A 20 "Server certificate"
# 输出示例:
# * Server certificate:
# * subject: CN=example.com
# * start date: Jan 1 00:00:00 2025 GMT
# * expire date: Dec 31 23:59:59 2025 GMT
# * issuer: C=US; O=Let's Encrypt; CN=R3
# * SSL certificate verify ok.
# 使用 openssl 查看更详细的证书信息
curl -sI https://example.com | head -1
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer
10.3 客户端证书
# 使用客户端证书和私钥
curl --cert client.crt --key client.key https://api.example.com/secure
# 证书和私钥在同一个 PEM 文件中
curl --cert combined.pem https://api.example.com/secure
# 使用 PKCS#12 格式
curl --cert client.p12 --cert-type P12 https://api.example.com/secure
# 私钥有密码保护
curl --cert client.crt --key client.key --pass "key-password" \
https://api.example.com/secure
# 使用 PKCS#11 硬件令牌
curl --cert "pkcs11:object=My%20Certificate" \
https://api.example.com/secure
10.4 自签名证书处理
开发/测试环境
# 方法 1:跳过验证(仅限开发环境)
curl -k https://dev.example.com/api
# 方法 2:信任自签名 CA 证书(推荐)
curl --cacert ca.crt https://dev.example.com/api
# 方法 3:导入 CA 证书到系统信任存储
# Ubuntu/Debian
sudo cp ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# CentOS/RHEL
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
# macOS
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ca.crt
# 然后正常访问
curl https://dev.example.com/api
快速生成测试证书
# 生成自签名 CA 证书
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 \
-keyout ca.key -out ca.crt \
-subj "/CN=My Test CA" \
-nodes
# 生成服务器证书(由 CA 签发)
openssl req -newkey rsa:2048 -nodes -keyout server.key \
-out server.csr \
-subj "/CN=dev.example.com"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 365 \
-extfile <(echo "subjectAltName=DNS:dev.example.com,IP:127.0.0.1")
# 使用生成的证书
curl --cacert ca.crt https://dev.example.com/api
10.5 HSTS(HTTP Strict Transport Security)
# HSTS 头的含义
curl -sI https://example.com | grep -i strict-transport-security
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# curl 默认不缓存 HSTS 策略
# 使用 --hsts 启用 HSTS 缓存
curl --hsts hsts-cache.txt https://example.com
# 首次访问(记录 HSTS 策略)
curl --hsts hsts-cache.txt https://example.com
# 后续访问(自动升级到 HTTPS)
curl --hsts hsts-cache.txt http://example.com
# curl 会自动转换为 https://example.com
# HSTS 缓存文件格式
cat hsts-cache.txt
# example.com "20270510 12:00:00" includeSubDomains
10.6 密码套件控制
# 查看服务器支持的密码套件
curl -v https://example.com 2>&1 | grep "Cipher is"
# * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
# 使用 openssl 查看完整密码套件列表
echo | openssl s_client -connect example.com:443 2>/dev/null \
| grep -E "Protocol|Cipher"
# 指定密码套件(TLS 1.2 及以下)
curl --ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" \
https://example.com
# 指定 TLS 1.3 密码套件(curl 7.61+)
curl --tls13-ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" \
https://example.com
# 列出 curl 支持的密码套件
curl --ciphers help 2>&1 || true
# 禁用不安全的密码(安全加固)
curl --ciphers "DEFAULT:!RC4:!DES:!3DES:!MD5" https://example.com
密码套件推荐配置
| 场景 | 推荐配置 |
|---|---|
| 最高兼容性 | 默认配置即可 |
| 安全优先 | --tlsv1.2 --ciphers "ECDHE+AESGCM" |
| TLS 1.3 最佳 | --tlsv1.3(自动选择最佳套件) |
| 性能优先 | --tls13-ciphers "TLS_CHACHA20_POLY1305_SHA256" |
10.7 证书固定(Certificate Pinning)
# 使用 --pinnedpubkey 固定服务器公钥
# 方式 1:固定证书的 SHA256 哈希
curl --pinnedpubkey="sha256//YhKJG1O3T..." https://example.com
# 方式 2:固定证书文件
curl --pinnedpubkey=/path/to/pinned.pub https://example.com
# 获取服务器证书的公钥哈希
openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| base64
# 输出类似:YhKJG1O3TjMmDj...
# 使用哈希
curl --pinnedpubkey="sha256//YhKJG1O3TjMmDj..." https://example.com
⚠️ 注意:证书固定需要谨慎使用。证书轮换时如果不更新固定值,会导致服务不可用。
10.8 OCSP Stapling 验证
# 查看 OCSP Stapling 状态
echo | openssl s_client -connect example.com:443 \
-status -servername example.com 2>/dev/null \
| grep -A 5 "OCSP response"
# curl 本身不直接控制 OCSP,但 TLS 后端会自动处理
# OpenSSL 默认启用 OCSP Stapling 验证
10.9 TLS 调试与诊断
连接诊断
# 查看完整的 TLS 握手过程
curl -vvv https://example.com 2>&1 | grep -E "SSL|TLS|cert|cipher|verify"
# 输出示例:
# * TLSv1.3 (OUT), TLS handshake, Client hello (1):
# * TLSv1.3 (IN), TLS handshake, Server hello (2):
# * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
# * TLSv1.3 (IN), TLS handshake, Certificate (11):
# * TLSv1.3 (IN), TLS handshake, Certificate Verify (15):
# * TLSv1.3 (IN), TLS handshake, Finished (20):
# * TLSv1.3 (OUT), TLS handshake, Finished (20):
# * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
# * Server certificate:
# * SSL certificate verify ok.
# 使用 openssl 更详细地诊断
openssl s_client -connect example.com:443 \
-servername example.com \
-tlsextdebug -status 2>&1
常见 TLS 错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
SSL certificate problem: self signed cert | 自签名证书 | --cacert 或 -k(仅测试) |
SSL certificate problem: certificate has expired | 证书过期 | 更新服务器证书 |
SSL certificate problem: unable to get local issuer certificate | CA 证书缺失 | --cacert 或更新系统 CA |
SSL: no alternative certificate subject name matches | 域名不匹配 | 检查证书 SAN |
OpenSSL SSL_connect: Connection reset | TLS 版本不兼容 | 尝试不同 TLS 版本 |
gnutls_handshake() failed | 密码套件不匹配 | --ciphers 指定套件 |
# 诊断证书域名不匹配
echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# X509v3 Subject Alternative Name:
# DNS:example.com, DNS:www.example.com
注意事项
- 永远不要在生产环境使用
-k:它会完全禁用证书验证 - 保持系统 CA 证书更新:
apt update && apt install ca-certificates - TLS 1.3 是首选:更快的握手、更强的安全性
- 证书固定需谨慎:证书轮换时需同步更新固定值
- 自签名证书用
--cacert:比-k更安全、更精确
# 安全检查脚本
check_tls() {
local host="$1"
echo "检查 $host 的 TLS 配置..."
# 检查证书有效期
expiry=$(echo | openssl s_client -connect "$host:443" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
echo "证书过期时间: $expiry"
# 检查 TLS 版本
echo | openssl s_client -connect "$host:443" 2>/dev/null \
| grep "Protocol" | head -1
# 检查密码套件
echo | openssl s_client -connect "$host:443" 2>/dev/null \
| grep "Cipher" | head -1
}
check_tls "example.com"
扩展阅读
📖 下一章:第 11 章:脚本编写 — 学习在 Shell 脚本中高效使用 curl,包括变量管理、配置文件和错误处理。