auth_spnego_gss.c revision 262339
1/* Copyright 2009 Justin Erenkrantz and Greg Stein
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "serf.h"
17#include "serf_private.h"
18#include "auth_spnego.h"
19
20#ifdef SERF_USE_GSSAPI
21#include <apr_strings.h>
22#include <gssapi/gssapi.h>
23
24
25/* This module can support all authentication mechanisms as provided by
26   the GSS-API implementation, but for now it only supports SPNEGO for
27   Negotiate.
28   SPNEGO can delegate authentication to Kerberos if supported by the
29   host. */
30
31#ifndef GSS_SPNEGO_MECHANISM
32static gss_OID_desc spnego_mech_oid = { 6, "\x2b\x06\x01\x05\x05\x02" };
33#define GSS_SPNEGO_MECHANISM &spnego_mech_oid
34#endif
35
36struct serf__spnego_context_t
37{
38    /* GSSAPI context */
39    gss_ctx_id_t gss_ctx;
40
41    /* Mechanism used to authenticate. */
42    gss_OID gss_mech;
43};
44
45static void
46log_error(int verbose_flag, apr_socket_t *skt,
47          serf__spnego_context_t *ctx,
48          OM_uint32 err_maj_stat,
49          OM_uint32 err_min_stat,
50          const char *msg)
51{
52    OM_uint32 maj_stat, min_stat;
53    gss_buffer_desc stat_buff;
54    OM_uint32 msg_ctx = 0;
55
56    if (verbose_flag) {
57        maj_stat = gss_display_status(&min_stat,
58                                      err_maj_stat,
59                                      GSS_C_GSS_CODE,
60                                      ctx->gss_mech,
61                                      &msg_ctx,
62                                      &stat_buff);
63        if (maj_stat == GSS_S_COMPLETE ||
64            maj_stat == GSS_S_FAILURE) {
65            maj_stat = gss_display_status(&min_stat,
66                                          err_min_stat,
67                                          GSS_C_MECH_CODE,
68                                          ctx->gss_mech,
69                                          &msg_ctx,
70                                          &stat_buff);
71        }
72
73        serf__log_skt(verbose_flag, __FILE__, skt,
74                  "%s (%x,%d): %s\n", msg,
75                  err_maj_stat, err_min_stat, stat_buff.value);
76    }
77}
78
79/* Cleans the GSS context object, when the pool used to create it gets
80   cleared or destroyed. */
81static apr_status_t
82cleanup_ctx(void *data)
83{
84    serf__spnego_context_t *ctx = data;
85
86    if (ctx->gss_ctx != GSS_C_NO_CONTEXT) {
87        OM_uint32 gss_min_stat, gss_maj_stat;
88
89        gss_maj_stat = gss_delete_sec_context(&gss_min_stat, &ctx->gss_ctx,
90                                              GSS_C_NO_BUFFER);
91        if(GSS_ERROR(gss_maj_stat)) {
92            log_error(AUTH_VERBOSE, NULL, ctx,
93                      gss_maj_stat, gss_min_stat,
94                      "Error cleaning up GSS security context");
95            return SERF_ERROR_AUTHN_FAILED;
96        }
97    }
98
99    return APR_SUCCESS;
100}
101
102static apr_status_t
103cleanup_sec_buffer(void *data)
104{
105    OM_uint32 min_stat;
106    gss_buffer_desc *gss_buf = data;
107
108    gss_release_buffer(&min_stat, gss_buf);
109
110    return APR_SUCCESS;
111}
112
113apr_status_t
114serf__spnego_create_sec_context(serf__spnego_context_t **ctx_p,
115                                const serf__authn_scheme_t *scheme,
116                                apr_pool_t *result_pool,
117                                apr_pool_t *scratch_pool)
118{
119    serf__spnego_context_t *ctx;
120
121    ctx = apr_pcalloc(result_pool, sizeof(*ctx));
122
123    ctx->gss_ctx = GSS_C_NO_CONTEXT;
124    ctx->gss_mech = GSS_SPNEGO_MECHANISM;
125
126    apr_pool_cleanup_register(result_pool, ctx,
127                              cleanup_ctx,
128                              apr_pool_cleanup_null);
129
130    *ctx_p = ctx;
131
132    return APR_SUCCESS;
133}
134
135apr_status_t
136serf__spnego_reset_sec_context(serf__spnego_context_t *ctx)
137{
138    OM_uint32 dummy_stat;
139
140    if (ctx->gss_ctx)
141        (void)gss_delete_sec_context(&dummy_stat, &ctx->gss_ctx,
142                                     GSS_C_NO_BUFFER);
143    ctx->gss_ctx = GSS_C_NO_CONTEXT;
144
145    return APR_SUCCESS;
146}
147
148apr_status_t
149serf__spnego_init_sec_context(serf_connection_t *conn,
150                              serf__spnego_context_t *ctx,
151                              const char *service,
152                              const char *hostname,
153                              serf__spnego_buffer_t *input_buf,
154                              serf__spnego_buffer_t *output_buf,
155                              apr_pool_t *result_pool,
156                              apr_pool_t *scratch_pool
157                              )
158{
159    gss_buffer_desc gss_input_buf = GSS_C_EMPTY_BUFFER;
160    gss_buffer_desc *gss_output_buf_p;
161    OM_uint32 gss_min_stat, gss_maj_stat;
162    gss_name_t host_gss_name;
163    gss_buffer_desc bufdesc;
164    gss_OID dummy; /* unused */
165
166    /* Get the name for the HTTP service at the target host. */
167    /* TODO: should be shared between multiple requests. */
168    bufdesc.value = apr_pstrcat(scratch_pool, service, "@", hostname, NULL);
169    bufdesc.length = strlen(bufdesc.value);
170    serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
171                  "Get principal for %s\n", bufdesc.value);
172    gss_maj_stat = gss_import_name (&gss_min_stat, &bufdesc,
173                                    GSS_C_NT_HOSTBASED_SERVICE,
174                                    &host_gss_name);
175    if(GSS_ERROR(gss_maj_stat)) {
176        log_error(AUTH_VERBOSE, conn->skt, ctx,
177                  gss_maj_stat, gss_min_stat,
178                  "Error converting principal name to GSS internal format ");
179        return SERF_ERROR_AUTHN_FAILED;
180    }
181
182    /* If the server sent us a token, pass it to gss_init_sec_token for
183       validation. */
184    gss_input_buf.value = input_buf->value;
185    gss_input_buf.length = input_buf->length;
186
187    gss_output_buf_p = apr_pcalloc(result_pool, sizeof(*gss_output_buf_p));
188
189    /* Establish a security context to the server. */
190    gss_maj_stat = gss_init_sec_context
191        (&gss_min_stat,             /* minor_status */
192         GSS_C_NO_CREDENTIAL,       /* XXXXX claimant_cred_handle */
193         &ctx->gss_ctx,              /* gssapi context handle */
194         host_gss_name,             /* HTTP@server name */
195         ctx->gss_mech,             /* mech_type (SPNEGO) */
196         GSS_C_MUTUAL_FLAG,         /* ensure the peer authenticates itself */
197         0,                         /* default validity period */
198         GSS_C_NO_CHANNEL_BINDINGS, /* do not use channel bindings */
199         &gss_input_buf,            /* server token, initially empty */
200         &dummy,                    /* actual mech type */
201         gss_output_buf_p,           /* output_token */
202         NULL,                      /* ret_flags */
203         NULL                       /* not interested in remaining validity */
204         );
205
206    apr_pool_cleanup_register(result_pool, gss_output_buf_p,
207                              cleanup_sec_buffer,
208                              apr_pool_cleanup_null);
209
210    output_buf->value = gss_output_buf_p->value;
211    output_buf->length = gss_output_buf_p->length;
212
213    switch(gss_maj_stat) {
214    case GSS_S_COMPLETE:
215        return APR_SUCCESS;
216    case GSS_S_CONTINUE_NEEDED:
217        return APR_EAGAIN;
218    default:
219        log_error(AUTH_VERBOSE, conn->skt, ctx,
220                  gss_maj_stat, gss_min_stat,
221                  "Error during Kerberos handshake");
222        return SERF_ERROR_AUTHN_FAILED;
223    }
224}
225
226#endif /* SERF_USE_GSSAPI */
227