2 * Copyright (c) 2010 CESNET
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of CESNET nor the names of its contributors may
16 * be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
32 #include "mod_auth_gssapi.h"
35 get_gss_error(request_rec *r, OM_uint32 err_maj, OM_uint32 err_min, char *prefix)
37 OM_uint32 maj_stat, min_stat;
38 OM_uint32 msg_ctx = 0;
39 gss_buffer_desc status_string;
43 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
44 "GSS-API major_status:%8.8x, minor_status:%8.8x",
47 err_msg = apr_pstrdup(r->pool, prefix);
49 maj_stat = gss_display_status (&min_stat,
55 if (!GSS_ERROR(maj_stat)) {
56 err_msg = apr_pstrcat(r->pool, err_msg,
57 ": ", (char*) status_string.value, NULL);
58 gss_release_buffer(&min_stat, &status_string);
61 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
64 err_msg = apr_pstrcat(r->pool, err_msg, " (", NULL);
67 maj_stat = gss_display_status (&min_stat,
73 if (!GSS_ERROR(maj_stat)) {
74 err_msg = apr_pstrcat(r->pool, err_msg,
75 (first_pass) ? "" : ", ",
76 (char *) status_string.value,
78 gss_release_buffer(&min_stat, &status_string);
81 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
82 err_msg = apr_pstrcat(r->pool, err_msg, ")", NULL);
88 get_gss_creds(request_rec *r,
89 gss_auth_config *conf,
90 gss_cred_id_t *server_creds)
92 gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
93 OM_uint32 major_status, minor_status, minor_status2;
94 gss_name_t server_name = GSS_C_NO_NAME;
96 int have_server_princ;
98 if (conf->service_name && strcmp(conf->service_name, "Any") == 0) {
99 *server_creds = GSS_C_NO_CREDENTIAL;
103 have_server_princ = conf->service_name && strchr(conf->service_name, '/') != NULL;
104 if (have_server_princ)
105 strncpy(buf, conf->service_name, sizeof(buf));
107 snprintf(buf, sizeof(buf), "%s@%s",
108 (conf->service_name) ? conf->service_name : SERVICE_NAME,
109 ap_get_server_name(r));
112 token.length = strlen(buf) + 1;
114 major_status = gss_import_name(&minor_status, &token,
115 (have_server_princ) ? (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME : (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
117 memset(&token, 0, sizeof(token));
118 if (GSS_ERROR(major_status)) {
119 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
120 "%s", get_gss_error(r, major_status, minor_status,
121 "gss_import_name() failed"));
122 return HTTP_INTERNAL_SERVER_ERROR;
125 major_status = gss_display_name(&minor_status, server_name, &token, NULL);
126 if (GSS_ERROR(major_status)) {
127 /* Perhaps we could just ignore this error but it's safer to give up now,
129 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
130 "%s", get_gss_error(r, major_status, minor_status,
131 "gss_display_name() failed"));
132 return HTTP_INTERNAL_SERVER_ERROR;
135 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s", token.value);
136 gss_release_buffer(&minor_status, &token);
138 major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
139 GSS_C_NO_OID_SET, GSS_C_ACCEPT,
140 server_creds, NULL, NULL);
141 gss_release_name(&minor_status2, &server_name);
142 if (GSS_ERROR(major_status)) {
143 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
144 "%s", get_gss_error(r, major_status, minor_status,
145 "Failed to load GSS-API credentials"));
146 return HTTP_INTERNAL_SERVER_ERROR;
153 cmp_gss_type(gss_buffer_t token, gss_OID oid)
158 if (token->length == 0)
159 return GSS_S_DEFECTIVE_TOKEN;
163 return GSS_S_DEFECTIVE_TOKEN;
166 if ((len & 0x7f) > 4)
167 return GSS_S_DEFECTIVE_TOKEN;
171 return GSS_S_DEFECTIVE_TOKEN;
173 if (((OM_uint32) *p++) != oid->length)
174 return GSS_S_DEFECTIVE_TOKEN;
176 return memcmp(p, oid->elements, oid->length);
180 gss_authenticate(request_rec *r, gss_auth_config *conf, gss_conn_ctx ctx,
181 const char *auth_line, char **negotiate_ret_value)
183 OM_uint32 major_status, minor_status, minor_status2;
184 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
185 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
186 const char *auth_param = NULL;
188 gss_name_t client_name = GSS_C_NO_NAME;
189 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
190 gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
191 OM_uint32 ret_flags = 0;
192 gss_OID_desc spnego_oid;
193 OM_uint32 (*accept_sec_context)
194 (OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t,
195 const gss_buffer_t, const gss_channel_bindings_t,
196 gss_name_t *, gss_OID *, gss_buffer_t, OM_uint32 *,
197 OM_uint32 *, gss_cred_id_t *);
199 *negotiate_ret_value = "\0";
201 spnego_oid.length = 6;
202 spnego_oid.elements = (void *)"\x2b\x06\x01\x05\x05\x02";
204 if (conf->krb5_keytab) {
206 /* we don't use the ap_* calls here, since the string passed to putenv()
207 * will become part of the enviroment and shouldn't be free()ed by apache
209 ktname = malloc(strlen("KRB5_KTNAME=") + strlen(conf->krb5_keytab) + 1);
210 if (ktname == NULL) {
211 gss_log(APLOG_MARK, APLOG_ERR, 0, r, "malloc() failed: not enough memory");
212 ret = HTTP_INTERNAL_SERVER_ERROR;
215 sprintf(ktname, "KRB5_KTNAME=%s", conf->krb5_keytab);
218 /* Seems to be also supported by latest MIT */
219 gsskrb5_register_acceptor_identity(conf->krb_5_keytab);
223 ret = get_gss_creds(r, conf, &server_creds);
227 /* ap_getword() shifts parameter */
228 auth_param = ap_getword_white(r->pool, &auth_line);
229 if (auth_param == NULL) {
230 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
231 "No Authorization parameter in request from client");
232 ret = HTTP_UNAUTHORIZED;
236 if (ctx->state == GSS_CTX_ESTABLISHED) {
237 gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER);
238 ctx->context = GSS_C_NO_CONTEXT;
239 ctx->state = GSS_CTX_EMPTY;
242 input_token.length = apr_base64_decode_len(auth_param) + 1;
243 input_token.value = apr_pcalloc(r->connection->pool, input_token.length);
244 if (input_token.value == NULL) {
245 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
246 "ap_pcalloc() failed (not enough memory)");
247 ret = HTTP_INTERNAL_SERVER_ERROR;
250 input_token.length = apr_base64_decode(input_token.value, auth_param);
252 /* LOG length, type */
254 #ifdef GSSAPI_SUPPORTS_SPNEGO
255 accept_sec_context = gss_accept_sec_context;
257 accept_sec_context = (cmp_gss_type(&input_token, &spnego_oid) == 0) ?
258 gss_accept_sec_context_spnego : gss_accept_sec_context;
261 major_status = accept_sec_context(&minor_status,
265 GSS_C_NO_CHANNEL_BINDINGS,
272 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
273 "Client %s us their credential",
274 (ret_flags & GSS_C_DELEG_FLAG) ? "delegated" : "didn't delegate");
275 if (output_token.length) {
279 len = apr_base64_encode_len(output_token.length) + 1;
280 token = apr_pcalloc(r->connection->pool, len + 1);
282 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
283 "ap_pcalloc() failed (not enough memory)");
284 ret = HTTP_INTERNAL_SERVER_ERROR;
285 gss_release_buffer(&minor_status2, &output_token);
288 apr_base64_encode(token, output_token.value, output_token.length);
290 *negotiate_ret_value = token;
291 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
292 "GSS-API token of length %d bytes will be sent back",
293 output_token.length);
294 gss_release_buffer(&minor_status2, &output_token);
297 if (GSS_ERROR(major_status)) {
298 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
299 "%s", get_gss_error(r, major_status, minor_status,
300 "Failed to establish authentication"));
302 /* Don't offer the Negotiate method again if call to GSS layer failed */
303 /* XXX ... which means we don't return the "error" output */
304 *negotiate_ret_value = NULL;
306 gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER);
307 ctx->context = GSS_C_NO_CONTEXT;
308 ctx->state = GSS_CTX_EMPTY;
309 ret = HTTP_UNAUTHORIZED;
313 if (major_status & GSS_S_CONTINUE_NEEDED) {
314 ctx->state = GSS_CTX_IN_PROGRESS;
315 ret = HTTP_UNAUTHORIZED;
319 major_status = gss_inquire_context(&minor_status, ctx->context, &client_name,
320 NULL, NULL, NULL, NULL, NULL, NULL);
321 if (GSS_ERROR(major_status)) {
322 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
323 "%s", get_gss_error(r, major_status, minor_status, "gss_inquire_context() failed"));
324 ret = HTTP_INTERNAL_SERVER_ERROR;
328 major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
329 gss_release_name(&minor_status, &client_name);
330 if (GSS_ERROR(major_status)) {
331 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
332 "%s", get_gss_error(r, major_status, minor_status,
333 "gss_display_name() failed"));
334 ret = HTTP_INTERNAL_SERVER_ERROR;
338 ctx->state = GSS_CTX_ESTABLISHED;
339 ctx->user = apr_pstrdup(r->pool, output_token.value);
340 gss_release_buffer(&minor_status, &output_token);
346 gss_release_cred(&minor_status, &delegated_cred);
348 if (output_token.length)
349 gss_release_buffer(&minor_status, &output_token);
351 if (client_name != GSS_C_NO_NAME)
352 gss_release_name(&minor_status, &client_name);
354 if (server_creds != GSS_C_NO_CREDENTIAL)
355 gss_release_cred(&minor_status, &server_creds);