~ubuntu-branches/ubuntu/trusty/serf/trusty-security

« back to all changes in this revision

Viewing changes to auth/auth_kerb.c

  • Committer: Bazaar Package Importer
  • Author(s): Peter Samuelson
  • Date: 2011-06-03 03:18:07 UTC
  • mfrom: (1.2.4 upstream)
  • mto: (3.3.1 sid)
  • mto: This revision was merged to the branch mainline in revision 11.
  • Revision ID: james.westby@ubuntu.com-20110603031807-3vq82t5lzw692our
Tags: 0.7.2-1
* New upstream release.
  - patches/no-export-vars: delete, now upstream.
* New ABI:
  - patches/abi-0.x: New patch to change upstream SONAME.
  - Rename libserf-0-0 to libserf0.7.
  - Rename libserf-0-0-dev to libserf-dev while we're at it.
* Policy 3.9.1: one instance of s/Conflicts/Breaks/.
* "Upgrade" to source format 1.0.
* Add some Depends: ${misc:Depends}; thanks, Lintian.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 "auth_kerb.h"
 
17
 
 
18
#ifdef SERF_HAVE_KERB
 
19
 
 
20
/*** Kerberos authentication ***/
 
21
 
 
22
#include <serf.h>
 
23
#include <serf_private.h>
 
24
#include <auth/auth.h>
 
25
 
 
26
#include <apr.h>
 
27
#include <apr_base64.h>
 
28
#include <apr_strings.h>
 
29
 
 
30
/** These functions implements Kerberos authentication, using GSS-API
 
31
 *  (RFC 2743). The message-exchange is documented in RFC 4559.
 
32
 *
 
33
 * Note: this implementation uses gssapi and only works on *nix.
 
34
 **/
 
35
 
 
36
/** TODO:
 
37
 ** - send session key directly on new connections where we already know
 
38
 **   the server requires Kerberos authn.
 
39
 ** - fix authn status, as the COMPLETE/CONTINUE status values
 
40
 **   are never used.
 
41
 ** - Add a way for serf to give detailed error information back to the
 
42
 **   application.
 
43
 ** - proxy support
 
44
 **/
 
45
 
 
46
/* Authentication over HTTP using Kerberos
 
47
 *
 
48
 * Kerberos involves three servers:
 
49
 * - Authentication Server (AS): verifies users during login
 
50
 * - Ticket-Granting Server (TGS): issues proof of identity tickets
 
51
 * - HTTP server (S)
 
52
 *
 
53
 * Steps:
 
54
 * 0. User logs in to the AS and receives a TGS ticket. On workstations
 
55
 * where the login program doesn't support Kerberos, the user can use
 
56
 * 'kinit'.
 
57
 *
 
58
 * 1. C  --> S:    GET
 
59
 *
 
60
 *    C <--  S:    401 Authentication Required
 
61
 *                 WWW-Authenticate: Negotiate
 
62
 *
 
63
 * -> app contacts the TGS to request a session key for the HTTP service
 
64
 *    @ target host. The returned session key is encrypted with the HTTP
 
65
 *    service's secret key, so we can safely send it to the server.
 
66
 *
 
67
 * 2. C  --> S:    GET
 
68
 *                 Authorization: Negotiate <Base64 encoded session key>
 
69
 *                 gss_api_ctx->state = gss_api_auth_in_progress;
 
70
 *
 
71
 *    C <--  S:    200 OK
 
72
 *                 WWW-Authenticate: Negotiate <Base64 encoded server
 
73
 *                                              authentication data>
 
74
 *
 
75
 * -> The server returned a key to proof itself to us. We check this key
 
76
 *    with the TGS again.
 
77
 *
 
78
 * Note: It's possible that the server returns 401 again in step 3, if the
 
79
 *       Kerberos context isn't complete yet. Some (simple) tests with
 
80
 *       mod_auth_kerb and MIT Kerberos 5 show this never happens.
 
81
 *
 
82
 * This handshake is required for every new connection. If the handshake is
 
83
 * completed successfully, all other requests on the same connection will
 
84
 * be authenticated without needing to pass the WWW-Authenticate header.
 
85
 *
 
86
 * Note: Step 1 of the handshake will only happen on the first connection, once
 
87
 * we know the server requires Kerberos authentication, the initial requests
 
88
 * on the other connections will include a session key, so we start  at
 
89
 * step 2 in the handshake.
 
90
 * ### TODO: Not implemented yet!
 
91
 */
 
92
 
 
93
typedef enum {
 
94
    gss_api_auth_not_started,
 
95
    gss_api_auth_in_progress,
 
96
    gss_api_auth_completed,
 
97
} gss_api_auth_state;
 
98
 
 
99
/* HTTP Service name, used to get the session key.  */
 
100
#define KRB_HTTP_SERVICE "HTTP"
 
101
 
 
102
/* Stores the context information related to Kerberos authentication. */
 
103
typedef struct
 
