From 8747a94e68294920263956f239cfd281e43aadad Mon Sep 17 00:00:00 2001 From: Margaret Wasserman Date: Wed, 13 Aug 2014 16:43:36 -0400 Subject: [PATCH] Apache auth hook appears to work, but filter still not registered properly. Added test client. --- gss.h | 3 +- mod_auth_gssweb.c | 166 ++++++++++++++++++++++++++++++------------------- mod_auth_gssweb.h | 57 +---------------- protocol.txt | 2 +- test/gssweb_client.pl | 70 +++++++++++++++++++++ test/gssweb_client.pl~ | 67 ++++++++++++++++++++ 6 files changed, 243 insertions(+), 122 deletions(-) create mode 100755 test/gssweb_client.pl create mode 100755 test/gssweb_client.pl~ diff --git a/gss.h b/gss.h index cc055f8..cb6a74a 100644 --- a/gss.h +++ b/gss.h @@ -58,9 +58,10 @@ typedef struct gss_conn_ctx_t { GSS_CTX_IN_PROGRESS, GSS_CTX_FAILED, GSS_CTX_ESTABLISHED, + GSS_CTX_ERROR, } state; char *user; - gss_buffer_desc *output_token; + gss_buffer_desc output_token; unsigned int nonce; } *gss_conn_ctx; diff --git a/mod_auth_gssweb.c b/mod_auth_gssweb.c index f59fe72..7521a28 100644 --- a/mod_auth_gssweb.c +++ b/mod_auth_gssweb.c @@ -1,39 +1,42 @@ /* - * Copyright (c) 2010 CESNET + * Copyright (c) 2012, 2013, 2014 JANET(UK) * All rights reserved. * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * modification, are permitted provided that the following conditions + * are met: * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * 3. Neither the name of CESNET nor the names of its contributors may - * be used to endorse or promote products derived from this software + * 3. Neither the name of JANET(UK) nor the names of its contributors + * may be used to endorse or promote products derived from this software * without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * NOTE: Some code in this module was derived from code in + * mod_auth_gssapi.c which is copyrighted by CESNET. See that file + * for full copyright details. + * * NOTE: Portions of the code in this file were derived from example * code distributed under the Apache 2.0 license: * http://www.apache.org/licenses/LICENSE-2.0 - * The example code was modified for inclusion in this module. * * This module implements the Apache server side of the GSSWeb * authentiction type which allows Moonshot to be used for @@ -68,36 +71,37 @@ static const command_rec gssweb_config_cmds[] = { #define DEFAULT_ENCTYPE "application/x-www-form-urlencoded" #define GSS_MAX_TOKEN_SIZE 4096 //TBD -- check this value -/* gssweb_read_post() -- Reads the post data associated with a - * request. +/* gssweb_read_req() -- reads the request data into a buffer */ -static int gssweb_read_post(request_rec *r, const char **rbuf) +static int gssweb_read_req(request_rec *r, const char **rbuf, apr_off_t *size) { - int rc; - if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) { - return rc; + int rc = OK; + + if((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) { + return(rc); } - if (ap_should_client_block(r)) { - char argsbuffer[GSS_MAX_TOKEN_SIZE+256]; - int rsize, len_read, rpos = 0; - long length = r->remaining; - *rbuf = ap_pcalloc(r->pool, length + 1); - ap_hard_timeout("util_read", r); - while ((len_read = - ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) { - ap_reset_timeout(r); - if ((rpos + len_read) > length) { + + if(ap_should_client_block(r)) { + + char argsbuffer[HUGE_STRING_LEN]; + apr_off_t rsize, len_read, rpos = 0; + apr_off_t length = r->remaining; + + *rbuf = (const char *) apr_pcalloc(r->pool, (apr_size_t) (length + 1)); + *size = length; + while((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) { + if((rpos + len_read) > length) { rsize = length - rpos; } else { rsize = len_read; } - memcpy((char*)*rbuf + rpos, argsbuffer, rsize); + + memcpy((char *) *rbuf + rpos, argsbuffer, (size_t) rsize); rpos += rsize; } - ap_kill_timeout(r); } - return rc; + return(rc); } /* gssweb_get_post_data() -- Gets the token and nonce from the request @@ -106,34 +110,57 @@ static int gssweb_read_post(request_rec *r, const char **rbuf) static int gssweb_get_post_data(request_rec *r, int *nonce, gss_buffer_desc *input_token) { const char *data; + apr_off_t datalen; const char *key, *val, *type; int rc = 0; + *nonce = 0; + input_token->length = 0; + input_token->value = NULL; + if(r->method_number != M_POST) { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: Request data is not a POST, declining."); return DECLINED; } - type = ap_table_get(r->headers_in, "Content-Type"); + type = apr_table_get(r->headers_in, "Content-Type"); if(strcasecmp(type, DEFAULT_ENCTYPE) != 0) { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: Unexpected content type, declining."); return DECLINED; } - if((rc = util_read(r, &data)) != OK) { + if((rc = gssweb_read_req(r, &data, &datalen)) != OK) { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: Data read error, rc = %d", rc); return rc; } - if(*tab) { - ap_clear_table(*tab); - } - else { - *tab = ap_make_table(r->pool, 8); - } + while(*data && (val = ap_getword(r->pool, &data, '&'))) { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: obtained val from ap_getword() (%s)", val); key = ap_getword(r->pool, &val, '='); ap_unescape_url((char*)key); ap_unescape_url((char*)val); - ap_table_merge(*tab, key, val); + if (0 == strcasecmp(key, "token")) { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: found token (%s)", val); + input_token->value = malloc(strlen(val)); + input_token->length = apr_base64_decode(input_token->value, val); + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: Token successfully decoded."); + } + else if (0 == strcasecmp(key, "nonce")) { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: found nonce (%s)", val); + *nonce = atoi(val); + } + else { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: unknown key (%s)", key); + } + } + if ((0 == *nonce) || (0 == input_token->length)) { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: nonce (%d) or token len (%d) is 0, declining", *nonce, input_token->length); + return DECLINED; + } + else { + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: returning nonce (%d) and token (%s)", *nonce, input_token->value); + return OK; } - return OK; } /* gssweb_authenticate_filter() -- Output filter for gssweb authentication. @@ -145,6 +172,11 @@ static int gssweb_get_post_data(request_rec *r, int *nonce, gss_buffer_desc *inp static apr_status_t gssweb_authenticate_filter (ap_filter_t *f, apr_bucket_brigade *pbbIn) { + apr_status_t ret = 0; + + gss_log(APLOG_MARK, APLOG_DEBUG, 0, f->r, "Entering GSSWeb filter"); + +#if 0 request_rec *r = f->r; conn_rec *c = r->connection; apr_bucket *pbktIn; @@ -192,6 +224,8 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f, */ apr_brigade_cleanup(pbbIn); return ap_pass_brigade(f->next,pbbOut); +#endif + return ret; } /* gssweb_authenticate_user() -- Hook to perform actual user @@ -216,7 +250,6 @@ gssweb_authenticate_user(request_rec *r) OM_uint32 major_status, minor_status, minor_status2; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; - const char *posted_token = NULL; gss_name_t client_name = GSS_C_NO_NAME; gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL; gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL; @@ -259,9 +292,8 @@ gssweb_authenticate_user(request_rec *r) conn_ctx->state = GSS_CTX_EMPTY; conn_ctx->user = NULL; if (0 != conn_ctx->output_token.length) { - gss_release_buffer(&minor_status, &conn_ctx->output_token); + gss_release_buffer(&minor_status, &(conn_ctx->output_token)); } - conn_ctx->output_token = GSS_C_EMPTY_BUFFER; } /* If the output filter reported an internal server error, return it */ @@ -275,22 +307,22 @@ gssweb_authenticate_user(request_rec *r) /* If this is a new authentiction cycle, set-up the output filter. */ if (GSS_CTX_EMPTY == conn_ctx->state) { - ap_add_output_filter("gssweb_auth_filter",conn_ctx->ctx,r,r->connection); - ap_register_output_filter("gssweb_auth_filter",gssweb_authenticate_filter,AP_FTYPE_RESOURCE); - + ap_add_output_filter("gssweb_auth_filter",conn_ctx->context,r,r->connection); + ap_register_output_filter("gssweb_auth_filter",gssweb_authenticate_filter, NULL, AP_FTYPE_RESOURCE); } /* Acquire server credentials */ ret = get_gss_creds(r, conf, &server_creds); if (ret) goto end; + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate: Server credentials acquired."); /* Decode input token */ - input_token.length = apr_base64_decode(input_token.value, posted_token); - + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate: Output token decoded, calling gss_accept_sec_context()."); + /* Call gss_accept_sec_context */ major_status = gss_accept_sec_context(&minor_status, - &ctx->context, + &conn_ctx->context, server_creds, &input_token, GSS_C_NO_CHANNEL_BINDINGS, @@ -308,26 +340,30 @@ gssweb_authenticate_user(request_rec *r) gss_log(APLOG_MARK, APLOG_ERR, 0, r, "%s", get_gss_error(r, major_status, minor_status, "Failed to establish authentication")); - gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER); - ctx->context = GSS_C_NO_CONTEXT; - ctx->state = GSS_CTX_EMPTY; + gss_delete_sec_context(&minor_status, &conn_ctx->context, GSS_C_NO_BUFFER); + conn_ctx->context = GSS_C_NO_CONTEXT; + conn_ctx->state = GSS_CTX_EMPTY; ret = HTTP_UNAUTHORIZED; goto end; + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate: Decoding ouput token."); } + gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate: Got sec context, storing nonce and output token."); + /* Store the nonce & ouput token in the stored context */ conn_ctx->nonce = nonce; conn_ctx->output_token = output_token; /* If we aren't done yet, go around again */ if (major_status & GSS_S_CONTINUE_NEEDED) { - ctx->state = GSS_CTX_IN_PROGRESS; + conn_ctx->state = GSS_CTX_IN_PROGRESS; ret = HTTP_UNAUTHORIZED; goto end; } - ctx->state = GSS_CTX_ESTABLISHED; - // TBD -- set the user and authtype in the request structure + conn_ctx->state = GSS_CTX_ESTABLISHED; + r->user = apr_pstrdup(r->pool, conn_ctx->user); + r->ap_auth_type = "GSSWeb"; ret = OK; end: diff --git a/mod_auth_gssweb.h b/mod_auth_gssweb.h index 5183b0d..914de96 100644 --- a/mod_auth_gssweb.h +++ b/mod_auth_gssweb.h @@ -32,62 +32,9 @@ #ifndef __MOD_AUTH_GSSWEB_H__ #define __MOD_AUTH_GSSWEB_H__ -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -// #ifndef GSSAPI_SUPPORTS_SPNEGO -// #include "spnegokrb5.h" -// #endif - -#define SERVICE_NAME "HTTP" - -typedef struct { - const char *service_name; - const char *krb5_keytab; -} gss_auth_config; - -typedef struct gss_conn_ctx_t { - gss_ctx_id_t context; - enum { - GSS_CTX_EMPTY, - GSS_CTX_IN_PROGRESS, - GSS_CTX_ESTABLISHED, - } state; - char *user; -} *gss_conn_ctx; - -void -gss_log(const char *file, int line, int level, int status, - const request_rec *r, const char *fmt, ...); - -apr_status_t -cleanup_conn_ctx(void *data); - -gss_conn_ctx -gss_get_conn_ctx(request_rec *r); - -void * -gss_config_dir_create(apr_pool_t *p, char *d); - -static const char * -get_gss_error(request_rec *r, OM_uint32 err_maj, OM_uint32 err_min, char *prefix); - -static int -get_gss_creds(request_rec *r, gss_auth_config *conf, gss_cred_id_t *server_creds); +#include static int -cmp_gss_type(gss_buffer_t token, gss_OID oid); +gssweb_authenticate_user(request_rec *r); -int -gssweb_authenticate(request_rec *r, gss_auth_config *conf, gss_conn_ctx ctx, - const char *auth_line, char **negotiate_ret_value); #endif diff --git a/protocol.txt b/protocol.txt index 13cde20..17233e9 100644 --- a/protocol.txt +++ b/protocol.txt @@ -15,7 +15,7 @@ The server will respond by sending a JSON reponse: application: { data: "", content-type: "", - Content-Length: ""} + content-length: ""} } The "gssweb" section in the response is used for the GSS exchange. diff --git a/test/gssweb_client.pl b/test/gssweb_client.pl new file mode 100755 index 0000000..fc08ae7 --- /dev/null +++ b/test/gssweb_client.pl @@ -0,0 +1,70 @@ +#!/usr/bin/perl -w + +use strict; +use WWW::Mechanize; +use GSSAPI; +use GSSAPI::OID; +use MIME::Base64; +use JSON; +use URI::Encode qw(uri_encode); + +sub parse_token($) { + my ($json) = @_; + my $ref = decode_json($json); + return $ref->{'gssweb'}{'token'}; + } + + +sub token_body($$) { + my ($target_server, $itoken) = @_; + my $status; + my $otoken; + my $target; + try: { + $status = GSSAPI::Name->import( $target, + $target_server, + GSSAPI::OID::gss_nt_hostbased_service) or last; + our $ctx = GSSAPI::Context->new() unless $ctx; + my $mech; + $status = GSSAPI::OID->from_str($mech, '{ 1.3.6.1.5.5.15.1.1.17 }') or last; + my $iflags = GSSAPI::GSS_C_MUTUAL_FLAG | GSSAPI::GSS_C_SEQUENCE_FLAG | GSSAPI::GSS_C_REPLAY_FLAG; + my $bindings = GSS_C_NO_CHANNEL_BINDINGS; + my $creds = GSS_C_NO_CREDENTIAL; + my $itime = 0; + + $status = $ctx->init($creds,$target, + $mech,$iflags,$itime,$bindings,$itoken, + undef, $otoken,undef,undef); + } + print "$status\n"; + return undef unless $otoken; + print "Pre-encoding token: $otoken\n"; + my $encoded_token = encode_base64($otoken); + chomp($encoded_token); + my $out = "token=" . uri_encode($encoded_token, {encode_reserved => 1}) ."&nonce=42"; + print "$out\n"; + return $out; +} + +my ($url, $gssname) = @ARGV; +my $www = WWW::Mechanize->new('autocheck' => 0); +my $done = 0; +my $response_token = undef; +unless ($done) { + + $www->post($url, 'Content' => token_body($gssname, $response_token)); + my $status = $www->status(); + if ($status == 200) { + $done = 1; + print "authenticated: response is ".$www->content()."\n"; + if (token_body($gssname, parse_token($www->content()))) { + print "Expecting gss success but did not get it!\n"; + } + } elsif ($status == 401) { + print "Continuing\n"; + $response_token = parse_token($www->content()); + } else { + print "Unexpected response status: $status\n"; + print $www->content(); + } +} diff --git a/test/gssweb_client.pl~ b/test/gssweb_client.pl~ new file mode 100755 index 0000000..8d01782 --- /dev/null +++ b/test/gssweb_client.pl~ @@ -0,0 +1,67 @@ +#!/usr/bin/perl -w + +use strict; +use WWW::Mechanize; +use GSSAPI; +use GSSAPI::OID; +use MIME::Base64; +use JSON; + +sub parse_token($) { + my ($json) = @_; + my $ref = decode_json($json); + return $ref->{'gssweb'}{'token'}; + } + + +sub token_body($$) { + my ($target_server, $itoken) = @_; + my $status; + my $otoken; + my $target; + try: { + $status = GSSAPI::Name->import( $target, + $target_server, + GSSAPI::OID::gss_nt_hostbased_service) or last; + our $ctx = GSSAPI::Context->new() unless $ctx; + my $mech; + $status = GSSAPI::OID->from_str($mech, '{ 1.3.6.1.5.5.15.1.1.17 }') or last; + my $iflags = GSSAPI::GSS_C_MUTUAL_FLAG | GSSAPI::GSS_C_SEQUENCE_FLAG | GSSAPI::GSS_C_REPLAY_FLAG; + my $bindings = GSS_C_NO_CHANNEL_BINDINGS; + my $creds = GSS_C_NO_CREDENTIAL; + my $itime = 0; + + $status = $ctx->init($creds,$target, + $mech,$iflags,$itime,$bindings,$itoken, + undef, $otoken,undef,undef); + } + print "$status\n"; + return undef unless $otoken; + + my $out = "token:" . encode_base64($otoken) ."nonce: 42\n"; + print $out; + return $out; +} + +my ($url, $gssname) = @ARGV; +my $www = WWW::Mechanize->new('autocheck' => 0); +my $done = 0; +my $response_token = undef; +unless ($done) { + + $www->post($url, 'Content' => token_body($gssname, $response_token)); + my $status = $www->status(); + if ($status == 200) { + $done = 1; + print "authenticated: response is ".$www->content()."\n"; + if (token_body($gssname, parse_token($www->content()))) { + print "Expecting gss success but did not get it!\n"; + } + } elsif ($status == 401) { + print "Contiuning\n"; + $response_token = parse_token($www->content()); + } else { + print "Unexpected response status: $status\n"; + print $www->content(); + } +} -- 2.1.4