一、描述
QDtlsClientVerifier 类实现服务器端 DTLS cookie 生成和验证。
数据报安全协议极易受到各种拒绝服务攻击。两种常见的攻击类型:
- 攻击者发送一系列握手发起请求,导致服务器分配过多资源并可能执行昂贵的加密操作。
- 攻击者使用受害者的伪造源发送一系列握手发起请求,使服务器充当放大器。通常,服务器会用一个证书消息回复受害机器,该消息可能非常大,从而使受害机器充满数据报。
作为对这些攻击的对策,可以使用一种服务器可以部署的无状态 cookie 技术:
作为对初始 ClientHello 消息的响应,服务器发送包含 cookie 的 HelloVerifyRequest。这个 cookie 是一个加密散列,是使用客户端的地址、端口号、服务器的secret(这是一个加密强的伪随机字节序列)生成的。
可访问的 DTLS 客户端应使用包含此 cookie 的新 ClientHello 消息进行回复。
当服务器收到带有 cookie 的 ClientHello 消息时,它会生成一个新的 cookie。将此新 cookie 与在 ClientHello 消息中找到的 cookie 进行比较。在 cookie 相等的情况下,客户端被认为是真实的,服务器可以继续进行 TLS 握手过程。
QDtlsClientVerifier 旨在与 QUdpSocket 配对工作,如以下代码摘录所示:
class DtlsServer : public QObject
{
public:
bool listen(const QHostAddress &address, quint16 port);
// ...
private:
void readyRead();
// ...
QUdpSocket serverSocket;
QDtlsClientVerifier verifier;
// ...
};
bool DtlsServer::listen(const QHostAddress &serverAddress, quint16 serverPort)
{
if (serverSocket.bind(serverAddress, serverPort))
connect(&serverSocket, &QUdpSocket::readyRead, this, &DtlsServer::readyRead);
return serverSocket.state() == QAbstractSocket::BoundState;
}
void DtlsServer::readyRead()
{
QByteArray dgram(serverSocket.pendingDatagramSize(), Qt::Uninitialized);
QHostAddress address;
quint16 port = {};
serverSocket.readDatagram(dgram.data(), dgram.size(), &address, &port);
if (verifiedClients.contains({address, port})
{
// 这个客户端之前已经验证过了,继续握手或解密传入的消息。
}
else if (verifier.verifyClient(&serverSocket, dgram, address, port))
{
// 有一个真正的 DTLS 客户端想要向我们(服务端)发送加密数据报。记住此客户端已验证并继续握手。
}
else
{
// 在传入的数据报中没有找到匹配的 cookie,verifyClient() 已经发送了一个 ClientVerify 消息。 如果他们是真的,服务端很快就会再次收到客户的消息,
}
}
QDtlsClientVerifier 不会对应用程序如何使用 QUdpSocket 施加任何限制。这意味着 QDtlsClientVerifier 不直接从socket读取,而是期望应用程序读取传入的数据报,提取发送者的地址和端口,然后将此数据传递给 verifyClient()。
注意:默认密钥由 QDtlsClientVerifier 和 QDtls 类的所有对象共享。
可以使用类 GeneratorParameters() 和 setCookieGeneratorParameters() 设置 Cookie 生成器参数:
void DtlsServer::updateServerSecret()
{
const QByteArray newSecret(generateCryptoStrongSecret());
if (newSecret.size())
{
usedCookies.append(newSecret);
verifier.setCookieGeneratorParameters({QCryptographicHash::Sha1, newSecret});
}
}
二、GeneratorParameters结构体
此结构体的对象提供 QDtlsClientVerifier 用于生成 DTLS cookie 的参数。包括加密哈希算法和secret。
三、成员函数
1、bool setCookieGeneratorParameters(const QDtlsClientVerifier::GeneratorParameters ¶ms)
设置将用来生成cookie的GeneratorParameters 对象。如果GeneratorParameters 对象的secret大小为零,则此函数返回 false 并且不会更改 cookie 生成器参数。
2、QByteArray verifiedHello()
返回最后一个成功验证的 ClientHello 消息。
3、bool verifyClient(QUdpSocket *socket, const QByteArray &dgram, const QHostAddress &address, quint16 port)
socket 必须是有效指针,dgram 必须是非空数据报,地址不能为空、广播或多播。 port 是远程对等方的端口。 如果 dgram 包含带有有效 cookie 的 ClientHello 消息,则此函数返回 true。如果没有找到匹配的 cookie,此函数将使用socket发送一个 HelloVerifyRequest 消息并返回 false。
以下代码段显示了服务器应用程序如何检查错误:
if (!verifier.verifyClient(&socket, message, address, port)) {
switch (verifyClient.dtlsError()) {
case QDtlsError::NoError:
// 还没有验证,但是没有发现错误,等待下一个来自这个客户端的消息。
return;
case QDtlsError::TlsInitializationError:
// 这个错误是致命的,我们无能为力。 可能报错后退出服务器。
return;
case QDtlsError::UnderlyingSocketError:
// QUdpSocket有问题,处理(参见QUdpSocket::error())
return;
case QDtlsError::InvalidInputParameters:
default:
Q_UNREACHABLE();
}
}