6

我在 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_callbackSNI 的功能?

4

1 回答 1

9

但我的问题是,这个回调函数是在执行“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# 在 TLSv1 中添加的 SNI
  • openssl s_client -connect localhost:8443 -tls1# Windows XP 客户端

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

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

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

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 源代码<openssl dir>/apps/s_server.c;或查看如何在 C 或 C++ 中在 OpenSSL 上实现服务器名称指示(SNI)?.

在您的get_ssl_servername_cb(set with SSL_CTX_set_tlsext_servername_callback) 中,您检查服务器名称。出现以下两种情况之一:您已经有一个SSL_CTXfor 服务器名称,或者您需要创建一个SSL_CTXfor 服务器名称。

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

这是我的一个项目中的样子。IsDomainInDefaultCert确定请求的服务器名称是否由默认服务器证书提供。如果没有,GetServerContext则获取所需的SSL_CTX. GetServerContext从应用级缓存中提取所需的证书;或创建它并将其放在应用程序级缓存中(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是一个指向BIO绑定到stderr(和其他一些),IIRC 的 s 的指针)。


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

于 2014-03-13T10:29:42.983 回答