C++ 使用OpenSSL内存BIOs的第二个ClientHello后未触发验证方法

C++ 使用OpenSSL内存BIOs的第二个ClientHello后未触发验证方法,c++,openssl,dtls,C++,Openssl,Dtls,我正在使用OpenSSL为NodeJS编写一个本机DTLS模块。它使用内存BIOs,因此节点自己的套接字可用于控制数据流。一切似乎都在运行,但我在DOS缓解方面遇到了一些问题 根据规范,发送到服务器的初始ClientHello应该被拒绝,服务器将发送一个HelloVerifyRequest,其中包含一个cookie,以便从客户端重新发送回来。这一切都可以正常工作,但当客户端发回第二个ClientHello时,由于某种原因,DTLSv1_listen()调用导致我的cookie生成方法第二次触发,

我正在使用OpenSSL为NodeJS编写一个本机DTLS模块。它使用内存BIOs,因此节点自己的套接字可用于控制数据流。一切似乎都在运行,但我在DOS缓解方面遇到了一些问题

根据规范,发送到服务器的初始ClientHello应该被拒绝,服务器将发送一个HelloVerifyRequest,其中包含一个cookie,以便从客户端重新发送回来。这一切都可以正常工作,但当客户端发回第二个ClientHello时,由于某种原因,DTLSv1_listen()调用导致我的cookie生成方法第二次触发,而不是cookie验证方法。奇怪的是,如果我发回第二个HelloVerifyRequest(与第一个请求的长度和内容完全相同),我最终会得到一个ClientHello,它似乎触发了验证方法

我写了一个小测试来说明我正在做的事情(不完全是,跳过了一些东西,比如导入证书/密钥、结果代码在调用握手后检查读/写、释放内存等)

测试(新,测试){
//初始化上下文
auto ctx=SSL_ctx_new(DTLS_method());
SSL_CTX_set_cipher_list(CTX,“所有:!ADH:!低:!EXP:!MD5:@STRENGTH”);
SSL_CTX_set_verify(CTX,SSL_verify_NONE,[](int-ok,X509_-STORE_-CTX*context){return 1;});
SSL_CTX_set_cookie_generate_cb(CTX,[](SSL*SSL,unsigned char*cookie,unsigned int*cookie){
返回1;
});
SSL\u CTX\u set\u cookie\u verify\u cb(CTX,[](SSL*SSL,const unsigned char*cookie,unsigned int cookie\u len){
返回1;
});
//初始化连接
自动客户端=SSL_新(ctx);
自动客户端\u rbio=BIO_new(BIO_s_mem());
自动客户端_wbio=BIO_new(BIO_s_mem());
SSL_set_bio(客户端、客户端\u rbio、客户端\u wbio);
SSL设置连接状态(客户端);
自动服务器=SSL_新(ctx);
自动服务器_rbio=BIO_new(BIO_s_mem());
自动服务器_wbio=BIO_new(BIO_s_mem());
SSL_set_bio(服务器、服务器rbio、服务器wbio);
SSL设置接受状态(服务器);
std::矢量数据;
//客户你好,没有饼干
SSL_do_握手(客户端);
自动数据\u len=BIO\u ctrl\u挂起(客户端\u wbio);
数据。调整大小(数据长度);
BIO_read(client_wbio,data.data(),data.size());
断言(数据[13],1);
//你好,验证请求
BIO_write(server_rbio,data.data(),data.size());
DTLSv1_侦听(服务器,空);
data\u len=BIO\u ctrl\u pending(服务器\u wbio);
数据。调整大小(数据长度);
BIO_read(server_wbio,data.data(),data.size());
ASSERT_EQ(数据[13],3];
//客户你好,我是cookie
BIO_write(client_rbio,data.data(),data.size());
SSL_do_握手(客户端);
数据长度=BIO控制待定(客户wbio);
数据。调整大小(数据长度);
BIO_read(client_wbio,data.data(),data.size());
断言(数据[13],1);
//应该通过吗。。。?
BIO_write(server_rbio,data.data(),data.size());
ASSERT_EQ(DTLSv1_listen(server,NULL),1);
}
最后一个断言失败——在本例中为-1,在我的实际代码中为0(随后的BIO_read获取我的数据[13]=3,又称HelloVerifyRequest),但这里需要注意的重要一点是,如果附加一个调试器并在验证lambda上放置一个断点,它将不会被命中。

您可以尝试使用

示例用法可在中找到

