Main Page | Modules | Data Structures | Directories | File List | Data Fields | Globals

ses_client.c

00001 /*
00002  * Copyright (c) 2005, 2006 by KoanLogic s.r.l. <http://www.koanlogic.com>
00003  * All rights reserved.
00004  *
00005  * This file is part of KLone, and as such it is subject to the license stated
00006  * in the LICENSE file which you have received as part of this distribution.
00007  *
00008  * $Id: ses_client.c,v 1.30 2007/08/20 16:06:08 tat Exp $
00009  */
00010 
00011 #include "klone_conf.h"
00012 #include <sys/types.h>
00013 #include <sys/stat.h>
00014 #include <stdlib.h>
00015 #include <time.h>
00016 #include <unistd.h>
00017 #include <fcntl.h>
00018 #include <openssl/hmac.h>
00019 #include <openssl/evp.h>
00020 #include <openssl/rand.h>
00021 #include <u/libu.h>
00022 #include <klone/session.h>
00023 #include <klone/request.h>
00024 #include <klone/response.h>
00025 #include <klone/vars.h>
00026 #include <klone/utils.h>
00027 #include <klone/emb.h>
00028 #include <klone/ses_prv.h>
00029 #include <klone/codecs.h>
00030 
00031 #define KL1_CLISES_DATA     "KL1_CLISES_DATA"
00032 #define KL1_CLISES_MTIME    "KL1_CLISES_MTIME"
00033 #define KL1_CLISES_HMAC     "KL1_CLISES_HMAC"
00034 #define KL1_CLISES_IV       "KL1_CLISES_IV"
00035 
00036 enum { HMAC_HEX_SIZE = 2*EVP_MAX_MD_SIZE + 1 };
00037 
00038 /* calc the hmac of data+sid+mtime */
00039 static int session_client_hmac(HMAC_CTX *ctx, char *hmac, size_t hmac_sz, 
00040     const char *data, const char *sid, const char *mtime, const char *hex_iv)
00041 {
00042     char mac[EVP_MAX_MD_SIZE];
00043     int mac_len;
00044 
00045     dbg_err_if (ctx == NULL);
00046     dbg_err_if (hmac == NULL);
00047     dbg_err_if (data == NULL);
00048     dbg_err_if (sid == NULL);
00049     dbg_err_if (mtime == NULL);
00050     /* hex_iv may be NULL */
00051     
00052     /* hmac must be at least 'EVP_MAX_MD_SIZE*2 + 1' (it will be hex encoded) */
00053     dbg_err_if(hmac_sz < EVP_MAX_MD_SIZE*2 + 1);
00054 
00055     /* calc HMAC hash of the data buf + mtime (reuse stored key and md) */
00056     HMAC_Init_ex(ctx, NULL, 0, NULL, NULL); 
00057     HMAC_Update(ctx, data, strlen(data));
00058     HMAC_Update(ctx, sid, strlen(sid));
00059     HMAC_Update(ctx, mtime, strlen(mtime));
00060     if(hex_iv)
00061         HMAC_Update(ctx, hex_iv, strlen(hex_iv));
00062     HMAC_Final(ctx, mac, &mac_len);
00063 
00064     /* encode the hash */
00065     dbg_err_if(u_hexncpy(hmac, mac, mac_len, HEXCPY_ENCODE) <= 0);
00066 
00067     return 0;
00068 err:
00069     return -1;
00070 }
00071 
00072 static int session_client_save(session_t *ss)
00073 {
00074     /* BUF_SIZE: cookie size + MAC size + gzip header + encryption padding  */
00075     enum { 
00076         MTIME_SIZE = 32, 
00077         BUF_SIZE = COOKIE_MAX_SIZE + EVP_MAX_BLOCK_LENGTH + 96 
00078     };
00079     session_opt_t *so = ss->so;
00080     char hmac[HMAC_HEX_SIZE], ebuf[BUF_SIZE], mtime[MTIME_SIZE];
00081     char *buf = NULL, cipher_iv_hex[CIPHER_IV_SIZE * 2 + 1];
00082     size_t sz;
00083 
00084     dbg_err_if (ss == NULL);
00085 
00086     #ifdef HAVE_LIBOPENSSL
00087     if(ss->so->encrypt)
00088     {
00089         /* generate a new IV for each session */
00090         dbg_err_if(!RAND_pseudo_bytes(ss->so->cipher_iv, CIPHER_IV_SIZE));
00091 
00092         /* hex encode the IV and save it in a cookie */
00093         dbg_err_if(u_hexncpy(cipher_iv_hex, ss->so->cipher_iv, CIPHER_IV_SIZE,
00094             HEXCPY_ENCODE) <= 0);
00095         dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_IV, cipher_iv_hex, 
00096             0, NULL, NULL, 0));
00097     }
00098     #endif
00099 
00100     /* save the session data to freshly alloc'd buf of size sz */
00101     dbg_err_if(session_prv_save_to_buf(ss, &buf, &sz));
00102 
00103     warn_err_ifm(sz > COOKIE_MAX_SIZE, 
00104                 "session data too big for client-side sessions");
00105 
00106     /* hex-encode the buffer */
00107     dbg_err_if(u_hexncpy(ebuf, buf, sz, HEXCPY_ENCODE) <= 0);
00108 
00109     dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_DATA, ebuf, 0, NULL, 
00110         NULL, 0));
00111 
00112     /* set mtime cookie */
00113     ss->mtime = time(0);
00114     dbg_err_if(u_snprintf(mtime, MTIME_SIZE, "%lu", ss->mtime));
00115     dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_MTIME, mtime, 0, NULL, 
00116         NULL, 0));
00117 
00118     /* calc the HMAC */
00119     dbg_err_if(session_client_hmac(&so->hmac_ctx, hmac, HMAC_HEX_SIZE, 
00120         ebuf, ss->id, mtime, ss->so->encrypt ? cipher_iv_hex : NULL));
00121 
00122     /* store the hash in a cookie */
00123     dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_HMAC, hmac, 0, NULL, 
00124         NULL, 0));
00125 
00126     return 0;
00127 err:
00128     return ~0;
00129 }
00130 
00131 static int session_client_load(session_t *ss)
00132 {
00133     session_opt_t *so = ss->so;
00134     char hmac[HMAC_HEX_SIZE], buf[COOKIE_MAX_SIZE];
00135     const char *cli_ebuf, *cli_hmac, *cli_mtime, *cli_iv;
00136     ssize_t c;
00137 
00138     dbg_err_if (ss == NULL);
00139 
00140     /* extract session data, mtime and hmac from cookies */
00141     cli_ebuf = request_get_cookie(ss->rq, KL1_CLISES_DATA);
00142     cli_mtime = request_get_cookie(ss->rq, KL1_CLISES_MTIME);
00143     cli_hmac = request_get_cookie(ss->rq, KL1_CLISES_HMAC);
00144     cli_iv = request_get_cookie(ss->rq, KL1_CLISES_IV);
00145 
00146     nop_err_if(cli_ebuf == NULL || cli_mtime == NULL || cli_hmac == NULL);
00147     /* cli_iv may be NULL */
00148 
00149     /* calc the HMAC */
00150     dbg_err_if(session_client_hmac(&so->hmac_ctx, hmac, HMAC_HEX_SIZE, 
00151         cli_ebuf, ss->id, cli_mtime, ss->so->encrypt ? cli_iv : NULL));
00152 
00153     /* compare HMACs */
00154     if(strcmp(hmac, cli_hmac))
00155     {
00156         session_remove(ss); /* remove all bad stale data */
00157         warn_err("HMAC don't match, rejecting session data");
00158     }
00159 
00160     /* hash ckeched. decode/uncompress/decrypt session data */
00161 
00162     /* hex decode and save current cipher IV */
00163     dbg_err_if((c = u_hexncpy(ss->so->cipher_iv, cli_iv, strlen(cli_iv), 
00164         HEXCPY_DECODE)) <= 0);
00165 
00166     /* set client provided mtime */
00167     ss->mtime = strtoul(cli_mtime, NULL, 0);
00168 
00169     dbg_err_if(strlen(cli_ebuf) > COOKIE_MAX_SIZE);
00170 
00171     /* hex decode session data */
00172     dbg_err_if((c = u_hexncpy(buf, cli_ebuf, strlen(cli_ebuf), 
00173         HEXCPY_DECODE)) <= 0);
00174 
00175     /* load session data from the buffer */
00176     dbg_err_if(session_prv_load_from_buf(ss, buf, c));
00177 
00178     return 0;
00179 err:
00180     return ~0;
00181 }
00182 
00183 static int session_client_term(session_t *ss)
00184 {
00185     u_unused_args(ss);
00186     return 0;
00187 }
00188 
00189 static int session_client_remove(session_t *ss)
00190 {
00191     dbg_err_if (ss == NULL);
00192     
00193     /* removes all clises-related cookies */
00194     dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_DATA, NULL, 0, NULL, 
00195         NULL, 0));
00196     dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_MTIME, NULL, 0, NULL, 
00197         NULL, 0));
00198     dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_HMAC, NULL, 0, NULL, 
00199         NULL, 0));
00200     dbg_err_if(response_set_cookie(ss->rs, KL1_CLISES_IV, NULL, 0, NULL, 
00201         NULL, 0));
00202 
00203     return 0;
00204 err:
00205     return ~0;
00206 }
00207 
00208 int session_client_create(session_opt_t *so, request_t *rq, response_t *rs, 
00209         session_t **pss)
00210 {
00211     session_t *ss = NULL;
00212 
00213     dbg_err_if (rq == NULL);
00214     dbg_err_if (rs == NULL);
00215     dbg_err_if (pss == NULL);
00216     dbg_err_if (so == NULL);
00217 
00218     ss = u_zalloc(sizeof(session_t));
00219     dbg_err_if(ss == NULL);
00220 
00221     ss->load = session_client_load;
00222     ss->save = session_client_save;
00223     ss->remove = session_client_remove;
00224     ss->term = session_client_term;
00225     ss->mtime = time(0);
00226     ss->so = so;
00227 
00228     dbg_err_if(session_prv_init(ss, rq, rs));
00229 
00230     *pss = ss;
00231 
00232     return 0;
00233 err:
00234     if(ss)
00235         session_free(ss);
00236     return ~0;
00237 }
00238 
00239 /* this function will be called once by the server during startup */
00240 int session_client_module_init(u_config_t *config, session_opt_t *so)
00241 {
00242     u_config_t *c;
00243     const char *v;
00244 
00245     /* config may be NULL */
00246     dbg_err_if (so == NULL);
00247     
00248     /* default */
00249     so->hash = EVP_sha1(); 
00250 
00251     /* always enable encryption for client-side sessions */
00252     if(!so->encrypt)
00253         warn("encryption is required for client side session");
00254     so->encrypt = 1;
00255 
00256     if(config && !u_config_get_subkey(config, "client", &c))
00257     {
00258         if((v = u_config_get_subkey_value(c, "hash_function")) != NULL)
00259         {
00260             if(!strcasecmp(v, "md5"))
00261                 so->hash = EVP_md5();
00262             else if(!strcasecmp(v, "sha1"))
00263                 so->hash = EVP_sha1();
00264             else if(!strcasecmp(v, "ripemd160"))
00265                 so->hash = EVP_ripemd160();
00266             else
00267                 warn_err("config error: bad hash_function");
00268         } 
00269     }
00270 
00271     /* initialize OpenSSL HMAC stuff */
00272     HMAC_CTX_init(&so->hmac_ctx);
00273 
00274     /* gen HMAC key */
00275     dbg_err_if(!RAND_bytes(so->hmac_key, HMAC_KEY_SIZE));
00276 
00277     /* init HMAC with our key and chose hash algorithm */
00278     HMAC_Init_ex(&so->hmac_ctx, so->hmac_key, HMAC_KEY_SIZE, so->hash, NULL);
00279 
00280     return 0;
00281 err:
00282     return ~0;
00283 }
00284 
00285