1
/* Copyright 2009 Justin Erenkrantz and Greg Stein
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
7
* http://www.apache.org/licenses/LICENSE-2.0
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.
16
#include "auth_kerb.h"
20
/*** Kerberos authentication ***/
23
#include <serf_private.h>
24
#include <auth/auth.h>
27
#include <apr_base64.h>
28
#include <apr_strings.h>
30
/** These functions implements Kerberos authentication, using GSS-API
31
* (RFC 2743). The message-exchange is documented in RFC 4559.
33
* Note: this implementation uses gssapi and only works on *nix.
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
41
** - Add a way for serf to give detailed error information back to the
46
/* Authentication over HTTP using Kerberos
48
* Kerberos involves three servers:
49
* - Authentication Server (AS): verifies users during login
50
* - Ticket-Granting Server (TGS): issues proof of identity tickets
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
60
* C <-- S: 401 Authentication Required
61
* WWW-Authenticate: Negotiate
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.
68
* Authorization: Negotiate <Base64 encoded session key>
69
* gss_api_ctx->state = gss_api_auth_in_progress;
72
* WWW-Authenticate: Negotiate <Base64 encoded server
73
* authentication data>
75
* -> The server returned a key to proof itself to us. We check this key
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.
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.
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!
94
gss_api_auth_not_started,
95
gss_api_auth_in_progress,
96
gss_api_auth_completed,
99
/* HTTP Service name, used to get the session key. */
100
#define KRB_HTTP_SERVICE "HTTP"
102
/* Stores the context information related to Kerberos authentication. */
108
serf__kerb_context_t *gss_ctx;
110
/* Current state of the authentication cycle. */
111
gss_api_auth_state state;
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. */
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)
127
serf__kerb_buffer_t input_buf;
128
serf__kerb_buffer_t output_buf;
129
apr_status_t status = APR_SUCCESS;
131
/* If the server sent us a token, pass it to gss_init_sec_token for
134
input_buf.value = token;
135
input_buf.length = token_len;
138
input_buf.length = 0;
141
/* Establish a security context to the server. */
142
status = serf__kerb_init_sec_context
144
KRB_HTTP_SERVICE, hostname,
153
gss_info->state = gss_api_auth_completed;
156
gss_info->state = gss_api_auth_in_progress;
157
status = APR_SUCCESS;
160
status = SERF_ERROR_AUTHN_FAILED;
164
/* Return the session key to our caller. */
165
*buf = output_buf.value;
166
*buf_len = output_buf.length;
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
176
gss_authn_info_t *gss_info,
177
serf_connection_t *conn,
178
const char *auth_hdr,
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;
186
apr_size_t tmp_len = 0, token_len = 0;
187
const char *space = NULL;
190
/* The server will return a token as attribute to the Negotiate key.
191
Negotiate YGwGCSqGSIb3EgECAgIAb10wW6ADAgEFoQMCAQ+iTzBNoAMCARCiRgREa6mouM
192
BAMFqKVdTGtfpZNXKzyw4Yo1paphJdIA3VOgncaoIlXxZLnkHiIHS2v65pVvrp
193
bRIyjF8xve9HxpnNIucCY9c=
195
Read this base64 value, decode it and validate it so we're sure the server
196
is who we expect it to be. */
198
space = strchr(auth_hdr, ' ');
201
token = apr_palloc(pool, apr_base64_decode_len(space + 1));
202
token_len = apr_base64_decode(token, space + 1);
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.
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)
214
status = gss_api_get_credentials(token, token_len, conn->host_info.hostname,
220
serf__encode_auth_header(&gss_info->value, authn_info->scheme->name,
224
gss_info->header = (code == 401) ? "Authorization" : "Proxy-Authorization";
226
/* If the handshake is finished tell serf it can send as much requests as it
228
if (gss_info->state == gss_api_auth_completed)
229
serf_connection_set_max_outstanding_requests(conn, 0);
235
serf__init_kerb(int code,
242
/* A new connection is created to a server that's known to use
245
serf__init_kerb_connection(int code,
246
serf_connection_t *conn,
249
gss_authn_info_t *gss_info;
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,
263
conn->authn_baton = gss_info;
265
conn->proxy_authn_baton = gss_info;
268
/* Make serf send the initial requests one by one */
269
serf_connection_set_max_outstanding_requests(conn, 1);
274
/* A 401 response was received, handle the authentication. */
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,
284
serf_connection_t *conn = request->conn;
285
gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
286
conn->proxy_authn_baton;
295
/* Setup the authn headers on this request message. */
297
serf__setup_request_kerb_auth(int code,
298
serf_connection_t *conn,
301
serf_bucket_t *hdrs_bkt)
303
gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
304
conn->proxy_authn_baton;
306
if (gss_info && gss_info->header && gss_info->value) {
307
serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
310
/* We should send each token only once. */
311
gss_info->header = NULL;
312
gss_info->value = NULL;
316
return SERF_ERROR_AUTHN_FAILED;
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).
325
serf__validate_response_kerb_auth(int code,
326
serf_connection_t *conn,
327
serf_request_t *request,
328
serf_bucket_t *response,
331
gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
332
conn->proxy_authn_baton;
334
const char *auth_hdr;
336
hdrs = serf_bucket_response_get_headers(response);
337
auth_hdr = serf_bucket_headers_get(hdrs, "WWW-Authenticate");
339
if (gss_info->state != gss_api_auth_completed) {
350
#endif /* SERF_HAVE_GSSAPI */