Keep track of cookies internally.

That allows to restrict the cookie validity time to the absolutely minimum
required to establish and reconnect a recently disconnected session.
That deprecates the cookie-validity option and introduces the cookie-timeout
option.
This commit is contained in:
Nikos Mavrogiannopoulos
2014-05-27 13:39:22 +02:00
parent a2728265b3
commit 25fbdfbf70
11 changed files with 242 additions and 51 deletions

View File

@@ -35,6 +35,7 @@
#include <auth/plain.h>
#include <vpn.h>
#include <cookies.h>
#include <main.h>
#include <ctl.h>
#include <tlslib.h>
@@ -100,7 +101,7 @@ static struct cfg_options available_options[] = {
{ .name = "mtu", .type = OPTION_NUMERIC, .mandatory = 0 },
{ .name = "net-priority", .type = OPTION_STRING, .mandatory = 0 },
{ .name = "output-buffer", .type = OPTION_NUMERIC, .mandatory = 0 },
{ .name = "cookie-validity", .type = OPTION_NUMERIC, .mandatory = 1 },
{ .name = "cookie-timeout", .type = OPTION_NUMERIC, .mandatory = 0 },
{ .name = "rekey-time", .type = OPTION_NUMERIC, .mandatory = 0 },
{ .name = "rekey-method", .type = OPTION_STRING, .mandatory = 0 },
{ .name = "auth-timeout", .type = OPTION_NUMERIC, .mandatory = 0 },
@@ -476,7 +477,6 @@ unsigned force_cert_auth;
config->tx_per_sec /= 1000;
READ_TF("deny-roaming", config->deny_roaming, 0);
READ_NUMERIC("cookie-validity", config->cookie_validity);
config->rekey_time = -1;
READ_NUMERIC("rekey-time", config->rekey_time);
@@ -496,6 +496,10 @@ unsigned force_cert_auth;
}
talloc_free(tmp); tmp = NULL;
READ_NUMERIC("cookie-timeout", config->cookie_timeout);
if (config->cookie_timeout == 0)
config->cookie_timeout = DEFAULT_COOKIE_RECON_TIMEOUT;
READ_NUMERIC("auth-timeout", config->auth_timeout);
READ_NUMERIC("idle-timeout", config->idle_timeout);

