【问题标题】:OpenSSL client not sending client certificateOpenSSL 客户端未发送客户端证书
【发布时间】:2025-12-04 11:55:02
【问题描述】:

我正在努力解决客户端证书问题,希望这里有人可以帮助我。我正在使用 boost asio 开发客户端/服务器对,但我会尽量不具体。我在 Windows 上并使用 openssl 1.0.1e

基本上,我想使用客户端证书进行客户端身份验证。服务器应仅接受具有我自己的 CA 签署的证书的客户端。所以我设置了一个自签名 CA。这又颁发了两个证书。一个用于客户端,一个用于服务器。两者均由 CA 签署。 我已经这样做了好几次了,我相信我做到了。

我的服务器端也可以正常工作。它请求客户端证书,如果我使用 s_client 并提供这些证书,一切正常。此外,如果我使用浏览器并将我的根 CA 安装为受信任的,然后导入客户端证书。

我唯一不能工作的是 libssl 客户端。它在握手期间总是失败,据我所知,它不会发送客户端证书:

$ openssl.exe s_server -servername localhost -bugs -CAfile myca.crt -cert server.crt
 -cert2 server.crt -key private/server.key -key2 private/server.key -accept 8887 -www 
 -state -Verify 5
verify depth is 5, must return a certificate
Setting secondary ctx parameters
Using default temp DH parameters
Using default temp ECDH parameters
ACCEPT
SSL_accept:before/accept initialization
SSL_accept:SSLv3 read client hello A
SSL_accept:SSLv3 write server hello A
SSL_accept:SSLv3 write certificate A
SSL_accept:SSLv3 write key exchange A
SSL_accept:SSLv3 write certificate request A
SSL_accept:SSLv3 flush data
SSL3 alert read:warning:no certificate
SSL3 alert write:fatal:handshake failure
SSL_accept:error in SSLv3 read client certificate B
SSL_accept:error in SSLv3 read client certificate B
2675716:error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a
certificate:s3_srvr.c:3193:
ACCEPT

我正在使用这个 s_server 作为调试工具,但在我的真实服务器上也会发生同样的事情。 s_client 可以使用相同的证书正常工作。此外,如果我在服务器中禁用“-Verify”,则连接有效。所以看起来真的只是客户拒绝发送它的证书。这可能是什么原因?

由于我使用 boost asio 作为 SSL 包装器,因此代码如下所示:

m_ssl_context.set_verify_mode( asio::ssl::context::verify_peer );
m_ssl_context.load_verify_file( "myca.crt" );
m_ssl_context.use_certificate_file( "testclient.crt", asio::ssl::context::pem );
m_ssl_context.use_private_key_file( "testclient.key", asio::ssl::context::pem );

我也尝试绕过 asio 直接访问 SSL 上下文:

SSL_CTX *ctx = m_ssl_context.impl();
SSL *ssl = m_ssl_socket.impl()->ssl;
int res = 0;
res = SSL_CTX_use_certificate_chain_file(ctx, "myca.crt");
if (res <= 0) {
    // handle error
}
res = SSL_CTX_use_certificate_file(ctx, "testclient.crt", SSL_FILETYPE_PEM);
if (res <= 0) {
    // handle error
}
res = SSL_CTX_use_PrivateKey_file(ctx, "testclient.key", SSL_FILETYPE_PEM);
if (res <= 0) {
    // handle error
}

我看不出行为上有什么不同。应该提到的是,我使用的是一个非常旧的 bo​​ost 1.43 asio,我无法更新,但我想所有相关的调用或多或少都直接转到 OpenSSL,服务器在该版本上工作正常,所以我想我可以排除这种情况。

如果我开始强制客户端和服务器使用特定版本,错误消息会发生变化,但它永远不会起作用,并且仍然始终适用于 s_client 测试。目前它设置为 TLSv1

如果我将它切换到 TLSv1,例如客户端和服务器之间会有更多的聊天,最终我会收到错误:

...
SSL_accept:SSLv3 read client key exchange A
<<< TLS 1.0 ChangeCipherSpec [length 0001]
    01
<<< TLS 1.0 Handshake [length 0010], Finished
    14 00 00 0c f4 71 28 4d ab e3 dd f2 46 e8 8b ed
>>> TLS 1.0 Alert [length 0002], fatal unexpected_message
    02 0a
SSL3 alert write:fatal:unexpected_message
SSL_accept:failed in SSLv3 read certificate verify B
2675716:error:140880AE:SSL routines:SSL3_GET_CERT_VERIFY:missing verify 
message:s3_srvr.c:2951:
2675716:error:140940E5:SSL routines:SSL3_READ_BYTES:ssl handshake failure:s3_pkt.c:989:
ACCEPT

我在 openssl 邮件列表上发现了一个较旧的错误条目,该条目对此进行了引用。显然,两年前已修复的握手中的错误 CRLF。或者有吗?

我已经调试了将近一个星期,我真的被困住了。有人对尝试什么有建议吗?我没有想法......

干杯, 斯蒂芬

PS:这是上面的 s_server 调试结果与 s_client 和相同的证书:

$ openssl s_client -CAfile ca.crt -cert testclient.crt -key private/testclient.key -verify 2 -connect myhost:8887

