强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 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.0PCI DSS 不再允许
TLS 1.1⚠️ 过时--tlsv1.1PCI 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 certificateCA 证书缺失--cacert 或更新系统 CA
SSL: no alternative certificate subject name matches域名不匹配检查证书 SAN
OpenSSL SSL_connect: Connection resetTLS 版本不兼容尝试不同 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

注意事项

  1. 永远不要在生产环境使用 -k:它会完全禁用证书验证
  2. 保持系统 CA 证书更新apt update && apt install ca-certificates
  3. TLS 1.3 是首选:更快的握手、更强的安全性
  4. 证书固定需谨慎:证书轮换时需同步更新固定值
  5. 自签名证书用 --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,包括变量管理、配置文件和错误处理。