View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Nikos Mavrogiannopoulos
* Copyright (C) 2013, 2014 Nikos Mavrogiannopoulos
* Copyright (C) 2014 Red Hat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,6 +32,8 @@
#include <limits.h>
#include <sys/stat.h>
#include <ccan/htable/htable.h>
#include <ccan/hash/hash.h>
#include <ip-lease.h>
#include <main.h>
#include <cookies.h>
@@ -52,10 +55,10 @@ unsigned p_size;
ret = gnutls_cipher_init(&h, GNUTLS_CIPHER_AES_128_GCM, key, &iv);
if (ret < 0)
return -1;
cookie += COOKIE_IV_SIZE;
cookie_size -= (COOKIE_IV_SIZE + COOKIE_MAC_SIZE);
ret = gnutls_cipher_decrypt2(h, cookie, cookie_size, cookie, cookie_size);
if (ret < 0) {
ret = -1;
@@ -70,7 +73,6 @@ unsigned p_size;
ret = -1;
goto cleanup;
}
cookie += cookie_size;
if (memcmp(tag, cookie, COOKIE_MAC_SIZE) != 0) {
ret = -1;
@@ -88,7 +90,7 @@ unsigned p_size;
cleanup:
gnutls_cipher_deinit(h);
return ret;
}
@@ -122,7 +124,7 @@ uint8_t *packed = NULL, *e;
ret = -1;
goto cleanup;
}
ret = gnutls_cipher_init(&h, GNUTLS_CIPHER_AES_128_GCM, key, &iv);
if (ret < 0) {
ret = -1;
@@ -142,7 +144,7 @@ uint8_t *packed = NULL, *e;
memcpy(e, _iv, COOKIE_IV_SIZE);
e += COOKIE_IV_SIZE;
e_size -= COOKIE_IV_SIZE;
ret = gnutls_cipher_encrypt2(h, packed, packed_size, e, e_size);
if (ret < 0) {
ret = -1;
@@ -150,7 +152,7 @@ uint8_t *packed = NULL, *e;
}
e += packed_size;
ret = gnutls_cipher_tag(h, e, COOKIE_MAC_SIZE);
if (ret < 0) {
ret = -1;
@@ -158,7 +160,7 @@ uint8_t *packed = NULL, *e;
}
ret = 0;
cleanup:
talloc_free(packed);
if (h != NULL)
@@ -167,3 +169,131 @@ cleanup:
}
void cookie_db_deinit(struct cookie_entry_db_st* db)
{
struct cookie_entry_st * e;
struct htable_iter iter;
e = htable_first(db->db, &iter);
while(e != NULL) {
if (e->proc)
e->proc->cookie_ptr = NULL;
e->proc = NULL;
safe_memset(e->cookie, 0, e->cookie_size);
talloc_free(e);
e = htable_next(db->db, &iter);
}
htable_clear(db->db);
talloc_free(db->db);
return;
}
void expire_cookies(struct cookie_entry_db_st* db)
{
struct cookie_entry_st * e;
struct htable_iter iter;
time_t now = time(0);
e = htable_first(db->db, &iter);
while(e != NULL) {
if (e->expiration == -1 || now < e->expiration)
goto cont;
if (e->proc) {
syslog(LOG_ERR, "found proc that references expired cookie!");
e->proc->cookie_ptr = NULL;
}
htable_delval(db->db, &iter);
db->total--;
safe_memset(e->cookie, 0, e->cookie_size);
talloc_free(e);
cont:
e = htable_next(db->db, &iter);
}
return;
}
static size_t rehash(const void* _e, void* unused)
{
const struct cookie_entry_st * e = _e;
return hash_any(e->cookie, e->cookie_size, 0);
}
void cookie_db_init(void *pool, struct cookie_entry_db_st* db)
{
db->db = talloc(pool, struct htable);
htable_init(db->db, rehash, NULL);
db->total = 0;
}
static bool cookie_entry_cmp(const void* _c1, void* _c2)
{
const struct cookie_entry_st* c1 = _c1;
struct cookie_entry_st* c2 = _c2;
if (c1->cookie_size == c2->cookie_size &&
memcmp(c1->cookie, c2->cookie, c2->cookie_size) == 0)
return 1;
return 0;
}
struct cookie_entry_st *find_cookie_entry(struct cookie_entry_db_st* db, void *cookie, unsigned cookie_size)
{
struct cookie_entry_st *e;
struct cookie_entry_st t;
t.cookie = cookie;
t.cookie_size = cookie_size;
e = htable_get(db->db, hash_any(cookie, cookie_size, 0), cookie_entry_cmp, &t);
if (e == NULL)
return NULL;
if (e->expiration != -1 && e->expiration < time(0))
return NULL;
return e;
}
void revive_cookie(struct cookie_entry_st * e)
{
e->expiration = -1;
}
struct cookie_entry_st *new_cookie_entry(struct cookie_entry_db_st* db, proc_st *proc, void *cookie, unsigned cookie_size)
{
struct cookie_entry_st *t;
t = talloc(db->db, struct cookie_entry_st);
if (t == NULL)
return NULL;
t->expiration = -1;
t->cookie = talloc_memdup(t, cookie, cookie_size);
t->cookie_size = cookie_size;
if (t->cookie == NULL) {
goto fail;
}
t->proc = proc;
if (htable_add(db->db, rehash(t, NULL), t) == 0) {
goto fail;
}
db->total++;
return t;
fail:
talloc_free(t);
return NULL;
}

View File

@@ -22,17 +22,27 @@
#define COOKIES_H
#include <vpn.h>
#include <main.h>
#include <ipc.pb-c.h>
#define COOKIE_KEY_SIZE 16
#define COOKIE_IV_SIZE 12 /* AES-GCM */
#define COOKIE_MAC_SIZE 12 /* 96-bits of AES-GCM */
/* The time after a disconnection the cookie is valid */
#define DEFAULT_COOKIE_RECON_TIMEOUT 120
int encrypt_cookie(void *pool, gnutls_datum_t *key, const Cookie *msg,
uint8_t** ecookie, unsigned *ecookie_size);
int decrypt_cookie(ProtobufCAllocator *pa, gnutls_datum_t *key,
uint8_t *cookie, unsigned cookie_size,
Cookie **msg);
void cookie_db_init(void *pool, struct cookie_entry_db_st* db);
void cookie_db_deinit(struct cookie_entry_db_st* db);
void expire_cookies(struct cookie_entry_db_st* db);
struct cookie_entry_st *new_cookie_entry(struct cookie_entry_db_st* db, proc_st *proc, void *cookie, unsigned cookie_size);
struct cookie_entry_st *find_cookie_entry(struct cookie_entry_db_st* db, void *cookie, unsigned cookie_len);
void revive_cookie(struct cookie_entry_st *);
#endif

View File

