常见的 HTTPS 只验证服务器;双向 TLS(mTLS)让客户端也出示证书,服务器据此知道“你是谁”,在服务之间的通信和零信任网络里很常用。这篇在本机用脚本生成 CA 和几张客户端证书,把握手的成功和各种失败、线上的可见性以及额外的代价逐个量一遍。
CA:CN=w94 Test CA,CA=True;服务器证书:CN=localhost,扩展 serverAuth,SAN localhost;客户端证书 alice:extendedKeyUsage = clientAuth,签发者 w94 Test CA mallory 的证书由 "CN=w94 Other CA" 签发(服务器不信任这个 CA);bob 的证书有效期 10-05 ~ 10-07,已经过期
CA、服务器证书、有效 / 不被信任 / 已过期的客户端证书,全是临时生成的。Python 3.13 起对缺少 AKI / SKI 的证书严格拒绝。
alice(有效的客户端证书) → 客户端:握手 1.7 ms 成功,收到 'hello alice@w94';服务器记录:('ok', 'alice@w94')
不带任何客户端证书 → 客户端:握手 1.1 ms 看起来成功,但第一次读就失败:SSLError [SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate requ;服务器记录:('fail', 'SSLError: [SSL: PEER_DID_NOT_RETURN_A_CERTIFICATE] peer did not return')
mallory(由不被信任的 CA 签发) → 客户端:握手 1.1 ms 看起来成功,但第一次读就失败:SSLError [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2713);服务器记录:('fail', 'SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ')
bob(证书已过期) → 客户端:握手 1.1 ms 看起来成功,但第一次读就失败:SSLError [SSL: SSLV3_ALERT_CERTIFICATE_EXPIRED] ssl/tls alert certificate expir;服务器记录:('fail', 'SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ')有效证书成功;没证书、不被信任、已过期在 TLS 1.3 里都是第一次读才失败,错误码分别是 certificate required / unknown ca / certificate expired。
alice → 客户端:握手 1.0 ms 成功,收到 'hello alice@w94';服务器记录:('ok', 'alice@w94')
不带证书 → 客户端:握手 1.2 ms 成功,收到 'hello anonymous';服务器记录:('ok', None)
mallory → 客户端:握手 1.1 ms 看起来成功,但第一次读就失败:SSLError [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2713);服务器记录:('fail', 'SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ')不出示可以进(身份 None);出示无效证书仍然失败。
TLS 1.2:客户端→服务器 1232 字节:['ClientHello', 'Certificate', 'ClientKeyExchange', 'CertificateVerify', 'ChangeCipherSpec', 190, '(加密记录 26 字节)'];服务器→客户端 1824 字节:['ServerHello', 'Certificate', 'ServerKeyExchange', 'CertificateRequest', 'ServerHelloDone', 'NewSessionTicket', 'ChangeCipherSpec', 201, '(加密记录 39 字节)'] TLS 1.3:客户端→服务器 2583 字节:['ClientHello', 'ChangeCipherSpec', '(加密记录 860 字节)', '(加密记录 95 字节)', '(加密记录 69 字节)', '(加密记录 19 字节)'];服务器→客户端 3800 字节:['ServerHello', 'ChangeCipherSpec', '(加密记录 23 字节)', '(加密记录 70 字节)', '(加密记录 884 字节)', '(加密记录 97 字节)', '(加密记录 69 字节)', '(加密记录 682 字节)', '(加密记录 682 字节)', '(加密记录 32 字节)'] TLS 1.2 里服务器的 CertificateRequest 和客户端的 Certificate / CertificateVerify 都是明文握手消息,路径上的设备能直接读出客户端证书(包括里面的身份);TLS 1.3 把它们放进了加密记录里
TLS 1.2 里客户端证书明文传输,TLS 1.3 里加密。
单向 TLS(不要客户端证书) :握手耗时 中位 0.72 ms,p90 0.78 ms 双向 TLS(客户端出示证书) :握手耗时 中位 0.87 ms,p90 0.99 ms 双向认证比单向多约 0.15 ms(21%):客户端要多发一个证书 + 一个 CertificateVerify 签名,服务器要多验证一条证书链和一个签名(本机 lo0 上没有网络延迟,这个差就是纯计算和消息处理)
双向比单向每次握手多约 0.15 ms(回环上)。