Charles代理转发TLS连接证书验证异常的技术咨询
TLS代理连接成功的原因分析与排查方向
核心现象回顾
- 你用Python 3.13+ssl库搭建的TLS服务器,配置了强制客户端认证,仅信任指定的
root.crt,同时禁用TLSv1/TLSv1.1、指定加密套件。 - 直接用
openssl s_client携带Charles CA分支的charles-client.crt连接服务器6000端口时,连接被拒绝;但通过配置了该证书的Charles代理(转发6001→6000),无需提供任何证书执行openssl s_client -connect localhost:6001 -state就能成功建立连接。
关键原因拆解
这个现象的核心是:Charles代理与客户端、代理与服务器之间是两个独立的TLS会话,两者的认证规则完全分离:
客户端(openssl s_client)与Charles代理的会话
你并未配置Charles代理要求客户端认证,所以openssl s_client无需提供任何证书,就能和代理建立TLS连接——这部分和后端服务器的认证规则无关。Charles代理与后端TLS服务器的会话
代理此时作为客户端,向服务器发送了charles-client.crt。连接能成功的可能性主要有以下几点:- 服务器实际信任Charles CA的证书链:
ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)会默认加载系统信任的根证书,后续调用context.load_verify_locations(cafile='root.crt')是追加信任CA而非替换。如果你的macOS系统中已经信任了Charles CA,服务器会同时信任root.crt和系统中的Charles CA,自然能验证charles-client.crt的合法性。 - Charles代理自动补充了完整证书链:当你在Charles中配置
charles-client.crt时,代理可能自动加载了对应的Charles CA中间证书和Root CA证书,向服务器发送了完整的证书链(charles-client.crt→Charles CA→Root CA)。而你直接用openssl s_client连接时,只发送了charles-client.crt叶子证书,缺少中间链,导致服务器无法完成验证。 - 对服务器信任配置的误解:可能
root.crt文件中实际包含了Charles CA的证书,或者Root CA本身就是Charles CA的签发者——服务器信任Root CA,因此能通过Root验证Charles CA签发的客户端证书。
- 服务器实际信任Charles CA的证书链:
具体排查步骤
- 验证服务器实际信任的CA列表:在服务器代码中添加打印逻辑,输出
context.get_ca_certs()的内容,确认是否包含Charles CA的证书信息。 - 抓包分析代理的证书链发送情况:用tcpdump或Wireshark抓取Charles代理到服务器6000端口的TLS流量,查看
Certificate消息中的证书链是否包含Charles CA和Root CA。 - 测试完整证书链的直接连接:执行命令
openssl s_client -connect localhost:6000 -cert charles-client.crt -key charles-client.key -CAfile root.crt -chain charles-ca.crt(替换charles-ca.crt为实际的Charles中间CA证书),验证是否是证书链缺失导致的直接连接失败。 - 确认服务器信任库的来源:临时移除
context.load_verify_locations(cafile='root.crt')代码,重启服务器后用Charles代理连接,看是否仍能成功——如果可以,说明服务器默认加载了系统信任的Charles CA。 - 检查Charles的证书配置:在Charles的客户端证书设置中,确认是否勾选了“Include intermediate certificates”或类似选项,查看代理实际使用的证书文件是否包含完整链。
内容的提问来源于stack exchange,提问作者Wisam Naji
相关产品推荐
相关产品推荐