@@ -166,45 +166,30 @@ time_t now = time(0);
gnutls_datum_t key = {s->cookie_key, sizeof(s->cookie_key)};
char str_ip[MAX_IP_STR+1];
PROTOBUF_ALLOCATOR(pa, proc);
struct cookie_entry_st *old;
if (req->cookie.len == 0) {
mslog(s, proc, LOG_INFO, "error in cookie size");
return -1;
}
proc->cookie = talloc_memdup(proc, req->cookie.data, req->cookie.len);
if (proc->cookie == NULL)
return -1;
proc->cookie_size = req->cookie.len;
ret = decrypt_cookie(&pa, &key, req->cookie.data, req->cookie.len, &cmsg);
if (ret < 0) {
mslog(s, proc, LOG_INFO, "error decrypting cookie");
return -1;
}
if (cmsg->expiration < now)
return -1;
if (cmsg->username == NULL)
return -1;
snprintf(proc->username, sizeof(proc->username), "%s", cmsg->username);
if (cmsg->groupname)
snprintf(proc->groupname, sizeof(proc->groupname), "%s", cmsg->groupname);
if (cmsg->hostname)
snprintf(proc->hostname, sizeof(proc->hostname), "%s", cmsg->hostname);
if (cmsg->session_id.len != sizeof(proc->dtls_session_id))
return -1;
memcpy(proc->dtls_session_id, cmsg->session_id.data, cmsg->session_id.len);
proc->dtls_session_id_size = cmsg->session_id.len;
memcpy(proc->ipv4_seed, &cmsg->ipv4_seed, sizeof(proc->ipv4_seed));
/* cookie is good so far, now read config */
/* cookie is good so far, now read config (in order to know whether roaming is allowed or not */
memset(&proc->config, 0, sizeof(proc->config));
apply_default_sup_config(s->config, proc);
@@ -238,6 +223,41 @@ PROTOBUF_ALLOCATOR(pa, proc);
}
}
/* check for a valid stored cookie */
if ((old=find_cookie_entry(&s->cookies, req->cookie.data, req->cookie.len)) != NULL) {
if (old->proc != NULL) {
mslog(s, old->proc, LOG_DEBUG, "disconnecting '%s' due to new cookie connection", old->proc->username);
/* steal its leases */
steal_ip_leases(old->proc, proc);
/* steal its cookie */
old->proc->cookie_ptr = NULL;
kill(old->proc->pid, SIGTERM);
} else {
revive_cookie(old);
}
proc->cookie_ptr = old;
old->proc = proc;
} else {
if (cmsg->expiration < now) {
mslog(s, proc, LOG_INFO, "ignoring expired cookie");
return -1;
}
proc->cookie_ptr = new_cookie_entry(&s->cookies, proc, req->cookie.data, req->cookie.len);
if (proc->cookie_ptr == NULL)
return -1;
}
if (cmsg->groupname)
snprintf(proc->groupname, sizeof(proc->groupname), "%s", cmsg->groupname);
if (cmsg->hostname)
snprintf(proc->hostname, sizeof(proc->hostname), "%s", cmsg->hostname);
memcpy(proc->ipv4_seed, &cmsg->ipv4_seed, sizeof(proc->ipv4_seed));
return 0;
}
@@ -256,17 +276,12 @@ int check_multiple_users(main_server_st *s, struct proc_st* proc)
struct proc_st *ctmp = NULL, *cpos;
unsigned int entries = 1; /* that one */
if (s->config->max_same_clients == 0)
return 0;
list_for_each_safe(&s->proc_list.head, ctmp, cpos, list) {
if (ctmp != proc && ctmp->pid != -1) {
if (ctmp->cookie_size == proc->cookie_size &&
memcmp(proc->cookie, ctmp->cookie, ctmp->cookie_size) == 0) {
mslog(s, ctmp, LOG_DEBUG, "disconnecting '%s' due to new cookie connection", ctmp->username);
/* steal its leases */
steal_ip_leases(ctmp, proc);
kill(ctmp->pid, SIGTERM);
} else if (strcmp(proc->username, ctmp->username) == 0) {
if (strcmp(proc->username, ctmp->username) == 0) {
entries++;
}
}

View File

@@ -193,6 +193,14 @@ void remove_proc(main_server_st * s, struct proc_st *proc, unsigned k)
if (proc->ipv4 || proc->ipv6)
remove_ip_leases(s, proc);
/* expire any available cookies */
if (proc->cookie_ptr) {
unsigned timeout = s->config->cookie_timeout;
proc->cookie_ptr->expiration = time(0) + timeout;
proc->cookie_ptr->proc = NULL;
}
talloc_free(proc);
}

View File

@@ -588,6 +588,7 @@ void clear_lists(main_server_st *s)
tls_cache_deinit(&s->tls_db);
ip_lease_deinit(&s->ip_leases);
ctl_handler_deinit(s);
cookie_db_deinit(&s->cookies);
}
static void kill_children(main_server_st* s)
@@ -746,7 +747,7 @@ fail:
}
#define MAINTAINANCE_TIME(s) (MIN(300, ((s)->config->cookie_validity + 300)))
#define MAINTAINANCE_TIME(s) (300)
static void check_other_work(main_server_st *s)
{
@@ -796,6 +797,7 @@ unsigned total = 10;
need_maintenance = 0;
mslog(s, NULL, LOG_DEBUG, "performing maintenance");
expire_tls_sessions(s);
expire_cookies(&s->cookies);
alarm(MAINTAINANCE_TIME(s));
}
}
@@ -866,6 +868,7 @@ int main(int argc, char** argv)
list_head_init(&s->ban_list.head);
list_head_init(&s->script_list.head);
tls_cache_init(s, &s->tls_db);
cookie_db_init(s, &s->cookies);
ip_lease_init(&s->ip_leases);
sigemptyset(&blockset);

