如果缺少 ALPN,则终止 TLSv1.3 握手

Terminate TLSv1.3 handshake if ALPN is missing

提问人:SBond 提问时间:10/11/2023 更新时间:10/11/2023 访问量:63

问:

在 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() 似乎不是正确的方法,因为无论如何都会执行握手。

谁能帮我,或者有人有什么想法吗?

C++ 加速 openSSL TLS1.3 ALPN

评论


答:

2赞 Matt Caswell 10/11/2023 #1

我建议使用 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_extal

1赞 SBond 10/11/2023 #2

解决!

非常感谢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 :)