看看CoAP和MQTT-SN协议

玉莲爱发酒店


根据我的经验,您必须提供一个
BIO\u addr
DTLSv1\u listen
。在您的情况下,传递的是
NULL
。我还以为这会管用,但可惜不行

让我试着解开客户的神秘之谜\u HELLO-HELLO\u VERIFY\u请求

解释说,尽管客户机HELLO可能被欺骗,但HELLO\u VERIFY\u请求用于证明客户机确实在侦听地址。因此,服务器使用来自客户端的数据、客户端IP地址和端口以及服务器机密创建cookie

Cookie=HMAC(机密、客户端IP、客户端参数)

服务器不存储该cookie,否则大规模欺骗将需要大量内存。因此,每当收到客户机HELLO时,服务器都会重新计算cookie。如果客户端包含cookie,则将其与新计算的cookie进行比较。 新计算的cookie现在可能有所不同,如果客户机\u HELLO更改了其他任何内容,那么cookie就会发生变化。这包括,客户端IP(地址+端口)也必须保持不变。如果服务器根据RFC6347的建议更新其机密,cookie在某些罕见的情况下可能也会有所不同

此方案的一个潜在攻击是攻击者收集 来自不同地址的Cookie数,然后将其重新用于 攻击服务器。服务器可以通过以下方式抵御此攻击: 频繁更改机密值,从而使这些cookie无效

这样,检查您的客户端是否更改(可能只是源端口更改),或者服务器是否过于频繁地更新机密。
如果您能提供一些wireshark捕获,我可能会帮助您。

这肯定是一个替代方案,但不是我真正想要的。我想利用node附带的OpenSSL。你有没有想过这一点。我遇到了同样的问题。我查看了
DTLSv1\u listen
的源代码,它应该能够用cookie很好地处理ClientHello…不幸的是,我从来没有弄清这一点。我试着加入OpenSSL邮件列表询问那里的人,但过程证明很麻烦,我一直没有得到答案。我最终让它工作起来。在我的例子中,我必须将一个有效的
BIO_ADDR
传递到
DTLSv1\u listen
,不管文档怎么说。我会尽量记住检查我的代码,以防我忘了其他东西…太棒了!如果你有机会回顾一下,请作为答案发布,我会
TEST(New, Test) {
    // Init context
    auto ctx = SSL_CTX_new(DTLS_method());
    SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, [](int ok, X509_STORE_CTX * context) { return 1; });
    SSL_CTX_set_cookie_generate_cb(ctx, [](SSL * ssl, unsigned char * cookie, unsigned int * cookie_len) { 
        return 1; 
        });
    SSL_CTX_set_cookie_verify_cb(ctx, [](SSL * ssl, const unsigned char * cookie, unsigned int cookie_len) { 
        return 1; 
        });

    // Init connections
    auto client = SSL_new(ctx);
    auto client_rbio = BIO_new(BIO_s_mem());
    auto client_wbio = BIO_new(BIO_s_mem());
    SSL_set_bio(client, client_rbio, client_wbio);
    SSL_set_connect_state(client);

    auto server = SSL_new(ctx);
    auto server_rbio = BIO_new(BIO_s_mem());
    auto server_wbio = BIO_new(BIO_s_mem());
    SSL_set_bio(server, server_rbio, server_wbio);
    SSL_set_accept_state(server);

    std::vector<unsigned char> data;

    // Client Hello, no cookie
    SSL_do_handshake(client);
    auto data_len = BIO_ctrl_pending(client_wbio);
    data.resize(data_len);
    BIO_read(client_wbio, data.data(), data.size());

    ASSERT_EQ(data[13], 1);

    // Hello Verify Request
    BIO_write(server_rbio, data.data(), data.size());
    DTLSv1_listen(server, NULL);
    data_len = BIO_ctrl_pending(server_wbio);
    data.resize(data_len);
    BIO_read(server_wbio, data.data(), data.size());

    ASSERT_EQ(data[13], 3);

    // Client Hello, with cookie
    BIO_write(client_rbio, data.data(), data.size());
    SSL_do_handshake(client);
    data_len = BIO_ctrl_pending(client_wbio);
    data.resize(data_len);
    BIO_read(client_wbio, data.data(), data.size());

    ASSERT_EQ(data[13], 1);

    // Should be pass...?
    BIO_write(server_rbio, data.data(), data.size());
    ASSERT_EQ(DTLSv1_listen(server, NULL), 1);
}