提问人:SBond 提问时间:10/11/2023 更新时间:10/11/2023 访问量:63
如果缺少 ALPN,则终止 TLSv1.3 握手
Terminate TLSv1.3 handshake if ALPN is missing
问:
在 TLSv1.3 握手期间,我在应用层协议协商 (ALPN) 验证方面遇到了一些问题。我想检查客户端是否发送了在其“客户端 Hello”中带有特定条目的 ALPN 扩展。否则,必须中止握手。
我将C++与Boost.Asio v1.83和OpenSSL 3.1结合使用。TLSv1.3 服务器使用 Boost 提供的功能:
boost::asio::async_accept(...);
boost::asio::async_handshake(...);
boost::asio::async_read_some(...);
boost::asio::async_write(...);
boost::asio::async_shutdown(...);
到目前为止,一切正常。如果在握手期间在“Client Hello”中注册了 ALPN 扩展,OpenSSL 会调用我的回调函数 (alpn_select_cb)。在这里,我可以检查 ALPN,并根据返回值中止握手。
// https://www.openssl.org/docs/manmaster/man3/SSL_select_next_proto.html
int ServerSession::alpn_select_cb(SSL *ssl,
const unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg)
{
ServerSession* srvSession = static_cast<ServerSession*>(arg);
int status = SSL_select_next_proto(const_cast<unsigned char **>(out), // selected ALPN
outlen, // ALPN entry size
srvSession->m_serverAlpnList.data(), // list with supported ALPNs of the server
srvSession->m_serverAlpnList.size(), // list size (server)
in, // received list with supported ALPNs of the client
inlen); // list size (client)
switch (status)
{
case OPENSSL_NPN_NEGOTIATED: return SSL_TLSEXT_ERR_OK; // OK: ALPN negotiated
case OPENSSL_NPN_UNSUPPORTED: return SSL_TLSEXT_ERR_NOACK; // ERR: ALPN unsupported
case OPENSSL_NPN_NO_OVERLAP: return SSL_TLSEXT_ERR_ALERT_FATAL; // ERR: ALPN no overlap
}
}
如果握手由于错误的 ALPN 而失败,则在 Wireshark 中如下所示(客户端为 127.0.0.1,服务器为 127.0.0.5):
No. Time Source Destination Protocol Length Info
---------------------------------------------------------------------------------------------------------------------------
22 38.136329 127.0.0.1 127.0.0.5 TLSv1.2 283 Client Hello --> client initiates TLSv1.3 connection (with a wrong ALPN)
24 38.137863 127.0.0.5 127.0.0.1 TLSv1.2 51 Alert (Level: Fatal, Description: No application Protocol)
到目前为止,该行为是正确的。但是,如果客户端没有发送 ALPN 扩展,则 OpenSSL 不会调用 ALPN 回调,因此握手将完成。然后,我可以在服务器端关闭TLS连接。在 Wireshark 中,它看起来像这样:
No. Time Source Destination Protocol Length Info
---------------------------------------------------------------------------------------------------------------------------
24 18.605087 127.0.0.1 127.0.0.5 TLSv1.3 273 Client Hello --> client initiates TLSv1.3 connection (without ALPN)
26 18.607975 127.0.0.5 127.0.0.1 TLSv1.3 1479 Server Hello, Change Cipher Spec, Application Data, Application Data, Application Data, Application Data
28 18.609183 127.0.0.1 127.0.0.5 TLSv1.3 124 Change Cipher Spec, Application Data
30 18.612081 127.0.0.5 127.0.0.1 TLSv1.3 554 Application Data, Application Data --> handshake finished
32 18.613404 127.0.0.5 127.0.0.1 TLSv1.3 68 Application Data --> server sends "close_notify" (connection shutdown)
34 22.126810 127.0.0.1 127.0.0.5 TLSv1.3 71 Application Data --> simultaneously the client is already trying to send data
36 22.127768 127.0.0.1 127.0.0.5 TLSv1.3 68 Application Data --> client sends "close_notify" (connection shutdown)
但是,我希望像第一种情况一样终止握手,并发送 TLS 警报。我的想法是使用 OpenSSL 在不同 TLS 阶段调用的消息回调。这样,我就可以在处理“客户端问候”后立即检查是否选择了 ALPN(参见 cb_ssl_msg())。
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_msg_callback.html
void ServerSession::cb_ssl_msg(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg)
{
// After processing the "Client Hello" the server can check if an ALPN exists and was selected
if (SSL_get_state(ssl) == TLS_ST_SW_SRVR_HELLO)
{
// get selected ALPN
unsigned int alpnLen;
const unsigned char* alpnData;
SSL_get0_alpn_selected(ssl, &alpnData, &alpnLen);
if (selectedAlpn.length() == 0) // no ALPN present
{
/*
... cancel the handshake process somehow ...
*/
// this does not work:
ServerSession* srvSession = static_cast<ServerSession*>(arg);
srvSession->m_socket.async_shutdown(boost::bind(&ServerSession::shutdownHandler, srvSession, boost::asio::placeholders::error));
return;
}
}
}
我的问题:如果没有 ALPN,我不知道如何在此回调 (cb_ssl_msg()) 中启动握手终止。async_shutdown() 似乎不是正确的方法,因为无论如何都会执行握手。
谁能帮我,或者有人有什么想法吗?
答:
我建议使用 ClientHello 回调来检查是否存在 ALPN 扩展。您可以使用以下函数设置 ClientHello 回调:SSL_CTX_set_client_hello_cb
https://www.openssl.org/docs/man3.1/man3/SSL_client_hello_cb_fn.html
回调可以使用函数(在上面的同一手册页中描述)检查是否存在 ALPN 扩展。如果扩展不存在,则回调应返回 0 并在参数中设置适当的警报。SSL_client_hello_get0_ext
al
解决!
非常感谢Matt Caswell。
是的,我也刚刚遇到了“SSL_CTX_set_client_hello_cb()”。有了这个,我可以尽早访问“客户你好”。
首先我设置回调函数:
SSL_CTX_set_client_hello_cb(context.native_handle(), ServerSession::client_hello_cb, nullptr);
...然后必须定义回调:
int ServerSession::client_hello_cb(SSL* ssl, int* al, void* arg)
{
const unsigned char* extData; // needed by SSL_client_hello_get0_ext(); on success: pointer to the 'length field' of the extension field
std::size_t extLen; // needed by SSL_client_hello_get0_ext(); on success: length of the extension field (length field + body)
if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_application_layer_protocol_negotiation, &extData, &extLen) == 1)
{
return SSL_CLIENT_HELLO_SUCCESS; // ALPN extension was found (success)
}
else
{
*al = TLS1_AD_NO_APPLICATION_PROTOCOL; // ALPN extension not found: set TLS Alert (Level: Fatal, Description: No application Protocol)
return SSL_CLIENT_HELLO_ERROR;
}
}
就是这样。要检查 ALPN(如果存在),我仍然在我的问题中使用 like。SSL_CTX_set_alpn_select_cb(context.native_handle(), ServerSession::alpn_select_cb, this)
非常感谢Matt :)
评论