但我的问题是,这个回调函数是在执行“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_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;
}
在上面的代码中,ad 和 arg 是未使用的参数。我不知道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 库会将其从其内部缓存中删除。