summaryrefslogtreecommitdiff
path: root/auth/auth_kerb.c
diff options
context:
space:
mode:
Diffstat (limited to 'auth/auth_kerb.c')
-rw-r--r--auth/auth_kerb.c350
1 files changed, 350 insertions, 0 deletions
diff --git a/auth/auth_kerb.c b/auth/auth_kerb.c
new file mode 100644
index 0000000..f9be9a4
--- /dev/null
+++ b/auth/auth_kerb.c
@@ -0,0 +1,350 @@
+/* Copyright 2009 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "auth_kerb.h"
+
+#ifdef SERF_HAVE_KERB
+
+/*** Kerberos authentication ***/
+
+#include <serf.h>
+#include <serf_private.h>
+#include <auth/auth.h>
+
+#include <apr.h>
+#include <apr_base64.h>
+#include <apr_strings.h>
+
+/** These functions implements Kerberos authentication, using GSS-API
+ * (RFC 2743). The message-exchange is documented in RFC 4559.
+ *
+ * Note: this implementation uses gssapi and only works on *nix.
+ **/
+
+/** TODO:
+ ** - send session key directly on new connections where we already know
+ ** the server requires Kerberos authn.
+ ** - fix authn status, as the COMPLETE/CONTINUE status values
+ ** are never used.
+ ** - Add a way for serf to give detailed error information back to the
+ ** application.
+ ** - proxy support
+ **/
+
+/* Authentication over HTTP using Kerberos
+ *
+ * Kerberos involves three servers:
+ * - Authentication Server (AS): verifies users during login
+ * - Ticket-Granting Server (TGS): issues proof of identity tickets
+ * - HTTP server (S)
+ *
+ * Steps:
+ * 0. User logs in to the AS and receives a TGS ticket. On workstations
+ * where the login program doesn't support Kerberos, the user can use
+ * 'kinit'.
+ *
+ * 1. C --> S: GET
+ *
+ * C <-- S: 401 Authentication Required
+ * WWW-Authenticate: Negotiate
+ *
+ * -> app contacts the TGS to request a session key for the HTTP service
+ * @ target host. The returned session key is encrypted with the HTTP
+ * service's secret key, so we can safely send it to the server.
+ *
+ * 2. C --> S: GET
+ * Authorization: Negotiate <Base64 encoded session key>
+ * gss_api_ctx->state = gss_api_auth_in_progress;
+ *
+ * C <-- S: 200 OK
+ * WWW-Authenticate: Negotiate <Base64 encoded server
+ * authentication data>
+ *
+ * -> The server returned a key to proof itself to us. We check this key
+ * with the TGS again.
+ *
+ * Note: It's possible that the server returns 401 again in step 3, if the
+ * Kerberos context isn't complete yet. Some (simple) tests with
+ * mod_auth_kerb and MIT Kerberos 5 show this never happens.
+ *
+ * This handshake is required for every new connection. If the handshake is
+ * completed successfully, all other requests on the same connection will
+ * be authenticated without needing to pass the WWW-Authenticate header.
+ *
+ * Note: Step 1 of the handshake will only happen on the first connection, once
+ * we know the server requires Kerberos authentication, the initial requests
+ * on the other connections will include a session key, so we start at
+ * step 2 in the handshake.
+ * ### TODO: Not implemented yet!
+ */
+
+typedef enum {
+ gss_api_auth_not_started,
+ gss_api_auth_in_progress,
+ gss_api_auth_completed,
+} gss_api_auth_state;
+
+/* HTTP Service name, used to get the session key. */
+#define KRB_HTTP_SERVICE "HTTP"
+
+/* Stores the context information related to Kerberos authentication. */
+typedef struct
+{
+ apr_pool_t *pool;
+
+ /* GSSAPI context */
+ serf__kerb_context_t *gss_ctx;
+
+ /* Current state of the authentication cycle. */
+ gss_api_auth_state state;
+
+ const char *header;
+ const char *value;
+} gss_authn_info_t;
+
+/* On the initial 401 response of the server, request a session key from
+ the Kerberos KDC to pass to the server, proving that we are who we
+ claim to be. The session key can only be used with the HTTP service
+ on the target host. */
+static apr_status_t
+gss_api_get_credentials(char *token, apr_size_t token_len,
+ const char *hostname,
+ const char **buf, apr_size_t *buf_len,
+ gss_authn_info_t *gss_info)
+{
+ serf__kerb_buffer_t input_buf;
+ serf__kerb_buffer_t output_buf;
+ apr_status_t status = APR_SUCCESS;
+
+ /* If the server sent us a token, pass it to gss_init_sec_token for
+ validation. */
+ if (token) {
+ input_buf.value = token;
+ input_buf.length = token_len;
+ } else {
+ input_buf.value = 0;
+ input_buf.length = 0;
+ }
+
+ /* Establish a security context to the server. */
+ status = serf__kerb_init_sec_context
+ (gss_info->gss_ctx,
+ KRB_HTTP_SERVICE, hostname,
+ &input_buf,
+ &output_buf,
+ gss_info->pool,
+ gss_info->pool
+ );
+
+ switch(status) {
+ case APR_SUCCESS:
+ gss_info->state = gss_api_auth_completed;
+ break;
+ case APR_EAGAIN:
+ gss_info->state = gss_api_auth_in_progress;
+ status = APR_SUCCESS;
+ break;
+ default:
+ status = SERF_ERROR_AUTHN_FAILED;
+ break;
+ }
+
+ /* Return the session key to our caller. */
+ *buf = output_buf.value;
+ *buf_len = output_buf.length;
+
+ return status;
+}
+
+/* Read the header sent by the server (if any), invoke the gssapi authn
+ code and use the resulting Server Ticket on the next request to the
+ server. */
+static apr_status_t
+do_auth(int code,
+ gss_authn_info_t *gss_info,
+ serf_connection_t *conn,
+ const char *auth_hdr,
+ apr_pool_t *pool)
+{
+ serf_context_t *ctx = conn->ctx;
+ serf__authn_info_t *authn_info = (code == 401) ? &ctx->authn_info :
+ &ctx->proxy_authn_info;
+ const char *tmp = NULL;
+ char *token = NULL;
+ apr_size_t tmp_len = 0, token_len = 0;
+ const char *space = NULL;
+ apr_status_t status;
+
+ /* The server will return a token as attribute to the Negotiate key.
+ Negotiate YGwGCSqGSIb3EgECAgIAb10wW6ADAgEFoQMCAQ+iTzBNoAMCARCiRgREa6mouM
+ BAMFqKVdTGtfpZNXKzyw4Yo1paphJdIA3VOgncaoIlXxZLnkHiIHS2v65pVvrp
+ bRIyjF8xve9HxpnNIucCY9c=
+
+ Read this base64 value, decode it and validate it so we're sure the server
+ is who we expect it to be. */
+ if (auth_hdr)
+ space = strchr(auth_hdr, ' ');
+
+ if (space) {
+ token = apr_palloc(pool, apr_base64_decode_len(space + 1));
+ token_len = apr_base64_decode(token, space + 1);
+ }
+
+ /* We can get a whole batch of 401 responses from the server, but we should
+ only start the authentication phase once, so if we started authentication
+ already ignore all responses with initial Negotiate authentication header.
+
+ Note: as we set the max. transfer rate to one message at a time until the
+ authentication cycle is finished, this check shouldn't be needed. */
+ if (!token && gss_info->state != gss_api_auth_not_started)
+ return APR_SUCCESS;
+
+ status = gss_api_get_credentials(token, token_len, conn->host_info.hostname,
+ &tmp, &tmp_len,
+ gss_info);
+ if (status)
+ return status;
+
+ serf__encode_auth_header(&gss_info->value, authn_info->scheme->name,
+ tmp,
+ tmp_len,
+ pool);
+ gss_info->header = (code == 401) ? "Authorization" : "Proxy-Authorization";
+
+ /* If the handshake is finished tell serf it can send as much requests as it
+ likes. */
+ if (gss_info->state == gss_api_auth_completed)
+ serf_connection_set_max_outstanding_requests(conn, 0);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t
+serf__init_kerb(int code,
+ serf_context_t *ctx,
+ apr_pool_t *pool)
+{
+ return APR_SUCCESS;
+}
+
+/* A new connection is created to a server that's known to use
+ Kerberos. */
+apr_status_t
+serf__init_kerb_connection(int code,
+ serf_connection_t *conn,
+ apr_pool_t *pool)
+{
+ gss_authn_info_t *gss_info;
+ apr_status_t status;
+
+ gss_info = apr_pcalloc(pool, sizeof(*gss_info));
+ gss_info->pool = conn->pool;
+ gss_info->state = gss_api_auth_not_started;
+ status = serf__kerb_create_sec_context(&gss_info->gss_ctx, pool,
+ gss_info->pool);
+
+ if (status) {
+ return status;
+ }
+
+ if (code == 401) {
+ conn->authn_baton = gss_info;
+ } else {
+ conn->proxy_authn_baton = gss_info;
+ }
+
+ /* Make serf send the initial requests one by one */
+ serf_connection_set_max_outstanding_requests(conn, 1);
+
+ return APR_SUCCESS;
+}
+
+/* A 401 response was received, handle the authentication. */
+apr_status_t
+serf__handle_kerb_auth(int code,
+ serf_request_t *request,
+ serf_bucket_t *response,
+ const char *auth_hdr,
+ const char *auth_attr,
+ void *baton,
+ apr_pool_t *pool)
+{
+ serf_connection_t *conn = request->conn;
+ gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
+ conn->proxy_authn_baton;
+
+ return do_auth(code,
+ gss_info,
+ request->conn,
+ auth_hdr,
+ pool);
+}
+
+/* Setup the authn headers on this request message. */
+apr_status_t
+serf__setup_request_kerb_auth(int code,
+ serf_connection_t *conn,
+ const char *method,
+ const char *uri,
+ serf_bucket_t *hdrs_bkt)
+{
+ gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
+ conn->proxy_authn_baton;
+
+ if (gss_info && gss_info->header && gss_info->value) {
+ serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
+ gss_info->value);
+
+ /* We should send each token only once. */
+ gss_info->header = NULL;
+ gss_info->value = NULL;
+ return APR_SUCCESS;
+ }
+
+ return SERF_ERROR_AUTHN_FAILED;
+}
+
+/* Function is called when 2xx responses are received. Normally we don't
+ * have to do anything, except for the first response after the
+ * authentication handshake. This specific response includes authentication
+ * data which should be validated by the client (mutual authentication).
+ */
+apr_status_t
+serf__validate_response_kerb_auth(int code,
+ serf_connection_t *conn,
+ serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *pool)
+{
+ gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
+ conn->proxy_authn_baton;
+ serf_bucket_t *hdrs;
+ const char *auth_hdr;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ auth_hdr = serf_bucket_headers_get(hdrs, "WWW-Authenticate");
+
+ if (gss_info->state != gss_api_auth_completed) {
+ return do_auth(code,
+ gss_info,
+ conn,
+ auth_hdr,
+ pool);
+ }
+
+ return APR_SUCCESS;
+}
+
+#endif /* SERF_HAVE_GSSAPI */