Optimize BASIC AUTH checks with sessions.
[mod_auth_gssapi.git] / src / sessions.c
index 1086505..2653ccd 100644 (file)
@@ -1,6 +1,9 @@
 /* Copyright (C) 2014 mod_auth_gssapi authors - See COPYING for (C) terms */
 
 #include "mod_auth_gssapi.h"
+#include "asn1c/GSSSessionData.h"
+
+APLOG_USE_MODULE(auth_gssapi);
 
 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;
@@ -39,6 +42,48 @@ static apr_status_t mag_session_set(request_rec *req, session_rec *sess,
     return DECLINED;
 }
 
+static bool encode_GSSSessionData(apr_pool_t *mempool,
+                                  GSSSessionData_t *gsessdata,
+                                  unsigned char **buf, int *len)
+{
+    asn_enc_rval_t rval;
+    unsigned char *buffer = NULL;
+    size_t buflen;
+    bool ret = false;
+
+    /* dry run to compute the size */
+    rval = der_encode(&asn_DEF_GSSSessionData, gsessdata, NULL, NULL);
+    if (rval.encoded == -1) goto done;
+
+    buflen = rval.encoded;
+    buffer = apr_pcalloc(mempool, buflen);
+
+    /* now for real */
+    rval = der_encode_to_buffer(&asn_DEF_GSSSessionData,
+                                gsessdata, buffer, buflen);
+    if (rval.encoded == -1) goto done;
+
+    *buf = buffer;
+    *len = buflen;
+    ret = true;
+
+done:
+    return ret;
+}
+
+static GSSSessionData_t *decode_GSSSessionData(void *buf, size_t len)
+{
+    GSSSessionData_t *gsessdata = NULL;
+    asn_dec_rval_t rval;
+
+    rval = ber_decode(NULL, &asn_DEF_GSSSessionData,
+                      (void **)&gsessdata, buf, len);
+    if (rval.code == RC_OK) {
+        return gsessdata;
+    }
+    return NULL;
+}
+
 #define MAG_BEARER_KEY "MagBearerToken"
 
 void mag_check_session(request_rec *req,
@@ -51,7 +96,7 @@ void mag_check_session(request_rec *req,
     int declen;
     struct databuf ctxbuf = { 0 };
     struct databuf cipherbuf = { 0 };
-    char *next, *last;
+    GSSSessionData_t *gsessdata;
     time_t expiration;
 
     rc = mag_session_load(req, &sess);
@@ -101,38 +146,53 @@ void mag_check_session(request_rec *req,
         return;
     }
 
+    gsessdata = decode_GSSSessionData(ctxbuf.value, ctxbuf.length);
+    if (!gsessdata) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
+                      "Failed to unpack session data!");
+        return;
+    }
+
+    /* booleans */
+    if (gsessdata->established != 0) mc->established = true;
+    if (gsessdata->delegated != 0) mc->delegated = true;
+
     /* get time */
-    next = apr_strtok((char *)ctxbuf.value, ":", &last);
-    expiration = (time_t)apr_atoi64(next);
+    expiration = gsessdata->expiration;
     if (expiration < time(NULL)) {
         /* credentials fully expired, return nothing */
-        return;
+        goto done;
     }
 
-    /* user name is next */
-    next = apr_strtok(NULL, ":", &last);
-    mc->user_name = apr_pstrdup(mc->parent, next);
-    if (!mc->user_name) return;
+    /* user name */
+    mc->user_name = apr_pstrndup(mc->parent,
+                                 (char *)gsessdata->username.buf,
+                                 gsessdata->username.size);
+    if (!mc->user_name) goto done;
 
-    /* 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;
+    /* gssapi name */
+    mc->gss_name = apr_pstrndup(mc->parent,
+                                (char *)gsessdata->gssname.buf,
+                                gsessdata->gssname.size);
+    if (!mc->gss_name) goto done;
 
     /* OK we have a valid token */
     mc->established = true;
+
+done:
+    ASN_STRUCT_FREE(asn_DEF_GSSSessionData, gsessdata);
 }
 
 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 };
+    GSSSessionData_t gsessdata = { 0 };
     apr_status_t rc;
+    bool ret;
 
     /* we save the session only if the authentication is established */
 
@@ -155,23 +215,31 @@ void mag_attempt_session(request_rec *req,
         }
     }
 
-    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;
+    gsessdata.established = mc->established?1:0;
+    gsessdata.delegated = mc->delegated?1:0;
+    gsessdata.expiration = mc->expiration;
+    if (OCTET_STRING_fromString(&gsessdata.username, mc->user_name) != 0)
+        goto done;
+    if (OCTET_STRING_fromString(&gsessdata.gssname, mc->gss_name) != 0)
+        goto done;
+    ret = encode_GSSSessionData(req->pool, &gsessdata,
+                                &plainbuf.value, &plainbuf.length);
+    if (ret == false) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
+                      "Failed to pack session data!");
+        goto done;
+    }
 
     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;
+        goto done;
     }
 
     ctxbuf.length = apr_base64_encode_len(cipherbuf.length);
     ctxbuf.value = apr_pcalloc(req->pool, ctxbuf.length);
-    if (!ctxbuf.value) return;
+    if (!ctxbuf.value) goto done;
 
     ctxbuf.length = apr_base64_encode((char *)ctxbuf.value,
                                       (char *)cipherbuf.value,
@@ -181,7 +249,64 @@ void mag_attempt_session(request_rec *req,
     if (rc != OK) {
         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
                       "Failed to set session data!");
-        return;
     }
+
+done:
+    ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_GSSSessionData, &gsessdata);
+}
+
+static int mag_basic_hmac(struct seal_key *key, unsigned char *mac,
+                          gss_buffer_desc user, gss_buffer_desc pwd)
+{
+    struct databuf hmacbuf = { mac, 0 };
+    int data_size = user.length + pwd.length + 1;
+    unsigned char data[data_size];
+    struct databuf databuf = { data, data_size };
+
+    memcpy(data, user.value, user.length);
+    data[user.length] = '\0';
+    memcpy(&data[user.length + 1], pwd.value, pwd.length);
+
+    return HMAC_BUFFER(key, &databuf, &hmacbuf);
+}
+
+bool mag_basic_check(struct mag_config *cfg, struct mag_conn *mc,
+                     gss_buffer_desc user, gss_buffer_desc pwd)
+{
+    int mac_size = get_mac_size(cfg->mag_skey);
+    unsigned char mac[mac_size];
+    int ret, i, j;
+    bool res = false;
+
+    if (mac_size == 0) return false;
+
+    ret = mag_basic_hmac(cfg->mag_skey, mac, user, pwd);
+    if (ret != 0) goto done;
+
+    for (i = 0, j = 0; i < mac_size; i++) {
+        if (mc->basic_hash.value[i] != mac[i]) j++;
+    }
+    if (j == 0) res = true;
+
+done:
+    if (res == false) {
+        mc->basic_hash.value = NULL;
+        mc->basic_hash.length = 0;
+    }
+    return res;
 }
 
+void mag_basic_cache(struct mag_config *cfg, struct mag_conn *mc,
+                     gss_buffer_desc user, gss_buffer_desc pwd)
+{
+    int mac_size = get_mac_size(cfg->mag_skey);
+    unsigned char mac[mac_size];
+    int ret;
+
+    ret = mag_basic_hmac(cfg->mag_skey, mac, user, pwd);
+    if (ret != 0) return;
+
+    mc->basic_hash.length = mac_size;
+    mc->basic_hash.value = apr_palloc(mc->parent, mac_size);
+    memcpy(mc->basic_hash.value, mac, mac_size);
+}