Add permanent session keys support
[mod_auth_gssapi.git] / src / mod_auth_gssapi.c
1 /*
2    MOD AUTH GSSAPI
3
4    Copyright (C) 2014 Simo Sorce <simo@redhat.com>
5
6    Permission is hereby granted, free of charge, to any person obtaining a
7    copy of this software and associated documentation files (the "Software"),
8    to deal in the Software without restriction, including without limitation
9    the rights to use, copy, modify, merge, publish, distribute, sublicense,
10    and/or sell copies of the Software, and to permit persons to whom the
11    Software is furnished to do so, subject to the following conditions:
12
13    The above copyright notice and this permission notice shall be included in
14    all copies or substantial portions of the Software.
15
16    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22    DEALINGS IN THE SOFTWARE.
23 */
24
25 #include "mod_auth_gssapi.h"
26
27
28 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
29
30 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
31
32 static char *mag_status(request_rec *req, int type, uint32_t err)
33 {
34     uint32_t maj_ret, min_ret;
35     gss_buffer_desc text;
36     uint32_t msg_ctx;
37     char *msg_ret;
38     int len;
39
40     msg_ret = NULL;
41     msg_ctx = 0;
42     do {
43         maj_ret = gss_display_status(&min_ret, err, type,
44                                      GSS_C_NO_OID, &msg_ctx, &text);
45         if (maj_ret != GSS_S_COMPLETE) {
46             return msg_ret;
47         }
48
49         len = text.length;
50         if (msg_ret) {
51             msg_ret = apr_psprintf(req->pool, "%s, %*s",
52                                    msg_ret, len, (char *)text.value);
53         } else {
54             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
55         }
56         gss_release_buffer(&min_ret, &text);
57     } while (msg_ctx != 0);
58
59     return msg_ret;
60 }
61
62 static char *mag_error(request_rec *req, const char *msg,
63                        uint32_t maj, uint32_t min)
64 {
65     char *msg_maj;
66     char *msg_min;
67
68     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
69     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
70     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
71 }
72
73 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
74
75 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
76                            apr_pool_t *temp, server_rec *s)
77 {
78     /* FIXME: create mutex to deal with connections and contexts ? */
79     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
80     mag_post_config_session();
81
82     return OK;
83 }
84
85 static int mag_pre_connection(conn_rec *c, void *csd)
86 {
87     struct mag_conn *mc;
88
89     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
90     if (!mc) return DECLINED;
91
92     mc->parent = c->pool;
93     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
94     return OK;
95 }
96
97 static apr_status_t mag_conn_destroy(void *ptr)
98 {
99     struct mag_conn *mc = (struct mag_conn *)ptr;
100     uint32_t min;
101
102     if (mc->ctx) {
103         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
104         mc->established = false;
105     }
106     return APR_SUCCESS;
107 }
108
109 static bool mag_conn_is_https(conn_rec *c)
110 {
111     if (mag_is_https) {
112         if (mag_is_https(c)) return true;
113     }
114
115     return false;
116 }
117
118 static int mag_auth(request_rec *req)
119 {
120     const char *type;
121     struct mag_config *cfg;
122     const char *auth_header;
123     char *auth_header_type;
124     char *auth_header_value;
125     int ret = HTTP_UNAUTHORIZED;
126     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
127     gss_ctx_id_t *pctx;
128     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
129     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
130     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
131     gss_name_t client = GSS_C_NO_NAME;
132     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
133     uint32_t flags;
134     uint32_t vtime;
135     uint32_t maj, min;
136     char *reply;
137     size_t replen;
138     char *clientname;
139     gss_OID mech_type = GSS_C_NO_OID;
140     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
141     struct mag_conn *mc = NULL;
142
143     type = ap_auth_type(req);
144     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
145         return DECLINED;
146     }
147
148     /* ignore auth for subrequests */
149     if (!ap_is_initial_req(req)) {
150         return OK;
151     }
152
153     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
154
155     if (cfg->ssl_only) {
156         if (!mag_conn_is_https(req->connection)) {
157             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
158                           "Not a TLS connection, refusing to authenticate!");
159             goto done;
160         }
161     }
162
163     if (cfg->gss_conn_ctx) {
164         mc = (struct mag_conn *)ap_get_module_config(
165                                                 req->connection->conn_config,
166                                                 &auth_gssapi_module);
167         if (!mc) {
168             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
169                           "Failed to retrieve connection context!");
170             goto done;
171         }
172     }
173
174     /* if available, session always supersedes connection bound data */
175     mag_check_session(req, cfg, &mc);
176
177     if (mc) {
178         /* register the context in the memory pool, so it can be freed
179          * when the connection/request is terminated */
180         apr_pool_userdata_set(mc, "mag_conn_ptr",
181                               mag_conn_destroy, mc->parent);
182
183         if (mc->established) {
184             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
185                           "Already established context found!");
186             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
187             req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
188             req->user = apr_pstrdup(req->pool, mc->user_name);
189             ret = OK;
190             goto done;
191         }
192         pctx = &mc->ctx;
193     } else {
194         pctx = &ctx;
195     }
196
197     auth_header = apr_table_get(req->headers_in, "Authorization");
198     if (!auth_header) goto done;
199
200     auth_header_type = ap_getword_white(req->pool, &auth_header);
201     if (!auth_header_type) goto done;
202
203     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
204
205     auth_header_value = ap_getword_white(req->pool, &auth_header);
206     if (!auth_header_value) goto done;
207     input.length = apr_base64_decode_len(auth_header_value) + 1;
208     input.value = apr_pcalloc(req->pool, input.length);
209     if (!input.value) goto done;
210     input.length = apr_base64_decode(input.value, auth_header_value);
211
212     maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL,
213                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
214                                  &client, &mech_type, &output, &flags, &vtime,
215                                  &delegated_cred);
216     if (GSS_ERROR(maj)) {
217         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
218                       mag_error(req, "gss_accept_sec_context() failed",
219                                 maj, min));
220         goto done;
221     }
222
223     if (maj == GSS_S_CONTINUE_NEEDED) {
224         if (!mc) {
225             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
226                           "Mechanism needs continuation but neither "
227                           "GssapiConnectionBound nor "
228                           "GssapiUseSessions are available");
229             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
230             gss_release_buffer(&min, &output);
231             output.length = 0;
232         }
233         /* auth not complete send token and wait next packet */
234         goto done;
235     }
236
237 #ifdef HAVE_GSS_STORE_CRED_INTO
238     if (cfg->cred_store.count != 0 && delegated_cred != GSS_C_NO_CREDENTIAL) {
239         gss_key_value_set_desc store = {0, NULL};
240         /* FIXME: run substitutions */
241
242         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
243                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
244     }
245 #endif
246
247     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
248
249     /* Always set the GSS name in an env var */
250     maj = gss_display_name(&min, client, &name, NULL);
251     if (GSS_ERROR(maj)) {
252         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
253                       mag_error(req, "gss_accept_sec_context() failed",
254                                 maj, min));
255         goto done;
256     }
257     clientname = apr_pstrndup(req->pool, name.value, name.length);
258     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
259
260     if (cfg->map_to_local) {
261         maj = gss_localname(&min, client, mech_type, &lname);
262         if (maj != GSS_S_COMPLETE) {
263             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
264                           mag_error(req, "gss_localname() failed", maj, min));
265             goto done;
266         }
267         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
268     } else {
269         req->user = clientname;
270     }
271
272     if (mc) {
273         mc->user_name = apr_pstrdup(mc->parent, req->user);
274         mc->gss_name = apr_pstrdup(mc->parent, clientname);
275         mc->established = true;
276         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
277             vtime = MIN_SESS_EXP_TIME;
278         }
279         mc->expiration = time(NULL) + vtime;
280         mag_attempt_session(req, cfg, mc);
281     }
282
283     ret = OK;
284
285 done:
286     if (ret == HTTP_UNAUTHORIZED) {
287         if (output.length != 0) {
288             replen = apr_base64_encode_len(output.length) + 1;
289             reply = apr_pcalloc(req->pool, 10 + replen);
290             if (reply) {
291                 memcpy(reply, "Negotiate ", 10);
292                 apr_base64_encode(&reply[10], output.value, output.length);
293                 apr_table_add(req->err_headers_out,
294                               "WWW-Authenticate", reply);
295             }
296         } else {
297             apr_table_add(req->err_headers_out,
298                           "WWW-Authenticate", "Negotiate");
299         }
300     }
301     gss_release_cred(&min, &delegated_cred);
302     gss_release_buffer(&min, &output);
303     gss_release_name(&min, &client);
304     gss_release_buffer(&min, &name);
305     gss_release_buffer(&min, &lname);
306     return ret;
307 }
308
309
310 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
311 {
312     struct mag_config *cfg;
313
314     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
315     if (!cfg) return NULL;
316     cfg->pool = p;
317
318     return cfg;
319 }
320
321 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
322 {
323     struct mag_config *cfg = (struct mag_config *)mconfig;
324     cfg->ssl_only = on ? true : false;
325     return NULL;
326 }
327
328 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
329 {
330     struct mag_config *cfg = (struct mag_config *)mconfig;
331     cfg->map_to_local = on ? true : false;
332     return NULL;
333 }
334
335 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
336 {
337     struct mag_config *cfg = (struct mag_config *)mconfig;
338     cfg->gss_conn_ctx = on ? true : false;
339     return NULL;
340 }
341
342 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
343 {
344     struct mag_config *cfg = (struct mag_config *)mconfig;
345     cfg->use_sessions = on ? true : false;
346     return NULL;
347 }
348
349 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
350 {
351     struct mag_config *cfg = (struct mag_config *)mconfig;
352     struct databuf keys;
353     unsigned char *val;
354     apr_status_t rc;
355     const char *k;
356     int l;
357
358     if (strncmp(w, "key:", 4) != 0) {
359         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
360                      "Invalid key format, expected prefix 'key:'");
361         return NULL;
362     }
363     k = w + 4;
364
365     l = apr_base64_decode_len(k);
366     val = apr_palloc(parms->temp_pool, l);
367     if (!val) {
368         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
369                      "Failed to get memory to decode key");
370         return NULL;
371     }
372
373     keys.length = (int)apr_base64_decode_binary(val, k);
374     keys.value = (unsigned char *)val;
375
376     if (keys.length != 32) {
377         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
378                      "Invalid key lenght, expected 32 got %d", keys.length);
379         return NULL;
380     }
381
382     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
383     if (rc != OK) {
384         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
385                      "Failed to import sealing key!");
386     }
387     return NULL;
388 }
389
390 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
391                                   const char *w)
392 {
393     struct mag_config *cfg = (struct mag_config *)mconfig;
394     gss_key_value_element_desc *elements;
395     uint32_t count;
396     size_t size;
397     const char *p;
398     char *value;
399     char *key;
400
401     p = strchr(w, ':');
402     if (!p) {
403         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
404                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
405         return NULL;
406     }
407
408     key = apr_pstrndup(parms->pool, w, (p-w));
409     value = apr_pstrdup(parms->pool, p + 1);
410     if (!key || !value) {
411         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
412                      "%s", "OOM handling GssapiCredStore option");
413         return NULL;
414     }
415
416     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
417     elements = apr_palloc(parms->pool, size);
418     if (!elements) {
419         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
420                      "%s", "OOM handling GssapiCredStore option");
421         return NULL;
422     }
423
424     for (count = 0; count < cfg->cred_store.count; count++) {
425         elements[count] = cfg->cred_store.elements[count];
426     }
427     elements[count].key = key;
428     elements[count].value = value;
429
430     cfg->cred_store.elements = elements;
431     cfg->cred_store.count = count;
432
433     return NULL;
434 }
435
436 static const command_rec mag_commands[] = {
437     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
438                   "Work only if connection is SSL Secured"),
439     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
440                   "Work only if connection is SSL Secured"),
441     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
442                   "Authentication is bound to the TCP connection"),
443     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
444                   "Authentication uses mod_sessions to hold status"),
445     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
446                      "Key Used to seal session data."),
447     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
448                     "Credential Store"),
449     { NULL }
450 };
451
452 static void
453 mag_register_hooks(apr_pool_t *p)
454 {
455     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
456     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
457     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
458 }
459
460 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
461 {
462     STANDARD20_MODULE_STUFF,
463     mag_create_dir_config,
464     NULL,
465     NULL,
466     NULL,
467     mag_commands,
468     mag_register_hooks
469 };