OpenSSL客户端未发送客户端证书

12

我正在处理一个客户端证书的问题,希望这里有人能帮助我。我正在使用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
}

我无法看出任何行为上的差异。需要提及的是,我正在使用一个非常古老的boost 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。还是这样吗?

我已经调试了将近一个星期了,但仍然卡住了。有人有什么建议可以尝试吗?我想不出更多主意...

祝好, Stephan

PS:以下是使用相同证书的s_client和上述s_server调试输出:

$ 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

握手完成并传输数据。

3个回答

7

好的,在经历了很多煎熬之后,OpenSSL 的 Dave Thompson 找到了答案。

原因是我的 SSL 代码在从 OpenSSL 上下文创建 socket 对象(SSL*)后调用了所有这些函数。这意味着所有这些函数实际上什么也没做或者做错了。

我所要做的就是:

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使用方式,并且从它创建的每个套接字都将共享其属性。

感谢大家的建议。


1

您不应同时使用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而不是自己的证书。 - user2471020
如果它发送了根CA,那是因为你只在其中调用了CA证书的SSL_CTX_use_certificate_chain_file()。你可以尝试将仅包含客户端证书的文件传递给SSL_CTX_use_certificate_chain_file()吗? - Remi Gacogne
谢谢大家的参与!非常感谢! @Remi:当我这样做时,客户端不发送任何证书。这是我所描述的第一种情况。顺便说一下,如果链文件只包含CA证书,那也是这种情况。我知道,这是不明智的,但我还是想试一试。 - user2471020

1
我认为你需要在服务器端调用SSL_CTX_set_client_CA_list。这会设置一个证书颁发机构的列表,与客户端证书请求一起发送。
如果证书不符合服务器发送的CA列表,则客户端将不会发送其证书,即使已经请求了证书。

是的,这已经完成了。并且已经发送了。不幸的是,我的问题在客户端上。我使用openssl的s_server来调试自己的客户端。因此,我的服务器在这里并不重要。只需假设它是正常的,我只是无法连接到s_server测试工具。我必须假设它正确地使用其协议。 - user2471020
抱歉,我忽略了那个。我假设你已经检查过了一些非常明显的东西,比如密钥文件是否正确?在你的 s_client 命令行中,它是 private/testclient.key,在代码中只有 "testclient.key"。我的(可工作的)代码与你的几乎完全相同,除了我使用 SSL_CTX_check_private_key 来验证它与证书是否匹配,并安装一个密码回调来 "解锁" 私钥:SSL_CTX_set_default_passwd_cb。 - Tannin
别担心,谢谢任何建议。我在这里完全挂了。至于证书,唉,我已经检查过了。查看了一遍又一遍,对比了文件等等。那个命令行差异的原因只是我用一个可以在这里发布的名字代替了一个“非常机密”的名字;-) 我确定它们是正确的。SSL_CTX_check_private_key返回true。对于空密码密钥,是否需要该密码回调函数? - user2471020

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接