0

我在多线程服务器(每个线程的连接)中的 cookie 生成/验证回调有点麻烦。据我所知,DTLS 的东西需要这些回调;我担心的是示例代码对 cookie 使用全局变量,如果有很多连接传入,那么我希望有一个 cookie 和连接的哈希。

我有两个问题:

  1. 我应该这样做还是有更好、更简单的 OpenSSL 方式?
  2. 我用什么来将回调/cookie 标识为属于特定连接?

关于(2),回调(见下文)发生的线程与连接不同,因此我不能使用线程 ID 作为哈希键。回调被赋予一个 SSL* 参数,我敢打赌,有办法从中获得一个唯一的会话/连接 ID,但我不知道如何获得它。我查看了文档,但没有看到任何采用 SSL* 对象并给出唯一编号的东西,至少从我能知道的方法名称来看。给定这些回调传递的参数,什么是识别具有连接的唯一 cookie 的最佳方法?

我正在使用基于Robin Seggelmann 示例的代码。我的东西在这里


2个全局变量:

unsigned char cookie_secret[COOKIE_SECRET_LENGTH];
int cookie_initialized=0;

2 个回调使用这些变量:

int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) {
    ...
    /* Initialize a random secret */
    if (!cookie_initialized) {
        if (!RAND_bytes(cookie_secret, COOKIE_SECRET_LENGTH))
            ...

int verify_cookie(SSL *ssl, unsigned char *cookie, unsigned int cookie_len) {
    ...
    /* If secret isn't initialized yet, the cookie can't be valid */
    if (!cookie_initialized) return 0;

主服务器循环设置这些回调并为每个客户端连接打开一个新线程:

void start_server(int port, char *local_address) {
    ...
    SSL_CTX_set_cookie_generate_cb(ctx, generate_cookie);
    SSL_CTX_set_cookie_verify_cb(ctx, verify_cookie);
    ...
    while (1) {
        // accept new conns
        ...
        if (pthread_create( &tid, NULL, connection_handle, info) != 0) {
            perror("pthread_create");
            exit(-1);
        }
4

2 回答 2

4

虽然@matt_h 的解决方案有效,但您应该意识到可能出现的后果。

#rfc4347中所述:


4.2. DTLS 握手协议

  1. 添加了无状态 cookie 交换以防止拒绝服务攻击。

4.2.1。拒绝服务对策

数据报安全协议极易受到各种拒绝服务 (DoS) 攻击。有两种攻击特别值得关注:

  1. 攻击者可以通过传输一系列握手启动请求来消耗服务器上的过多资源,从而导致服务器分配状态并可能执行昂贵的加密操作。

  2. 攻击者可以通过向伪造的受害者源发送连接启动消息来将服务器用作放大器。然后服务器将它的下一条消息(在 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:我进一步改进了堆栈版本,还添加了一个堆版本。

于 2016-06-05T03:04:54.617 回答
1

只需存储指向 cookie 的 SSL 指针的简单映射。

struct cookie_entry { SSL *ssl; unsigned char cookie[COOKIE_SECRET_LENGTH]; }
struct cookie_entry cookie_tbl[100];

struct cookie_entry* find_cookie(SSL *ssl)
{
  for (int i = 0; i < 100; i++)
    if (ssl == cookie_tbl[i].ssl)
       return &cookie_tbl[i];
  return NULL;
}

int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) {

    /* Initialize a random secret */
    if (!find_cookie(ssl)) {
        struct cookie_entry ce* = find_cookie(NULL); 
        if (!RAND_bytes(ce->cookie, COOKIE_SECRET_LENGTH))
}

int verify_cookie(SSL *ssl, unsigned char *cookie, unsigned int cookie_len) {
    /* find_cookie and compare.. */

希望这是有道理的。显然可以修改为动态调整大小,或使用哈希表实现,例如 hcreate(3)。

于 2013-01-23T02:19:52.710 回答