ACCEPT
SSL_accept:before/accept initialization
SSL_accept:SSLv3 read client hello A
SSL_accept:SSLv3 write server hello A
SSL_accept:SSLv3 write certificate A
SSL_accept:SSLv3 write key exchange A
SSL_accept:SSLv3 write certificate request A
SSL_accept:SSLv3 flush data
depth=1 C = DE, // further info
verify return:1
depth=0 C = DE, // further info
verify return:1
SSL_accept:SSLv3 read client certificate A
SSL_accept:SSLv3 read client key exchange A
SSL_accept:SSLv3 read certificate verify A
SSL_accept:SSLv3 read finished A
SSL_accept:SSLv3 write session ticket A
SSL_accept:SSLv3 write change cipher spec A
SSL_accept:SSLv3 write finished A
SSL_accept:SSLv3 flush data
ACCEPT

...握手完成并传输数据。

【问题讨论】:

    标签: c++ ssl network-programming openssl client-side


    【解决方案1】:

    好吧,经过一番磨难,OpenSSL 的 Dave Thompson 找到了答案。

    原因是我的 ssl 代码在从它创建套接字对象 (SSL*) 之后调用了 OpenSSL 上下文中的所有这些函数。这意味着所有这些功能实际上什么也没做或做错事。

    我所要做的就是:

    1.调用 SSL_use_certificate_file

    res = SSL_use_certificate_file(ssl, "testclient.crt", SSL_FILETYPE_PEM);
    if (res <= 0) {
        // handle error
    }
    res = SSL_use_PrivateKey_file(ssl, "testclient.key", SSL_FILETYPE_PEM);
    if (res <= 0) {
        // handle error
    }
    

    (注意缺少的CTX

    2.调用 CTX 函数

    在创建套接字之前根据上下文调用 CTX 函数。 asio 似乎鼓励在之后立即创建上下文和套接字(就像我在初始化列表中所做的那样),这些调用几乎毫无用处。

    SSL 上下文(在 lib OpenSSL 或类似 asio 中)封装了 SSL 使用,并且从它创建的每个套接字都将共享它的属性。

    谢谢大家的建议。

    【讨论】:

      【解决方案2】:

      您不应同时使用 SSL_CTX_use_certificate_chain_file() 和 SSL_CTX_use_certificate_file(),因为 SSL_CTX_use_certificate_chain_file() 会尝试加载包含客户端证书的链,而不仅仅是 CA 链。来自SSL_CTX_use_certificate(3)

      SSL_CTX_use_certificate_chain_file() 将证书链从文件加载到 ctx。证书必须采用 PEM 格式,并且必须从主题的证书(实际客户端或服务器证书)开始排序,然后是中间 CA 证书(如果适用),并以*别(根)CA 结束。

      我认为您应该只使用 SSL_CTX_use_certificate_file() 和 SSL_CTX_use_PrivateKey_file() 就可以了,因为无论如何客户端并不关心 CA 链。

      【讨论】:

      • 不幸的是,我已经尝试了所有我能想到的组合。包括这个。无济于事。看起来客户不会信任他自己的客户证书并且不会发送它。如果我使用这种组合,客户端根本不发送证书,如果我使用链文件,它看起来像发送根 CA 而不是他自己的。
      • 如果它发送根 CA,那是因为你在调用 SSL_CTX_use_certificate_chain_file() 时只有 CA 证书。您可以尝试将仅包含您的客户端证书的文件传递给 SSL_CTX_use_certificate_chain_file() 吗?
      • 感谢大家的意见!非常感激! @Remi:当我这样做时,客户端不会发送任何证书。这是我描述的第一个案例。顺便说一句,如果链文件仅包含 ca 证书,情况也是如此。我知道,这不明智,但我还是想试试。
      【解决方案3】:

      我认为您需要在服务器端致电SSL_CTX_set_client_CA_list。这将设置要与客户端证书请求一起发送的证书颁发机构列表。

      客户端不会发送其证书,即使请求了一个,如果证书与服务器发送的 CA 列表不匹配。

      【讨论】:

      • 是的。这已经完成了。它被发送了。不幸的是,我的问题出在客户端上。我使用 openssl 的 s_server 来调试我自己的客户端。所以我自己的服务器在这里无关紧要。假设没问题,我根本无法连接到 s_server 测试工具。我必须假设它正确地说出了它的协议。
      • 抱歉,我忽略了这一点。我假设你检查了完全明显的东西,比如密钥文件是正确的吗?在您的 s_client 命令行中它是 private/testclient.key,在代码中它只是“testclient.key”。我的(工作)代码与您所做的完全一样,除了我执行 SSL_CTX_check_private_key 以验证它与证书匹配并安装密码回调以“解锁”私钥:SSL_CTX_set_default_passwd_cb。
      • 不用担心,感谢您的任何意见。我在这里完全搞砸了。至于证书,唉,我做到了。一遍又一遍地检查它们,区分文件等等。命令行差异的原因只是我用我可以在此处发布的证书名称替换了“可疑秘密”证书名称;-) 我相信他们是对的。 SSL_CTX_check_private_key 返回真。空密码密钥是否需要该密码回调?