104
{
 
105
    apr_pool_t *pool;
 
106
 
 
107
    /* GSSAPI context */
 
108
    serf__kerb_context_t *gss_ctx;
 
109
 
 
110
    /* Current state of the authentication cycle. */
 
111
    gss_api_auth_state state;
 
112
 
 
113
    const char *header;
 
114
    const char *value;
 
115
} gss_authn_info_t;
 
116
 
 
117
/* On the initial 401 response of the server, request a session key from
 
118
   the Kerberos KDC to pass to the server, proving that we are who we
 
119
   claim to be. The session key can only be used with the HTTP service
 
120
   on the target host. */
 
121
static apr_status_t
 
122
gss_api_get_credentials(char *token, apr_size_t token_len,
 
123
                        const char *hostname,
 
124
                        const char **buf, apr_size_t *buf_len,
 
125
                        gss_authn_info_t *gss_info)
 
126
{
 
127
    serf__kerb_buffer_t input_buf;
 
128
    serf__kerb_buffer_t output_buf;
 
129
    apr_status_t status = APR_SUCCESS;
 
130
 
 
131
    /* If the server sent us a token, pass it to gss_init_sec_token for
 
132
       validation. */
 
133
    if (token) {
 
134
        input_buf.value = token;
 
135
        input_buf.length = token_len;
 
136
    } else {
 
137
        input_buf.value = 0;
 
138
        input_buf.length = 0;
 
139
    }
 
140
 
 
141
    /* Establish a security context to the server. */
 
142
    status = serf__kerb_init_sec_context
 
143
        (gss_info->gss_ctx,
 
144
         KRB_HTTP_SERVICE, hostname,
 
145
         &input_buf,
 
146
         &output_buf,
 
147
         gss_info->pool,
 
148
         gss_info->pool
 
149
        );
 
150
 
 
151
    switch(status) {
 
152
    case APR_SUCCESS:
 
153
        gss_info->state = gss_api_auth_completed;
 
154
        break;
 
155
    case APR_EAGAIN:
 
156
        gss_info->state = gss_api_auth_in_progress;
 
157
        status = APR_SUCCESS;
 
158
        break;
 
159
    default:
 
160
        status = SERF_ERROR_AUTHN_FAILED;
 
161
        break;
 
162
    }
 
163
 
 
164
    /* Return the session key to our caller. */
 
165
    *buf = output_buf.value;
 
166
    *buf_len = output_buf.length;
 
167
 
 
168
    return status;
 
169
}
 
170
 
 
171
/* Read the header sent by the server (if any), invoke the gssapi authn
 
172
   code and use the resulting Server Ticket  on the next request to the
 
173
   server. */
 
174
static apr_status_t
 
175
do_auth(int code,
 
176
        gss_authn_info_t *gss_info,
 
177
        serf_connection_t *conn,
 
178
        const char *auth_hdr,
 
179
        apr_pool_t *pool)
 
180
{
 
181
    serf_context_t *ctx = conn->ctx;
 
182
    serf__authn_info_t *authn_info = (code == 401) ? &ctx->authn_info :
 
183
        &ctx->proxy_authn_info;
 
184
    const char *tmp = NULL;
 
185
    char *token = NULL;
 
186
    apr_size_t tmp_len = 0, token_len = 0;
 
187
    const char *space = NULL;
 
188
    apr_status_t status;
 
189
 
 
190
    /* The server will return a token as attribute to the Negotiate key.
 
191
       Negotiate YGwGCSqGSIb3EgECAgIAb10wW6ADAgEFoQMCAQ+iTzBNoAMCARCiRgREa6mouM
 
192
       BAMFqKVdTGtfpZNXKzyw4Yo1paphJdIA3VOgncaoIlXxZLnkHiIHS2v65pVvrp
 
193
       bRIyjF8xve9HxpnNIucCY9c=
 
194
 
 
195
       Read this base64 value, decode it and validate it so we're sure the server
 
196
       is who we expect it to be. */
 
197
    if (auth_hdr)
 
198
        space = strchr(auth_hdr, ' ');
 
199
 
 
200
    if (space) {
 
201
        token = apr_palloc(pool, apr_base64_decode_len(space + 1));
 
202
        token_len = apr_base64_decode(token, space + 1);
 
203
    }
 
204
 
 
205
    /* We can get a whole batch of 401 responses from the server, but we should
 
206
       only start the authentication phase once, so if we started authentication
 
207
       already ignore all responses with initial Negotiate authentication header.
 
208
 
 
209
       Note: as we set the max. transfer rate to one message at a time until the
 
210
       authentication cycle is finished, this check shouldn't be needed. */
 
211
    if (!token && gss_info->state != gss_api_auth_not_started)
 
212
        return APR_SUCCESS;
 
213
 
 
214
    status = gss_api_get_credentials(token, token_len, conn->host_info.hostname,
 
215
                                     &tmp, &tmp_len,
 
216
                                     gss_info);
 
217
    if (status)
 
218
        return status;
 
219
 
 
220
    serf__encode_auth_header(&gss_info->value, authn_info->scheme->name,
 
221
                             tmp,
 
222
                             tmp_len,
 
223
                             pool);
 
224
    gss_info->header = (code == 401) ? "Authorization" : "Proxy-Authorization";
 
225
 
 
226
    /* If the handshake is finished tell serf it can send as much requests as it
 
227
       likes. */
 
228
    if (gss_info->state == gss_api_auth_completed)
 
229
        serf_connection_set_max_outstanding_requests(conn, 0);
 
230
 
 
231
    return APR_SUCCESS;
 
232
}
 
