本文介绍了使用 SNI 在一个盒子中为多个域提供服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 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 执行之前的方式.

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

解决方案

当您启动服务器时,您会提供一个默认的 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

" |/usr/local/ssl/bin/openssl s_client -connect 本地主机:8443 -tls1 -servername 本地主机-CAfile pki/signing-dss-cert.pem

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

/apps/s_server.c 中查看 OpenSSL 源代码;或查看 如何实现服务器名称指示(SNI) 在 C 或 C++ 中的 OpenSSL 上?.

在您的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 的代码(在 /apps/s_server.c 中).跟踪ctx2,

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

static int ServerNameCallback(SSL *ssl, int *ad, void *arg){未使用(广告);未使用(arg);断言(SSL);如果(ssl == NULL)返回 SSL_TLSEXT_ERR_NOACK;const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);断言(服务器名&& 服务器名[0]);if (!servername || servername[0] == '')返回 SSL_TLSEXT_ERR_NOACK;/* 默认证书是否已经处理了这个域?*/如果(IsDomainInDefCert(服务器名))返回 SSL_TLSEXT_ERR_OK;/* 需要这个域的新证书 */SSL_CTX* ctx = GetServerContext(servername);断言(ctx != NULL);如果(ctx == NULL)返回 SSL_TLSEXT_ERR_NOACK;/* 无用的返回值 */SSL_CTX* v = SSL_set_SSL_CTX(ssl, ctx);断言(v == ctx);如果(v != ctx)返回 SSL_TLSEXT_ERR_NOACK;返回 SSL_TLSEXT_ERR_OK;}

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

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

I'm using OpenSSL 0.9.8q in FreeBSD-8.2. I have 3 virtual hosts on my system and want to implement SNI to serve for all 3 of them in one server.

I have 3 separate certificates one for each, and in my ssl-server code I have to somehow find out what is the domain-name of client's request, and use the appropriate certificate file based on that. For this I wrote a function named get_ssl_servername_cb and passed it as callback function to SSL_CTX_set_tlsext_servername_callback. This way, in callback function I can get the the domain-name of the client's request.

But my problem is, this callback function is being executed after execution of SSL_accept function, but I have to choose and use the appropriate certificate before using SSL_new command, which is way before execution of SSL_accept.

So my question is, how can I use SSL_CTX_set_tlsext_servername_callback function for SNI?

解决方案

When you start your server, you provide a default SSL_CTX. This is used for non-SNI clients, like SSLv3 clients and TLS clients that don't utilize SNI (like Windows XP). This is needed because the callback is not invoked in this situation.

Here are some examples to tickle the behavior using OpenSSL's s_client. To simulate a non-SNI client so that your get_ssl_servername_cb is not called, issue:

  • openssl s_client -connect localhost:8443 -ssl3 # SNI added at TLSv1
  • openssl s_client -connect localhost:8443 -tls1 # Windows XP client

To simulate a SNI client so that your get_ssl_servername_cb is called, issue:

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

You can also avoid the certificate verification errors by adding -CAfile. This is from one of my test scripts (for testing DSS/DSA certificates on localhost):

printf "GET / HTTP/1.1

" | /usr/local/ssl/bin/openssl s_client
    -connect localhost:8443 -tls1 -servername localhost
    -CAfile pki/signing-dss-cert.pem


See the OpenSSL source code at <openssl dir>/apps/s_server.c; or see How to implement Server Name Indication(SNI) on OpenSSL in C or C++?.

In your get_ssl_servername_cb (set with SSL_CTX_set_tlsext_servername_callback), you examine the server name. One of two situations occur: you already have a SSL_CTX for the server's name, or you need to create a SSL_CTX for server's name.

Once you fetch the SSL_CTX from cache or create a new SSL_CTX, you then use SSL_set_SSL_CTX to swap in the context. There's an example of swapping in the new context in the OpenSSL source files. See the code for s_server.c (in <openssl dir>/apps/s_server.c). Follow the trail of ctx2,

Here's what it looks like in one of my projects. IsDomainInDefaultCert determines if the requested server name is provided by the default server certificate. If not, GetServerContext fetches the needed SSL_CTX. GetServerContext pulls the needed certificate out of an app-level cache; or creates it and puts it in the app-level cache (GetServerContext also asserts one reference count on the SSL_CTX so the OpenSSL library does not delete it from under the app).

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] == '')
        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;
}

In the code above, ad and arg are unused parameters. I don't know what ad does because I don't use it. arg can be used to pass in a context to the callback. I don't use arg either, but s_server.c uses it to print some debug information (the arg is a pointer to a BIOs tied to stderr (and a few others), IIRC).


For completeness, SSL_CTX are reference counted and they can be re-used. A newly created SSL_CTX has a count of 1, which is delegated to the OpenSSL internal caching mechanism. When you hand the SSL_CTX to a SSL object, the count increments to 2. When the SSL object calls SSL_CTX_free on the SSL_CTX, the function will decrement the reference count. If the context is expired and the reference count is 1, then the OpenSSL library will delete it from its internal cache.

这篇关于使用 SNI 在一个盒子中为多个域提供服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-30 06:05