双向 TLS 里,服务器怎么要求客户端证书?没有证书、证书不被信任时会怎样?客户端有多个证书怎么选?这篇用 JDK 的 SSLSocket 和真实的 nginx、curl 逐项验证,并看 TLS 1.3 与 1.2 的失败表现差异。
[TLSv1.3]
服务器 need,客户端有 alice(ca1 签发) → 客户端:startHandshake 返回(106 ms),本地证书 O=demo,CN=alice,读到 1 | 服务器:握手成功,客户端身份 = O=demo,CN=alice
服务器 need,客户端没有证书 → 客户端:startHandshake 返回(15 ms),但第一次读写失败 → SSLHandshakeException: (certificate_required) Received fatal alert: certificate_required | 服务器:SSLHandshakeException: (certificate_required) Empty client certificate chain
服务器 want,客户端没有证书 → 客户端:startHandshake 返回(12 ms),本地证书 无,读到 1 | 服务器:握手成功,客户端身份 = (没有客户端证书)
服务器 none(不要求),客户端有 alice → 客户端:startHandshake 返回(10 ms),本地证书 无,读到 1 | 服务器:握手成功,客户端身份 = (没有客户端证书)
[TLSv1.2]
服务器 need,客户端有 alice(ca1 签发) → 客户端:startHandshake 返回(15 ms),本地证书 O=demo,CN=alice,读到 1 | 服务器:握手成功,客户端身份 = O=demo,CN=alice
服务器 need,客户端没有证书 → 客户端:startHandshake 失败 → SocketException: Broken pipe | 服务器:SSLHandshakeException: (handshake_failure) Empty client certificate chain
服务器 want,客户端没有证书 → 客户端:startHandshake 返回(6 ms),本地证书 无,读到 1 | 服务器:握手成功,客户端身份 = (没有客户端证书)
服务器 none(不要求),客户端有 alice → 客户端:startHandshake 返回(7 ms),本地证书 无,读到 1 | 服务器:握手成功,客户端身份 = (没有客户端证书)服务器 need,客户端 mallory(ca2 签发,服务器不信任 ca2) → 客户端:startHandshake 返回(92 ms),但第一次读写失败 → SSLHandshakeException: (certificate_required) Received fatal alert: certificate_required | 服务器:SSLHandshakeException: (certificate_required) Empty client certificate chain 服务器 need 且信任 ca1+ca2,客户端 mallory → 客户端:startHandshake 返回(25 ms),本地证书 O=demo,CN=mallory,读到 1 | 服务器:握手成功,客户端身份 = O=demo,CN=mallory 服务器 need,客户端自签名证书(old_self,已过期) → 客户端:startHandshake 返回(15 ms),但第一次读写失败 → SSLHandshakeException: (certificate_required) Received fatal alert: certificate_required | 服务器:SSLHandshakeException: (certificate_required) Empty client certificate chain 服务器 want,客户端 mallory(不被信任) → 客户端:startHandshake 返回(12 ms),本地证书 无,读到 1 | 服务器:握手成功,客户端身份 = (没有客户端证书)
客户端 keystore 里有两个身份 [mallory, alice] 服务器只信任 ca1(CertificateRequest 里的可接受签发者只有 ca1):客户端:startHandshake 返回(128 ms),本地证书 O=demo,CN=alice,读到 1 | 服务器:握手成功,客户端身份 = O=demo,CN=alice 服务器信任 ca1+ca2:客户端:startHandshake 返回(35 ms),本地证书 O=demo,CN=mallory,读到 1 | 服务器:握手成功,客户端身份 = O=demo,CN=mallory X509KeyManager.chooseClientAlias(RSA, issuers=[CN=ca1]) = alice;issuers=[CN=ca2] = mallory;issuers=null(服务器没给列表)= mallory
ssl_verify_client on curl 没有客户端证书 → curl 返回码 0;HTTP 400,页面标题「400 No required SSL certificate was sent」 ssl_verify_client on curl alice(ca1 签发) → curl 返回码 0;verify=SUCCESS dn=O=demo,CN=alice [HTTP 200] ssl_verify_client on curl mallory(ca2 签发) → curl 返回码 0;HTTP 400,页面标题「400 The SSL certificate error」 ssl_verify_client optional curl 没有客户端证书 → curl 返回码 0;verify=NONE dn= [HTTP 200] ssl_verify_client optional curl alice(ca1 签发) → curl 返回码 0;verify=SUCCESS dn=O=demo,CN=alice [HTTP 200] ssl_verify_client optional curl mallory(ca2 签发) → curl 返回码 0;HTTP 400,页面标题「400 The SSL certificate error」 openssl s_client 看到 nginx 在 CertificateRequest 里给出的可接受 CA 名字: CN=ca1 ssl_verify_client on,TLS 1.2,curl 不带证书 → curl 返回码 0,HTTP 状态 400