diff options
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/crypto.c | 210 | ||||
-rw-r--r-- | src/crypto.h | 17 | ||||
-rw-r--r-- | src/mod_auth_gssapi.c | 65 | ||||
-rw-r--r-- | src/mod_auth_gssapi.h | 25 | ||||
-rw-r--r-- | src/sessions.c | 187 | ||||
-rw-r--r-- | src/sessions.h | 10 |
7 files changed, 493 insertions, 23 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 95b85b3..2273194 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ all-local: - @APXS@ -c ${LIBS} ${CFLAGS} mod_auth_gssapi.c + @APXS@ -c ${LIBS} ${CFLAGS} mod_auth_gssapi.c sessions.c crypto.c install-exec-local: if test ! -d ${APXS_LIBEXECDIR}; then mkdir -p ${APXS_LIBEXECDIR}; fi diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..9be58e5 --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,210 @@ +/* Copyright (C) 2014 mod_auth_gssapi authors - See COPYING for (C) terms */ + +#include <openssl/evp.h> +#include <openssl/hmac.h> +#include <openssl/rand.h> +#include <stdbool.h> +#include "crypto.h" + +struct seal_key { + const EVP_CIPHER *cipher; + const EVP_MD *md; + unsigned char *ekey; + unsigned char *hkey; +}; + +apr_status_t SEAL_KEY_CREATE(struct seal_key **skey) +{ + struct seal_key *n; + int ret; + + n = calloc(1, sizeof(*n)); + if (!n) return ENOMEM; + + n->cipher = EVP_aes_128_cbc(); + if (!n->cipher) { + free(n); + return EFAULT; + } + + n->md = EVP_sha256(); + if (!n->md) { + free(n); + return EFAULT; + } + + n->ekey = malloc(n->cipher->key_len); + if (!n->ekey) { + free(n); + return ENOMEM; + } + + n->hkey = malloc(n->cipher->key_len); + if (!n->hkey) { + free(n); + return ENOMEM; + } + + ret = RAND_bytes(n->ekey, n->cipher->key_len); + if (ret == 0) { + free(n->ekey); + free(n->hkey); + free(n); + return EFAULT; + } + + ret = RAND_bytes(n->hkey, n->cipher->key_len); + if (ret == 0) { + free(n->ekey); + free(n->hkey); + free(n); + return EFAULT; + } + + *skey = n; + return 0; +} + +apr_status_t SEAL_BUFFER(apr_pool_t *p, struct seal_key *skey, + struct databuf *plain, struct databuf *cipher) +{ + apr_status_t err = EFAULT; + EVP_CIPHER_CTX ctx = { 0 }; + HMAC_CTX hmac_ctx = { 0 }; + uint8_t rbuf[16]; + unsigned int len; + int outlen, totlen; + int ret; + + EVP_CIPHER_CTX_init(&ctx); + + /* confounder to avoid exposing random numbers directly to clients + * as IVs */ + ret = RAND_bytes(rbuf, 16); + if (ret == 0) goto done; + + if (cipher->length == 0) { + /* add space for confounder and padding and MAC */ + cipher->length = (plain->length / 16 + 2) * 16; + cipher->value = apr_palloc(p, cipher->length + skey->md->md_size); + if (!cipher->value) { + err = ENOMEM; + goto done; + } + } + + ret = EVP_EncryptInit_ex(&ctx, skey->cipher, NULL, skey->ekey, NULL); + if (ret == 0) goto done; + totlen = 0; + + outlen = cipher->length; + ret = EVP_EncryptUpdate(&ctx, cipher->value, &outlen, rbuf, 16); + if (ret == 0) goto done; + totlen += outlen; + + outlen = cipher->length - totlen; + ret = EVP_EncryptUpdate(&ctx, &cipher->value[totlen], &outlen, + plain->value, plain->length); + if (ret == 0) goto done; + totlen += outlen; + + outlen = cipher->length - totlen; + ret = EVP_EncryptFinal_ex(&ctx, &cipher->value[totlen], &outlen); + if (ret == 0) goto done; + totlen += outlen; + + /* now MAC the buffer */ + HMAC_CTX_init(&hmac_ctx); + + ret = HMAC_Init_ex(&hmac_ctx, skey->hkey, + skey->cipher->key_len, skey->md, NULL); + if (ret == 0) goto done; + + ret = HMAC_Update(&hmac_ctx, cipher->value, totlen); + if (ret == 0) goto done; + + ret = HMAC_Final(&hmac_ctx, &cipher->value[totlen], &len); + if (ret == 0) goto done; + + cipher->length = totlen + len; + err = 0; + +done: + EVP_CIPHER_CTX_cleanup(&ctx); + HMAC_CTX_cleanup(&hmac_ctx); + return err; +} + +apr_status_t UNSEAL_BUFFER(apr_pool_t *p, struct seal_key *skey, + struct databuf *cipher, struct databuf *plain) +{ + apr_status_t err = EFAULT; + EVP_CIPHER_CTX ctx = { 0 }; + HMAC_CTX hmac_ctx = { 0 }; + unsigned char mac[skey->md->md_size]; + unsigned int len; + int outlen, totlen; + volatile bool equal = true; + int ret, i; + + /* check MAC first */ + HMAC_CTX_init(&hmac_ctx); + + ret = HMAC_Init_ex(&hmac_ctx, skey->hkey, + skey->cipher->key_len, skey->md, NULL); + if (ret == 0) goto done; + + cipher->length -= skey->md->md_size; + + ret = HMAC_Update(&hmac_ctx, cipher->value, cipher->length); + if (ret == 0) goto done; + + ret = HMAC_Final(&hmac_ctx, mac, &len); + if (ret == 0) goto done; + + if (len != skey->md->md_size) goto done; + for (i = 0; i < skey->md->md_size; i++) { + if (cipher->value[cipher->length + i] != mac[i]) equal = false; + /* not breaking intentionally, + * or we would allow an oracle attack */ + } + if (!equal) goto done; + + EVP_CIPHER_CTX_init(&ctx); + + if (plain->length == 0) { + plain->length = cipher->length; + plain->value = apr_palloc(p, plain->length); + if (!plain->value) { + err = ENOMEM; + goto done; + } + } + + ret = EVP_DecryptInit_ex(&ctx, skey->cipher, NULL, skey->ekey, NULL); + if (ret == 0) goto done; + + totlen = 0; + outlen = plain->length; + ret = EVP_DecryptUpdate(&ctx, plain->value, &outlen, + cipher->value, cipher->length); + if (ret == 0) goto done; + + totlen += outlen; + outlen = plain->length - totlen; + ret = EVP_DecryptFinal_ex(&ctx, plain->value, &outlen); + if (ret == 0) goto done; + + totlen += outlen; + /* now remove the confounder */ + totlen -= 16; + memmove(plain->value, plain->value + 16, totlen); + + plain->length = totlen; + err = 0; + +done: + EVP_CIPHER_CTX_cleanup(&ctx); + HMAC_CTX_cleanup(&hmac_ctx); + return err; +} diff --git a/src/crypto.h b/src/crypto.h new file mode 100644 index 0000000..a8b5ca0 --- /dev/null +++ b/src/crypto.h @@ -0,0 +1,17 @@ +/* Copyright (C) 2014 mod_auth_gssapi authors - See COPYING for (C) terms */ + +#include <apr_errno.h> +#include <apr_pools.h> + +struct seal_key; + +struct databuf { + unsigned char *value; + int length; +}; + +apr_status_t SEAL_KEY_CREATE(struct seal_key **skey); +apr_status_t SEAL_BUFFER(apr_pool_t *p, struct seal_key *skey, + struct databuf *plain, struct databuf *cipher); +apr_status_t UNSEAL_BUFFER(apr_pool_t *p, struct seal_key *skey, + struct databuf *cipher, struct databuf *plain); diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c index e8c1966..7e8df96 100644 --- a/src/mod_auth_gssapi.c +++ b/src/mod_auth_gssapi.c @@ -24,6 +24,7 @@ #include "mod_auth_gssapi.h" + module AP_MODULE_DECLARE_DATA auth_gssapi_module; APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); @@ -71,24 +72,16 @@ static char *mag_error(request_rec *req, const char *msg, static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL; -static int mag_post_config(apr_pool_t *cfg, apr_pool_t *log, +static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log, apr_pool_t *temp, server_rec *s) { /* FIXME: create mutex to deal with connections and contexts ? */ mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + mag_post_config_session(); return OK; } - -struct mag_conn { - apr_pool_t *parent; - gss_ctx_id_t ctx; - bool established; - char *user_name; - char *gss_name; -}; - static int mag_pre_connection(conn_rec *c, void *csd) { struct mag_conn *mc; @@ -138,6 +131,7 @@ static int mag_auth(request_rec *req) gss_name_t client = GSS_C_NO_NAME; gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL; uint32_t flags; + uint32_t vtime; uint32_t maj, min; char *reply; size_t replen; @@ -151,6 +145,11 @@ static int mag_auth(request_rec *req) return DECLINED; } + /* ignore auth for subrequests */ + if (!ap_is_initial_req(req)) { + return OK; + } + cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module); if (cfg->ssl_only) { @@ -166,11 +165,24 @@ static int mag_auth(request_rec *req) req->connection->conn_config, &auth_gssapi_module); if (!mc) { - return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req, + "Failed to retrieve connection context!"); + goto done; } + } + + /* if available, session always supersedes connection bound data */ + mag_check_session(req, cfg, &mc); + + if (mc) { + /* register the context in the memory pool, so it can be freed + * when the connection/request is terminated */ + apr_pool_userdata_set(mc, "mag_conn_ptr", + mag_conn_destroy, mc->parent); + if (mc->established) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req, - "Connection bound pre-authentication found."); + "Already established context found!"); apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name); req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate"); req->user = apr_pstrdup(req->pool, mc->user_name); @@ -199,7 +211,7 @@ static int mag_auth(request_rec *req) maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL, &input, GSS_C_NO_CHANNEL_BINDINGS, - &client, &mech_type, &output, &flags, NULL, + &client, &mech_type, &output, &flags, &vtime, &delegated_cred); if (GSS_ERROR(maj)) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, @@ -208,19 +220,17 @@ static int mag_auth(request_rec *req) goto done; } - /* register the context in the connection pool, so it can be freed - * when the connection is terminated */ - apr_pool_userdata_set(mc, "mag_conn_ptr", mag_conn_destroy, mc->parent); - if (maj == GSS_S_CONTINUE_NEEDED) { - if (!cfg->gss_conn_ctx) { + if (!mc) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, - "Mechanism needs continuation but " - "GssapiConnectionBound is off."); + "Mechanism needs continuation but neither " + "GssapiConnectionBound nor " + "GssapiUseSessions are available"); gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER); gss_release_buffer(&min, &output); output.length = 0; } + /* auth not complete send token and wait next packet */ goto done; } @@ -263,6 +273,11 @@ static int mag_auth(request_rec *req) mc->user_name = apr_pstrdup(mc->parent, req->user); mc->gss_name = apr_pstrdup(mc->parent, clientname); mc->established = true; + if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) { + vtime = MIN_SESS_EXP_TIME; + } + mc->expiration = time(NULL) + vtime; + mag_attempt_session(req, cfg, mc); } ret = OK; @@ -298,6 +313,7 @@ static void *mag_create_dir_config(apr_pool_t *p, char *dir) cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config)); if (!cfg) return NULL; + cfg->pool = p; return cfg; } @@ -323,6 +339,13 @@ static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on) return NULL; } +static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on) +{ + struct mag_config *cfg = (struct mag_config *)mconfig; + cfg->use_sessions = on ? true : false; + return NULL; +} + static const char *mag_cred_store(cmd_parms *parms, void *mconfig, const char *w) { @@ -376,6 +399,8 @@ static const command_rec mag_commands[] = { "Work only if connection is SSL Secured"), AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG, "Authentication is bound to the TCP connection"), + AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG, + "Authentication uses mod_sessions to hold status"), AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG, "Credential Store"), { NULL } diff --git a/src/mod_auth_gssapi.h b/src/mod_auth_gssapi.h index 2022061..6a21254 100644 --- a/src/mod_auth_gssapi.h +++ b/src/mod_auth_gssapi.h @@ -2,16 +2,21 @@ #include <stdbool.h> #include <stdint.h> +#include <time.h> #include <gssapi/gssapi.h> #include <gssapi/gssapi_ext.h> +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include <apr_strings.h> +#include <apr_base64.h> + #include <httpd.h> #include <http_core.h> #include <http_connection.h> #include <http_log.h> #include <http_request.h> -#include <apr_strings.h> -#include <apr_base64.h> +#include <mod_session.h> /* apache's httpd.h drags in empty PACKAGE_* variables. * undefine them to avoid annoying compile warnings as they @@ -23,10 +28,26 @@ #undef PACKAGE_VERSION #include "config.h" +#include "crypto.h" +#include "sessions.h" + +#define MIN_SESS_EXP_TIME 300 /* 5 minutes validity minimum */ + struct mag_config { + apr_pool_t *pool; bool ssl_only; bool map_to_local; bool gss_conn_ctx; + bool use_sessions; gss_key_value_set_desc cred_store; + struct seal_key *mag_skey; }; +struct mag_conn { + apr_pool_t *parent; + gss_ctx_id_t ctx; + bool established; + const char *user_name; + const char *gss_name; + time_t expiration; +}; diff --git a/src/sessions.c b/src/sessions.c new file mode 100644 index 0000000..766ca66 --- /dev/null +++ b/src/sessions.c @@ -0,0 +1,187 @@ +/* Copyright (C) 2014 mod_auth_gssapi authors - See COPYING for (C) terms */ + +#include "mod_auth_gssapi.h" + +static APR_OPTIONAL_FN_TYPE(ap_session_load) *mag_sess_load_fn = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_get) *mag_sess_get_fn = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_set) *mag_sess_set_fn = NULL; + +void mag_post_config_session(void) +{ + mag_sess_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load); + mag_sess_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get); + mag_sess_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set); +} + +static apr_status_t mag_session_load(request_rec *req, session_rec **sess) +{ + if (mag_sess_load_fn) { + return mag_sess_load_fn(req, sess); + } + return DECLINED; +} + +static apr_status_t mag_session_get(request_rec *req, session_rec *sess, + const char *key, const char **value) +{ + if (mag_sess_get_fn) { + return mag_sess_get_fn(req, sess, key, value); + } + return DECLINED; +} + +static apr_status_t mag_session_set(request_rec *req, session_rec *sess, + const char *key, const char *value) +{ + if (mag_sess_set_fn) { + return mag_sess_set_fn(req, sess, key, value); + } + return DECLINED; +} + +#define MAG_BEARER_KEY "MagBearerToken" + +void mag_check_session(request_rec *req, + struct mag_config *cfg, struct mag_conn **conn) +{ + struct mag_conn *mc; + apr_status_t rc; + session_rec *sess = NULL; + const char *sessval = NULL; + int declen; + struct databuf ctxbuf = { 0 }; + struct databuf cipherbuf = { 0 }; + char *next, *last; + time_t expiration; + + rc = mag_session_load(req, &sess); + if (rc != OK || sess == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, req, + "Sessions not available, no cookies!"); + return; + } + + mc = *conn; + if (!mc) { + mc = apr_pcalloc(req->pool, sizeof(struct mag_conn)); + if (!mc) return; + + mc->parent = req->pool; + *conn = mc; + } + + rc = mag_session_get(req, sess, MAG_BEARER_KEY, &sessval); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to get session data!"); + return; + } + if (!sessval) { + /* no session established, just return */ + return; + } + + if (!cfg->mag_skey) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, req, + "Session key not available, no cookies!"); + /* we do not have a key, just return */ + return; + } + + /* decode it */ + declen = apr_base64_decode_len(sessval); + cipherbuf.value = apr_palloc(req->pool, declen); + if (!cipherbuf.value) return; + cipherbuf.length = (int)apr_base64_decode((char *)cipherbuf.value, sessval); + + rc = UNSEAL_BUFFER(req->pool, cfg->mag_skey, &cipherbuf, &ctxbuf); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to unseal session data!"); + return; + } + + /* get time */ + next = apr_strtok((char *)ctxbuf.value, ":", &last); + expiration = (time_t)apr_atoi64(next); + if (expiration < time(NULL)) { + /* credentials fully expired, return nothing */ + return; + } + + /* user name is next */ + next = apr_strtok(NULL, ":", &last); + mc->user_name = apr_pstrdup(mc->parent, next); + if (!mc->user_name) return; + + /* gssapi name (often a principal) is last. + * (because it may contain the separator as a valid char we + * just read last as is, without further tokenizing */ + mc->gss_name = apr_pstrdup(mc->parent, last); + if (!mc->gss_name) return; + + /* OK we have a valid token */ + mc->established = true; +} + +void mag_attempt_session(request_rec *req, + struct mag_config *cfg, struct mag_conn *mc) +{ + session_rec *sess = NULL; + char *sessval = NULL; + struct databuf plainbuf = { 0 }; + struct databuf cipherbuf = { 0 }; + struct databuf ctxbuf = { 0 }; + apr_status_t rc; + + /* we save the session only if the authentication is established */ + + if (!mc->established) return; + rc = mag_session_load(req, &sess); + if (rc != OK || sess == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, req, + "Sessions not available, can't send cookies!"); + return; + } + + if (!cfg->mag_skey) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, req, + "Session key not available, generating new one."); + rc = SEAL_KEY_CREATE(&cfg->mag_skey); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to create sealing key!"); + return; + } + } + + sessval = apr_psprintf(req->pool, "%ld:%s:%s", + (long)mc->expiration, mc->user_name, mc->gss_name); + if (!sessval) return; + + plainbuf.length = strlen(sessval) + 1; + plainbuf.value = (unsigned char *)sessval; + + rc = SEAL_BUFFER(req->pool, cfg->mag_skey, &plainbuf, &cipherbuf); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to seal session data!"); + return; + } + + ctxbuf.length = apr_base64_encode_len(cipherbuf.length); + ctxbuf.value = apr_pcalloc(req->pool, ctxbuf.length); + if (!ctxbuf.value) return; + + ctxbuf.length = apr_base64_encode((char *)ctxbuf.value, + (char *)cipherbuf.value, + cipherbuf.length); + + rc = mag_session_set(req, sess, MAG_BEARER_KEY, (char *)ctxbuf.value); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to set session data!"); + return; + } +} + diff --git a/src/sessions.h b/src/sessions.h new file mode 100644 index 0000000..f3b398e --- /dev/null +++ b/src/sessions.h @@ -0,0 +1,10 @@ +/* Copyright (C) 2014 mod_auth_gssapi authors - See COPYING for (C) terms */ + +struct mag_config; +struct mag_conn; + +void mag_post_config_session(void); +void mag_check_session(request_rec *req, + struct mag_config *cfg, struct mag_conn **conn); +void mag_attempt_session(request_rec *req, + struct mag_config *cfg, struct mag_conn *mc); |