我正在使用 OpenSSL 为 NodeJS 编写本机 DTLS 模块。它使用内存 BIO,因此节点自己的套接字可用于控制数据流。一切似乎都在工作,但我在 DOS 缓解方面遇到了一些问题。
根据规范,发送到服务器的初始 ClientHello 应该被拒绝,并且服务器将发送一个包含 cookie 的 HelloVerifyRequest 以从客户端重新发送回。这一切都很好,但是当客户端发回第二个 ClientHello 时,由于某种原因,DTLSv1_listen() 调用导致我的 cookie 生成方法第二次触发,而不是 cookie 验证方法。奇怪的是,如果我发回第二个 HelloVerifyRequest(与第一个完全相同的长度和内容),我最终会得到一个似乎触发验证方法的 ClientHello。
这是我写的一个小测试来说明我正在做的事情(不完全是,跳过了一些东西,比如导入证书/密钥、调用握手后检查读/写的结果代码、释放内存等)。
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);
}
最后一个断言失败——在本例中为 -1,在我的实际代码中为 0(随后的 BIO_read 获取我的数据 [13] = 3 aka HelloVerifyRequest)但这里要注意的重要一点是,如果您附加调试器并将验证 lambda 上的断点不会被命中。