【问题标题】:Serving multiple domains in one box with SNI使用 SNI 在一个盒子中为多个域提供服务
【发布时间】:2014-04-17 21:00:01
【问题描述】:

我在 FreeBSD-8.2 中使用 OpenSSL 0.9.8q。我的系统上有 3 台虚拟主机,并希望实现 SNI 以在一台服务器上为所有 3 台主机提供服务。

我有 3 个单独的证书,每个证书都有一个,在我的 ssl-server 代码中,我必须以某种方式找出客户端请求的域名是什么,并在此基础上使用适当的证书文件。为此,我编写了一个名为get_ssl_servername_cb 的函数并将其作为回调函数传递给SSL_CTX_set_tlsext_servername_callback。这样,在回调函数中就可以得到客户端请求的域名了。

但我的问题是,这个回调函数是在执行SSL_accept函数之后执行的,但是我必须在使用SSL_new命令之前选择和使用适当的证书,这是在执行SSL_accept之前的方式。

所以我的问题是,如何将SSL_CTX_set_tlsext_servername_callback 函数用于 SNI?

【问题讨论】:

标签: c openssl sni


【解决方案1】:

但我的问题是,这个回调函数是在执行“SSL_accept”函数之后执行的,但是我必须在使用“SSL_new”命令之前选择并使用适当的证书,这在执行 SSL_accept 之前。

当你启动你的服务器时,你提供一个默认的SSL_CTX。这用于非 SNI 客户端,如 SSLv3 客户端和不使用 SNI 的 TLS 客户端(如 Windows XP)。这是必需的,因为在这种情况下不会调用回调。

以下是一些使用 OpenSSL 的 s_client 来处理行为的示例。要模拟非 SNI 客户端以使您的 get_ssl_servername_cb 被调用,请发出:

  • openssl s_client -connect localhost:8443 -ssl3 # SNI 添加到 TLSv1
  • openssl s_client -connect localhost:8443 -tls1#Windows XP 客户端

要模拟 SNI 客户端以便调用您的 get_ssl_servername_cb ,请发出:

  • openssl s_client -connect localhost:8443 -tls1 -servername localhost

您还可以通过添加-CAfile 来避免证书验证错误。这是来自我的一个测试脚本(用于在localhost 上测试 DSS/DSA 证书):

printf "GET / HTTP/1.1\r\n\r\n" | /usr/local/ssl/bin/openssl s_client \
    -connect localhost:8443 -tls1 -servername localhost \
    -CAfile pki/signing-dss-cert.pem 

所以我的问题是,如何为 SNI 使用“SSL_CTX_set_tlsext_servername_callback”函数?

请参阅<openssl dir>/apps/s_server.c 的 OpenSSL 源代码;或查看How to implement Server Name Indication(SNI) on OpenSSL in C or C++?

在您的get_ssl_servername_cb(使用SSL_CTX_set_tlsext_servername_callback 设置)中,您检查服务器名称。会出现以下两种情况之一:您已经有一个SSL_CTX 作为服务器名称,或者您需要为服务器名称创建一个SSL_CTX

一旦您从缓存中获取SSL_CTX 或创建一个新的SSL_CTX,您就可以使用SSL_set_SSL_CTX 在上下文中进行交换。 OpenSSL 源文件中有一个在新上下文中交换的示例。请参阅 s_server.c 的代码(在 <openssl dir>/apps/s_server.c 中)。追踪ctx2

这是我的一个项目中的样子。 IsDomainInDefaultCert 确定请求的服务器名称是否由默认服务器证书提供。如果没有,GetServerContext 获取所需的SSL_CTXGetServerContext 从应用级缓存中提取所需的证书;或创建它并将其放入应用程序级缓存中(GetServerContext 还在SSL_CTX 上声明一个引用计数,因此 OpenSSL 库不会从应用程序下删除它)。

