stunnel实现TCP双向认证加密
在一个 TCP 端口上实现“双向加密认证(mutual TLS / 双向 TLS)”,用来对客户端和服务器双方都做加密与证书验证——最常见、最简单的做法就是用 stunnel(或同类 TLS 包装器)来把普通 TCP 服务“套上”TLS,并把双方证书互相校验。下面按步骤走就能把任意 TCP 服务封装成双向认证的 TLS 服务。引用与细节参照 stunnel 官方文档与实务指南。(stunnel, OPNsense 文档)
1) 总体思路(一句话)
用一个自建 CA 签发服务器证书和客户端证书,服务器端的 stunnel 要呈现(server)证书并要求验证客户端证书;客户端的 stunnel 要提供(client)证书并验证服务器证书。stunnel 在本地监听 TLS 端口(accept),解密后把流量转发(connect)到目标内部服务端口(或反过来)。(stunnel)
2) 生成 CA / 服务器证书 / 客户端证书(OpenSSL 快速示例)
下面命令适用于测试或私有环境(生产请用更严格的 openssl.cnf 与密码管理):
# 1) 生成自签 CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -days 3650 -subj "/CN=MyLocalCA" -out ca.crt# 2) 生成服务器私钥 + CSR,然后用 CA 签发
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=server.example.com" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt# 组合为 server.pem(stunnel 要求 cert+chain)
cat server.crt ca.crt > server.pem# 3) 客户端证书(同样方式)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=client1" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out client.crt
cat client.crt ca.crt > client.pem
要点:
-
stunnel 要的
cert
文件通常是 PEM 格式,server 端需要包含证书链(从 server 证书 到 root CA)。(stunnel)
3) stunnel 配置示例(Server & Client)
服务器端(作为 TLS 服务器,前端暴露 TLS 端口,把解密后的流量转到本地服务端口 8080)
配置文件(例如 /etc/stunnel/stunnel.conf
):
; 全局选项
foreground = yes
pid = /var/run/stunnel.pid
debug = 3[myservice]
accept = 0.0.0.0:4433 ; 对外的 TLS 端口
connect = 127.0.0.1:8080 ; 本地后端服务(明文)
cert = /etc/stunnel/server.pem
key = /etc/stunnel/server.key
CAfile = /etc/stunnel/ca.crt
verify = 2
options = NO_SSLv2
客户端(作为 TLS 客户端,客户本地监听一个本地端口 127.0.0.1:12345,应用连接本地端口,stunnel 与远端 TLS 服务建立加密连接)
client = yes[myservice-client]
accept = 127.0.0.1:12345 ; 应用连接这个本地端口(明文)
connect = server.example.com:4433
cert = /etc/stunnel/client.pem
key = /etc/stunnel/client.key
CAfile = /etc/stunnel/ca.crt
verify = 2
options = NO_SSLv2
要点说明:
-
client = yes
必须在客户端配置中设置(或在服务节里client = yes
)。 -
cert
/key
:各自提供自己的证书和私钥(用于向对端证明身份)。 -
CAfile
:用于验证对端证书(包含签发链或根 CA)。 -
verify
:控制验证级别(下节详细解释)。(stunnel)
4) verify
级别与常见配置陷阱
stunnel 的 verify
/-v
决定如何校验对端证书(常用值):
-
verify =
(未设置):不验证对端证书(不安全)。 -
verify = 1
:如果对方提供证书则验证(允许无证书的一侧连接)。 -
verify = 2
:强制验证对端证书是否由CAfile
中的 CA 签发(常用)。 -
verify = 3
:除了 CA 验证,还要求在本地存有对端证书(更严格的本地匹配)。有时用于把某个具体证书放入本地列表并比对。(Debian Manpages, stunnel)
常见坑:
-
stunnel 要求
CAfile
/证书链格式正确且顺序从 leaf 到 root;否则验证会失败。(stunnel) -
生产环境通常用
verify = 2
(验证链)或者使用CRL/OCSP
更严格管理吊销。 -
如果使用第三方 CA(非自签),客户端的
CAfile
要包含该 CA 的根证书。
5) 测试方法(排错必备)
-
用
openssl s_client
测试 TLS 连接并观察证书信息:
# 测试服务器端(查看服务器证书)
openssl s_client -connect server.example.com:4433 -showcerts# 测试需要客户端证书的情况(提供 client.pem)
openssl s_client -connect server.example.com:4433 -cert client.crt -key client.key -CAfile ca.crt
-
在服务器端查看 stunnel 日志(
debug = 3
)可以看到证书验证失败的原因(链不完整、CN 不对、过期等)。 -
确认私钥文件权限(stunnel 运行用户需要读取 key),并防止私钥泄露。
6) 生产建议与替代方案
-
生产:使用短有效期证书 + 自动更新(或用 PKI),并考虑 CRL/OCSP。
-
替代:如果你只需要简单的 TLS 包装,可以用
nginx stream
(支持 mutual TLS)、haproxy
或ghostunnel
(专注于单向/双向 TLS 的轻量守护进程)。如果需要更复杂的流量管理,nginx/haproxy 可能更合适。(负载均衡器, ArchWiki)
7) 简短清单(部署检查点)
-
用 OpenSSL 生成 CA、server/client 证书并确认 PEM 格式和链完整。
-
server.pem 包含 server.crt + CA(链),client.pem 同理(client 包含自己的 cert + CA)。
-
server side stunnel:
cert
/key
指向 server,CAfile
指向 ca.crt,verify = 2/3
强制验证客户端。 -
client side stunnel:
client = yes
,cert
/key
指向 client,CAfile
指向 ca.crt。 -
使用
openssl s_client
+ stunnel 日志排错。 -
防火墙放通 stunnel 的 accept 端口;后端服务端口只允许本地访问或 stunnel 所在主机访问。(stunnel)
第三方客户端生成 PKCS#12 文件
生成 PKCS#12 文件
# 生成 client.p12,包含 client.crt + client.key + ca.crt
openssl pkcs12 -export \-inkey client.key \-in client.crt \-certfile ca.crt \-name "client-cert" \-out client.p12
命令解释:
-
-inkey client.key
:指定私钥 -
-in client.crt
:指定客户端证书 -
-certfile ca.crt
:把 CA 根证书也放进去 -
-name "client-cert"
:别名(在应用里显示的名字,可以自定义) -
-out client.p12
:输出的 p12 文件 -
执行时会提示你输入一个 导出密码(客户端导入时需要输入这个密码)
可以用下面命令确认 client.p12
里包含了三部分内容(证书链 + 私钥):
openssl pkcs12 -info -in client.p12
会显示:
-
Certificate chain(含客户端证书 + 根证书)
-
Private key(客户端私钥)