虽然@matt_h 的解决方案有效,但您应该意识到可能出现的后果。
如#rfc4347中所述:
4.2. DTLS 握手协议
- 添加了无状态 cookie 交换以防止拒绝服务攻击。
4.2.1。拒绝服务对策
数据报安全协议极易受到各种拒绝服务 (DoS) 攻击。有两种攻击特别值得关注:
攻击者可以通过传输一系列握手启动请求来消耗服务器上的过多资源,从而导致服务器分配状态并可能执行昂贵的加密操作。
攻击者可以通过向伪造的受害者源发送连接启动消息来将服务器用作放大器。然后服务器将它的下一条消息(在 DTLS 中,一个证书消息,可能非常大)发送到受害机器,从而淹没它。
[...]
当客户端向服务器发送它的 ClientHello 消息时,服务器可以用 HelloVerifyRequest 消息进行响应。此消息包含使用 [PHOTURIS] 技术生成的无状态 cookie。客户端必须重新发送添加了 cookie 的 ClientHello。然后,服务器验证 cookie 并仅在其有效时继续进行握手。这种机制迫使攻击者/客户端能够接收 cookie,这使得使用欺骗性 IP 地址的 DoS 攻击变得困难。此机制不提供任何防御来自有效 IP 地址的 DoS 攻击。
最重要的部分是:
DTLS 服务器应该以这样一种方式生成 cookie,即它们可以被验证,而无需在服务器上保留任何每个客户端的状态。
因此,在实践中,您根本不应该存储任何 cookie。这也违背了 DTLS 的 DOS 对策的整体安全概念。目标是在对等方通过身份验证之前不分配额外资源。
攻击者可以通过使用虚假 IP 地址轻松地向您的存储(内存、数据库等)发送垃圾邮件。
结论:我们不需要存储 cookie 或一遍又一遍地使用相同的秘密,我们只需生成特定数量的秘密,将它们存储在保险库中,并在创建 cookie 时随机选择一个。稍后,我们将 cookie 与我们在该保险库中的秘密进行匹配。
请阅读以下来自@Nathaniel J. Smith 的评论,他在其中指出,该解决方案并未反映 RFC 4347 中描述的最合适的方式,其中仅应使用 2 个自我失效的机密。因此,我的解决方案应该只被视为实现 RFC 的工具。
这是我的开源解决方案(经过测试和工作):
项目:https ://github.com/Burnett01/openssl-cookie-secret-vault
堆栈版本:https ://github.com/Burnett01/openssl-cookie-secret-vault/blob/master/stack/
堆版本:https ://github.com/Burnett01/openssl-cookie-secret-vault/blob/master/heap/
API(堆栈版本):
#define CK_SECRET_MAX 20
#define CK_SECRET_LEN 16
/*
Vault that contains the secrets
*/
static unsigned char ck_secrets_vault[CK_SECRET_MAX][CK_SECRET_LEN];
/*
Creates and stores an amount of secrets
into the vault
*/
size_t ck_secrets_generate( size_t amount );
/*
Returns the amount of secrets in the vault
*/
size_t ck_secrets_count( void );
/*
Picks a random secret off the vault
*/
unsigned char *ck_secrets_random( void );
/*
Tests whether cookie matches on of the secrets
in the vault
*/
size_t ck_secrets_exist( unsigned char* peer, size_t plen,
unsigned char *cookie, size_t clen );
生成 20 个秘密:
printf( "Generated %d cookie-secrets.\n", ck_secrets_generate( CK_SECRET_MAX ) );
生成一个带有随机密钥的 cookie:
HMAC( EVP_sha256(), (const void*)ck_secrets_random(), CK_SECRET_LENGTH,
(const unsigned char*)buff, bufflen, result, &reslen );
测试 cookie 是否匹配我们的秘密之一:
if( ck_secrets_exist( buff, bufflen, cookie, clen ) == 1 )
/* Cookie is valid since we found a matching secret */
else
/* Cookie is not valid */
API(堆版本):
#define CK_SECRET_MAX 20
#define CK_SECRET_LEN 16
/*
Vault that contains the secrets
*/
struct Vault
{
unsigned char **secrets;
size_t count;
};
/*
Creates and stores an amount of secrets
into a vault
*/
Vault *vault_init( size_t amount );
/*
Destroys a vault
*/
void vault_destroy( Vault *v );
/*
Picks a random secret off a vault
*/
unsigned char *vault_random( Vault *v );
/*
Tests whether cookie matches one of the secrets
in a vault
*/
size_t vault_sec_exists( Vault *v, unsigned char* peer, size_t plen,
unsigned char *cookie, size_t clen );
创建一个保管库并生成 20 个秘密:
Vault *v = vault_init( CK_SECRET_MAX );
生成一个带有随机密钥的 cookie:
HMAC( EVP_sha256(), (const void*)vault_random( v ), CK_SECRET_LENGTH,
(const unsigned char*)buff, bufflen, result, &reslen );
测试 cookie 是否匹配我们的秘密之一:
if( vault_sec_exists( v, buff, bufflen, cookie, clen ) == 1 )
/* Cookie is valid since we found a matching secret */
else
/* Cookie is not valid */
销毁保险库:
vault_destroy( v );
编辑 30/04/2017:我添加了一个可能有用的堆栈版本示例:
https://github.com/Burnett01/openssl-cookie-secret-vault/blob/master/stack/example.c
编辑 28/05/2017:我进一步改进了堆栈版本,还添加了一个堆版本。