static int ServerNameCallback(SSL *ssl, int *ad, void *arg)
{
    UNUSED(ad);
    UNUSED(arg);

    ASSERT(ssl);
    if (ssl == NULL)
        return SSL_TLSEXT_ERR_NOACK;

    const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
    ASSERT(servername && servername[0]);
    if (!servername || servername[0] == '\0')
        return SSL_TLSEXT_ERR_NOACK;

    /* Does the default cert already handle this domain? */
    if (IsDomainInDefCert(servername))
        return SSL_TLSEXT_ERR_OK;

    /* Need a new certificate for this domain */
    SSL_CTX* ctx = GetServerContext(servername);
    ASSERT(ctx != NULL);
    if (ctx == NULL)
        return SSL_TLSEXT_ERR_NOACK;   

    /* Useless return value */
    SSL_CTX* v = SSL_set_SSL_CTX(ssl, ctx);
    ASSERT(v == ctx);
    if (v != ctx)   
        return SSL_TLSEXT_ERR_NOACK;

    return SSL_TLSEXT_ERR_OK;
}

在上面的代码中,adarg 是未使用的参数。我不知道ad 做了什么,因为我不使用它。 arg 可用于将上下文传递给回调。我也不使用arg,但s_server.c 使用它来打印一些调试信息(arg 是一个指向BIOs 的指针,它与stderr(以及其他一些)绑定,IIRC)。


为了完整起见,SSL_CTX 被引用计数并且可以重复使用。新创建的SSL_CTX 的计数为 1,它被委托给 OpenSSL 内部缓存机制。当您将SSL_CTX 交给SSL 对象时,计数将增加到2。当SSL 对象在SSL_CTX 上调用SSL_CTX_free 时,该函数将减少引用计数。如果上下文过期且引用计数为 1,则 OpenSSL 库会将其从其内部缓存中删除。

【讨论】:

  • 感谢您完美而完整的回答。根据您的回答,我设法使用了 SNI。太好了:)
  • 非常感谢您所做的一切!对于other answer 所附的关于SSL_set_SSL_CTX 的线程(非)安全性的评论,我应该怎么做?我有一个多线程服务器,并希望完全按照您的说明交换上下文。我的服务器每个客户端连接使用一个线程,每个客户端可以使用不同的主机名。因此,上下文的交换可以发生在任何线程中。以这种方式更改上下文会导致任何问题,即其他线程在什么意义上会受到此操作的影响?非常感谢!
  • 我现在已经成功实现了SNI,也是基于你的回答,所以首先非常感谢你这么详细的描述和细心的解释!我现在正在研究您的答案的一些细节,我认为在您的示例中,GetServerContext 应该将引用计数增加一(正如您在文中所说):这将是由 OpenSSL 在 SSL_set_SSL_CTX 中完成。如果你自己的GetServerContext 也增加了引用计数,你就会多了一个。这可以防止 OpenSSL 在实际不再需要时回收上下文。你怎么看?
  • @mat - 自从我使用代码以来已经有一段时间了,所以我正在从逐渐消失的记忆中工作......我似乎记得我需要维护一个上下文列表。要知道何时可以从列表中删除上下文,我需要记数。 SSL_CTX 仍将被 OpenSSL 引用计数,并且 OpenSSL 计数按预期递增和递减。但是,您无权访问 OpenSSL 内部引用计数,因此您需要保留一个外部引用计数。
  • 好的,我明白了,这是一个有趣的用例。目前与此外部计数不一致的是声明“因此 OpenSSL 库不会从应用程序下删除它”。这在这个例子中不会发生(我认为),如果可以,那么增加外部引用计数不能阻止它,因为 OpenSSL 不会知道它。我并不是要挑剔您的出色答案,我只想确定我的细节是正确的,然后简单地添加这些注意事项并与您讨论一下,以澄清您的答案中更微妙的方面。
猜你喜欢
  • 2020-05-30
  • 2013-02-14
  • 1970-01-01
  • 2018-03-20
  • 1970-01-01
  • 2017-05-22
  • 2019-06-10
  • 1970-01-01
  • 2021-11-08
相关资源
最近更新 更多