OpenSSL 忽略自签名证书错误

17

我正在使用OpenSSL库编写一个小程序,旨在与一个SSLv3服务器建立连接。该服务器分发自签名证书,这导致握手失败并出现以下消息:“在证书链中存在自签名证书的sslv3警报握手失败。”

有没有办法强制进行连接?我尝试通过调用SSL_CTX_set_verify来解决:

SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

但是它似乎没有改变任何东西。

有什么建议吗?


1
SSL_VERIFY_NONE 禁用了证书验证。你可能不想这样做,因为这会让你容易受到中间人攻击。正确的做法是将自签名证书添加到可信证书列表中。(请参见 SSL_CTX_load_verify_locations()。) - Erwan Legrand
1
顺便说一下,错误信息并不意味着服务器证书是自签名的。它意味着您不信任根CA证书:X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN 证书链中存在自签名证书。可以使用不受信任的证书构建证书链,但本地找不到根CA。根CA证书始终是自签名的。 - Erwan Legrand
6个回答

7
默认情况下,OpenSSL会遍历证书链并尝试在每一步上进行验证,SSL_set_verify() 不会改变这个过程,请参考相关文档。引用文档中的一段话:
实际的验证过程要么使用内置的验证程序,要么使用使用 SSL_CTX_set_cert_verify_callback(3) 设置的另一个应用提供的验证函数集来执行。
因此,解决方案是创建一个简单的回调函数并设置它,以便覆盖所有证书链的遍历:
static int always_true_callback(X509_STORE_CTX *ctx, void *arg)
{
    return 1;
}

SSL_CTX_set_cert_verify_callback(CTX, always_true_callback);

2
在引用的文本下面只有几段文字:“如果没有指定verify_callback,则将使用默认回调。其返回值与preverify_ok相同,因此如果设置了SSL_VERIFY_PEER,则任何验证失败都将导致TLS/SSL握手终止并显示警报消息。”您的答案是不正确的,将验证模式设置为SSL_VERIFY_NONE就足够了。 - Spidey
1
@Spidey 你有看到这个问题吗?题目已经说明了 SSL_VERIFY_NONE 是不够的。你有试过吗?另外,你引用的那个手册是针对 SSL_CTX_set_verify() 的。但即使 set_verify 回调一直成功,验证仍然可能以许多其他方式失败,这就是为什么你必须使用 SSL_CTX_set_cert_verify_callback() 设置更通用的回调的原因。 - jimis
始终返回 1 将完全禁用证书验证。这与设置 SSL_VERIFY_NONE 相同。(即 @Spidey 是正确的。) 你可能不想这样做,因为这会使你容易受到中间人攻击。正确的做法是将自签名证书添加到受信任证书列表中。(请参阅 SSL_CTX_load_verify_locations()。) - Erwan Legrand
@ErwanLegrand 在问题中已经说明SSL_VERIFY_NONE是不够的。原因是OpenSSL仍然遍历整个证书链,仍然执行验证过程,只是从未调用回调。但是过程仍会在回调外失败。您必须调用SSL_CTX_set_cert_verify_callback(3)并使用该函数设置回调,以完全禁用证书链的遍历和检查。 - jimis

2

您是否尝试将服务器的CA证书提供给您的应用程序,以便您的应用程序可以验证证书链?


虽然在这种情况下服务器使用的是自签名证书,但 OP 必须让他的应用程序信任的不是 CA 证书,而是服务器的证书。 - Erwan Legrand
我的错,原帖是错误的。服务器证书不是自签名的。问题中包含的错误信息显示错误是 X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,这意味着提到的自签名证书不是服务器的证书,而是根CA的证书。 - Erwan Legrand

2

请查看这些OpenSSL示例:http://www.rtfm.com/openssl-examples/

wclient.c连接到任何https页面,例如:

wclient -h www.yahoo.com -p 443

如果您使用默认安装运行,会出现证书错误(但您可以使用-i标志绕过证书检查)。
为了验证证书,您需要下载CA证书(Verisign,Thawte,Equifax等),因此请搜索文件cacert.pem,下载并将其重命名为root.pem,然后您就可以连接到Web服务器并验证其证书。

1
此外,如果您想打印证书,请在 check_cert(ssl,host) 后面插入以下代码行(位于 wclient.c 文件中):X509_print_fp(stdout,SSL_get_peer_certificate(ssl)); - Adriano P
虽然在这种情况下服务器使用的是自签名证书,但 OP 必须让他的应用程序信任的不是 CA 证书,而是服务器的证书。 - Erwan Legrand
我的错,原帖是错误的。服务器证书不是自签名的。问题中包含的错误信息显示错误是 X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,这意味着提到的自签名证书不是服务器的证书,而是根CA的证书。 - Erwan Legrand

0
你尝试过设置 SSL_set_verify 吗?
SSL_set_verify(s, SSL_VERIFY_NONE, NULL);

感谢大家的回复。我尝试了你们所有的建议,但仍然没有运气。我在这里粘贴了程序的关键部分: http://pastebin.com/m78497bf3 - Ramsey
你的代码中自定义回调函数被调用了吗?在那里设置断点有没有被触发?尝试使用除 verify none 以外的其他内容...最好将该代码片段添加到您的原始问题中,以便所有内容都包含在此处... - Len Holgate

0
你可以尝试将自己的回调传递给SSL_set_verify(),然后进行自己的验证。这并不是理想的解决方法,因为我认为您需要完成所有验证,然后允许忽略自签名错误,但是您应该能够从OpenSSL源代码中了解标准验证代码的运行方式,然后将其简单地放入您自己的验证回调中,并允许特定的错误代码...

如果你打算接受自签名证书,那么其他的验证步骤也就毫无意义了。 - caf

0

我的示例客户端代码(链接)可以与自签名的服务器证书正常工作。在SSL_connect之后,我有以下代码,并且完全控制客户端接受自签名证书的可接受性。

SSL_CTX* ctx = SSL_CTX_new(SSLv3_method());

// TCP connection and SSL handshake ...

/* Check the certificate */

rc = SSL_get_verify_result(ssl);
if(rc != X509_V_OK) {
  if (rc == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || rc == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
    fprintf(stderr, "self signed certificate\n");
  }
  else {
    fprintf(stderr, "Certificate verification error: %ld\n", SSL_get_verify_result(ssl));
    SSL_CTX_free(ctx);
    return 0;
  }
}

这会使您容易受到中间人攻击!如果这样做,任何自签名证书都将被接受。如果证书是受信任的,则忽略的错误不会被触发。正确的做法是将自签名证书添加到可信证书列表中(请参见SSL_CTX_load_verify_locations())。 - Erwan Legrand

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