View File

@@ -28,9 +28,10 @@
#include <vpn.h>
#include <tlslib.h>
#include "ipc.pb-c.h"
#include <cookies.h>
#include <common.h>
#define COOKIE_KEY_SIZE 16
int cmd_parser (void *pool, int argc, char **argv, struct cfg_st** config);
void reload_cfg_file(void *pool, struct cfg_st* config);
void clear_cfg_file(struct cfg_st* config);
@@ -71,6 +72,14 @@ enum {
PS_AUTH_COMPLETED, /* successful authentication */
};
typedef struct cookie_entry_st {
struct proc_st *proc; /* may be null, otherwise the proc that uses that cookie */
time_t expiration; /* -1 or the time it should expire */
uint8_t *cookie; /* the cookie associated with the session */
unsigned cookie_size;
} cookie_st;
/* Each worker process maps to a unique proc_st structure.
*/
typedef struct proc_st {
@@ -100,8 +109,6 @@ typedef struct proc_st {
char username[MAX_USERNAME_SIZE]; /* the owner */
char groupname[MAX_GROUPNAME_SIZE]; /* the owner's group */
char hostname[MAX_HOSTNAME_SIZE]; /* the requested hostname */
uint8_t *cookie; /* the cookie associated with the session */
unsigned cookie_size;
/* the following are copied here from the worker process for reporting
* purposes (from main-ctl-handler). */
@@ -109,6 +116,9 @@ typedef struct proc_st {
char tls_ciphersuite[MAX_CIPHERSUITE_NAME];
char dtls_ciphersuite[MAX_DTLS_CIPHERSUITE_NAME];
/* pointer to the cookie used by this session */
struct cookie_entry_st *cookie_ptr;
/* if the session is initiated by a cookie the following two are set
* and are considered when generating an IP address. That is used to
* generate the same address as previously allocated.
@@ -151,10 +161,16 @@ struct ban_list_st {
struct list_head head;
};
struct cookie_entry_db_st {
struct htable *db;
unsigned total;
};
typedef struct main_server_st {
struct cfg_st *config;
struct ip_lease_db_st ip_leases;
struct cookie_entry_db_st cookies;
tls_sess_db_st tls_db;
tls_st *creds;

View File

@@ -208,11 +208,14 @@ auth-timeout = 40
# a failed authentication attempt.
#min-reauth-time = 2
# Cookie validity time (in seconds)
# Cookie timeout (in seconds)
# Once a client is authenticated he's provided a cookie with
# which he can reconnect. This option sets the maximum lifetime
# of that cookie.
cookie-validity = 10800
# which he can reconnect. That cookie will be invalided if not
# used within this timeout value. On a user disconnection, that
# cookie will also be active for this time amount prior to be
# invalid. That allows a reasonable amount of time for roaming between
# networks.
cookie-timeout = 300
# Whether roaming is allowed, i.e., if true a cookie is
# restricted to a single IP address and cannot be re-used

View File

@@ -93,7 +93,9 @@ static int generate_cookie(sec_mod_st * sec, client_entry_st * entry)
return -1;
}
msg.expiration = time(0) + sec->config->cookie_validity;
/* this is the time when this cookie must be activated (used to authenticate).
* if not activated by that time it expires */
msg.expiration = time(0) + sec->config->cookie_timeout;
ret =
encrypt_cookie(entry, &sec->dcookie_key, &msg, &entry->cookie,

View File

@@ -210,7 +210,7 @@ struct cfg_st {
char* socket_file_prefix;
unsigned deny_roaming; /* whether a cookie is restricted to a single IP */
time_t cookie_validity; /* in seconds */
time_t cookie_timeout; /* in seconds */
time_t rekey_time; /* in seconds */
unsigned rekey_method; /* REKEY_METHOD_ */

View File

@@ -833,8 +833,8 @@ int post_common_handler(worker_st * ws, unsigned http_ver)
ret =
tls_printf(ws->session,
"Set-Cookie: webvpn=%s; Max-Age=%u; Secure\r\n",
str_cookie, (unsigned)ws->config->cookie_validity);
"Set-Cookie: webvpn=%s; Secure\r\n",
str_cookie);
if (ret < 0)
return -1;