233
 
 
234
apr_status_t
 
235
serf__init_kerb(int code,
 
236
                serf_context_t *ctx,
 
237
                apr_pool_t *pool)
 
238
{
 
239
    return APR_SUCCESS;
 
240
}
 
241
 
 
242
/* A new connection is created to a server that's known to use
 
243
   Kerberos. */
 
244
apr_status_t
 
245
serf__init_kerb_connection(int code,
 
246
                           serf_connection_t *conn,
 
247
                           apr_pool_t *pool)
 
248
{
 
249
    gss_authn_info_t *gss_info;
 
250
    apr_status_t status;
 
251
 
 
252
    gss_info = apr_pcalloc(pool, sizeof(*gss_info));
 
253
    gss_info->pool = conn->pool;
 
254
    gss_info->state = gss_api_auth_not_started;
 
255
    status = serf__kerb_create_sec_context(&gss_info->gss_ctx, pool,
 
256
                                           gss_info->pool);
 
257
 
 
258
    if (status) {
 
259
        return status;
 
260
    }
 
261
 
 
262
    if (code == 401) {
 
263
        conn->authn_baton = gss_info;
 
264
    } else {
 
265
        conn->proxy_authn_baton = gss_info;
 
266
    }
 
267
 
 
268
    /* Make serf send the initial requests one by one */
 
269
    serf_connection_set_max_outstanding_requests(conn, 1);
 
270
 
 
271
    return APR_SUCCESS;
 
272
}
 
273
 
 
274
/* A 401 response was received, handle the authentication. */
 
275
apr_status_t
 
276
serf__handle_kerb_auth(int code,
 
277
                       serf_request_t *request,
 
278
                       serf_bucket_t *response,
 
279
                       const char *auth_hdr,
 
280
                       const char *auth_attr,
 
281
                       void *baton,
 
282
                       apr_pool_t *pool)
 
283
{
 
284
    serf_connection_t *conn = request->conn;
 
285
    gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
 
286
        conn->proxy_authn_baton;
 
287
 
 
288
    return do_auth(code,
 
289
                   gss_info,
 
290
                   request->conn,
 
291
                   auth_hdr,
 
292
                   pool);
 
293
}
 
294
 
 
295
/* Setup the authn headers on this request message. */
 
296
apr_status_t
 
297
serf__setup_request_kerb_auth(int code,
 
298
                              serf_connection_t *conn,
 
299
                              const char *method,
 
300
                              const char *uri,
 
301
                              serf_bucket_t *hdrs_bkt)
 
302
{
 
303
    gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
 
304
        conn->proxy_authn_baton;
 
305
 
 
306
    if (gss_info && gss_info->header && gss_info->value) {
 
307
        serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
 
308
                                 gss_info->value);
 
309
 
 
310
        /* We should send each token only once. */
 
311
        gss_info->header = NULL;
 
312
        gss_info->value = NULL;
 
313
        return APR_SUCCESS;
 
314
    }
 
315
 
 
316
    return SERF_ERROR_AUTHN_FAILED;
 
317
}
 
318
 
 
319
/* Function is called when 2xx responses are received. Normally we don't
 
320
 * have to do anything, except for the first response after the
 
321
 * authentication handshake. This specific response includes authentication
 
322
 * data which should be validated by the client (mutual authentication).
 
323
 */
 
324
apr_status_t
 
325
serf__validate_response_kerb_auth(int code,
 
326
                                    serf_connection_t *conn,
 
327
                                    serf_request_t *request,
 
328
                                    serf_bucket_t *response,
 
329
                                    apr_pool_t *pool)
 
330
{
 
331
    gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
 
332
        conn->proxy_authn_baton;
 
333
    serf_bucket_t *hdrs;
 
334
    const char *auth_hdr;
 
335
 
 
336
    hdrs = serf_bucket_response_get_headers(response);
 
337
    auth_hdr = serf_bucket_headers_get(hdrs, "WWW-Authenticate");
 
338
 
 
339
    if (gss_info->state != gss_api_auth_completed) {
 
340
        return do_auth(code,
 
341
                       gss_info,
 
342
                       conn,
 
343
                       auth_hdr,
 
344
                       pool);
 
345
    }
 
346
 
 
347
    return APR_SUCCESS;
 
348
}
 
349
 
 
350
#endif /* SERF_HAVE_GSSAPI */