summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2014-07-31 18:43:20 -0400
committerEdward Thomson <ethomson@edwardthomson.com>2014-08-15 11:12:42 -0400
commit315cb38e1e32e77037e82c6cbdc383c151041112 (patch)
treeef6f113d3df5acfe713a1c0b26b2e3234c7c8870 /src
parente003f83a5840d6e9966f1830768771bcd205ba52 (diff)
downloadlibgit2-315cb38e1e32e77037e82c6cbdc383c151041112.tar.gz
Add GSSAPI support for SPNEGO/Kerberos auth over HTTP
Diffstat (limited to 'src')
-rw-r--r--src/transports/http.c317
1 files changed, 287 insertions, 30 deletions
diff --git a/src/transports/http.c b/src/transports/http.c
index ffa293ec0..c43f6c548 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -12,6 +12,11 @@
#include "netops.h"
#include "smart.h"
+#ifdef GIT_GSSAPI
+# include <gssapi.h>
+# include <krb5.h>
+#endif
+
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/git-upload-pack";
@@ -21,6 +26,17 @@ static const char *receive_pack_service_url = "/git-receive-pack";
static const char *get_verb = "GET";
static const char *post_verb = "POST";
static const char *basic_authtype = "Basic";
+static const char *negotiate_authtype = "Negotiate";
+
+#ifdef GIT_GSSAPI
+static gss_OID_desc negotiate_oid_spnego =
+ { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
+static gss_OID_desc negotiate_oid_krb5 =
+ { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+
+static gss_OID negotiate_oids[] =
+ { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
+#endif
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
@@ -37,6 +53,7 @@ enum last_cb {
typedef enum {
GIT_HTTP_AUTH_BASIC = 1,
+ GIT_HTTP_AUTH_NEGOTIATE = 2,
} http_authmechanism_t;
typedef struct {
@@ -61,6 +78,7 @@ typedef struct {
git_cred *cred;
git_cred *url_cred;
http_authmechanism_t auth_mechanism;
+ char *auth_challenge;
bool connected;
/* Parser structures */
@@ -76,6 +94,14 @@ typedef struct {
enum last_cb last_cb;
int parse_error;
unsigned parse_finished : 1;
+
+#ifdef GIT_GSSAPI
+ unsigned negotiate_configured : 1,
+ negotiate_complete : 1;
+ git_buf negotiate_target;
+ gss_ctx_id_t negotiate_context;
+ gss_OID negotiate_oid;
+#endif
} http_subtransport;
typedef struct {
@@ -88,12 +114,17 @@ typedef struct {
size_t *bytes_read;
} parser_context;
-static int apply_basic_credential(git_buf *buf, git_cred *cred)
+static int apply_basic_credential(
+ git_buf *buf,
+ http_subtransport *transport,
+ git_cred *cred)
{
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
git_buf raw = GIT_BUF_INIT;
int error = -1;
+ GIT_UNUSED(transport);
+
git_buf_printf(&raw, "%s:%s", c->username, c->password);
if (git_buf_oom(&raw) ||
@@ -112,6 +143,188 @@ on_error:
return error;
}
+#ifdef GIT_GSSAPI
+
+static void negotiate_err_set(
+ OM_uint32 status_major,
+ OM_uint32 status_minor,
+ const char *message)
+{
+ gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
+ OM_uint32 status_display, context = 0;
+
+ if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE,
+ GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) {
+ giterr_set(GITERR_NET, "%s: %.*s (%d.%d)",
+ message, (int)buffer.length, (const char *)buffer.value,
+ status_major, status_minor);
+ gss_release_buffer(&status_minor, &buffer);
+ } else {
+ giterr_set(GITERR_NET, "%s: unknown negotiate error (%d.%d)",
+ message, status_major, status_minor);
+ }
+}
+
+static int negotiate_configure(http_subtransport *transport)
+{
+ OM_uint32 status_major, status_minor;
+ gss_OID item, *oid;
+ gss_OID_set mechanism_list;
+ size_t i;
+
+ /* Query supported mechanisms looking for SPNEGO) */
+ if (GSS_ERROR(status_major =
+ gss_indicate_mechs(&status_minor, &mechanism_list))) {
+ negotiate_err_set(status_major, status_minor,
+ "could not query mechanisms");
+ return -1;
+ }
+
+ if (mechanism_list) {
+ for (oid = negotiate_oids; *oid; oid++) {
+ for (i = 0; i < mechanism_list->count; i++) {
+ item = &mechanism_list->elements[i];
+
+ if (item->length == (*oid)->length &&
+ memcmp(item->elements, (*oid)->elements, item->length) == 0) {
+ transport->negotiate_oid = *oid;
+ break;
+ }
+
+ }
+
+ if (transport->negotiate_oid)
+ break;
+ }
+ }
+
+ gss_release_oid_set(&status_minor, &mechanism_list);
+
+ if (!transport->negotiate_oid) {
+ giterr_set(GITERR_NET, "Negotiate authentication is not supported");
+ return -1;
+ }
+
+ git_buf_puts(&transport->negotiate_target, "HTTP@");
+ git_buf_puts(&transport->negotiate_target, transport->connection_data.host);
+
+ if (git_buf_oom(&transport->negotiate_target))
+ return -1;
+
+ transport->negotiate_context = GSS_C_NO_CONTEXT;
+ transport->negotiate_configured = 1;
+ return 0;
+}
+
+static int negotiate_next_token(
+ git_buf *buf,
+ http_subtransport *transport,
+ git_cred *cred)
+{
+ OM_uint32 status_major, status_minor;
+ gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
+ input_token = GSS_C_EMPTY_BUFFER,
+ output_token = GSS_C_EMPTY_BUFFER;
+ gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
+ git_buf input_buf = GIT_BUF_INIT;
+ gss_name_t server = NULL;
+ gss_OID mech;
+ size_t challenge_len;
+ int error = 0;
+
+ GIT_UNUSED(cred);
+
+ target_buffer.value = (void *)transport->negotiate_target.ptr;
+ target_buffer.length = transport->negotiate_target.size;
+
+ status_major = gss_import_name(&status_minor, &target_buffer,
+ GSS_C_NT_HOSTBASED_SERVICE, &server);
+
+ if (GSS_ERROR(status_major)) {
+ negotiate_err_set(status_major, status_minor,
+ "Could not parse principal");
+ error = -1;
+ goto done;
+ }
+
+ challenge_len = transport->auth_challenge ?
+ strlen(transport->auth_challenge) : 0;
+ assert(challenge_len >= 9);
+
+ if (challenge_len > 9) {
+ if (git_buf_decode_base64(&input_buf,
+ transport->auth_challenge + 10, challenge_len - 10) < 0) {
+ giterr_set(GITERR_NET, "Invalid negotiate challenge from server");
+ error = -1;
+ goto done;
+ }
+
+ input_token.value = input_buf.ptr;
+ input_token.length = input_buf.size;
+ input_token_ptr = &input_token;
+ } else if (transport->negotiate_context != GSS_C_NO_CONTEXT) {
+ giterr_set(GITERR_NET, "Could not restart authentication");
+ error = -1;
+ goto done;
+ }
+
+ mech = &negotiate_oid_spnego;
+
+ if (GSS_ERROR(status_major = gss_init_sec_context(
+ &status_minor,
+ GSS_C_NO_CREDENTIAL,
+ &transport->negotiate_context,
+ server,
+ mech,
+ GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
+ GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ input_token_ptr,
+ NULL,
+ &output_token,
+ NULL,
+ NULL))) {
+ negotiate_err_set(status_major, status_minor, "Negotiate failure");
+ error = -1;
+ goto done;
+ }
+
+ /* This message merely told us auth was complete; we do not respond. */
+ if (status_major == GSS_S_COMPLETE) {
+ transport->negotiate_complete = 1;
+ goto done;
+ }
+
+ git_buf_puts(buf, "Authorization: Negotiate ");
+ git_buf_encode_base64(buf, output_token.value, output_token.length);
+ git_buf_puts(buf, "\r\n");
+
+ if (git_buf_oom(buf))
+ error = -1;
+
+done:
+ gss_release_name(&status_minor, &server);
+ gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
+ git_buf_free(&input_buf);
+ return error;
+}
+
+static int apply_negotiate_credential(
+ git_buf *buf,
+ http_subtransport *transport,
+ git_cred *cred)
+{
+ if (!transport->negotiate_configured && negotiate_configure(transport) < 0)
+ return -1;
+
+ if (transport->negotiate_complete)
+ return 0;
+
+ return negotiate_next_token(buf, transport, cred);
+}
+
+#endif /* GIT_GSSAPI */
+
static int gen_request(
git_buf *buf,
http_stream *s,
@@ -137,17 +350,26 @@ static int gen_request(
git_buf_puts(buf, "Accept: */*\r\n");
/* Apply credentials to the request */
+#ifdef GIT_GSSAPI
+ if (t->cred && t->cred->credtype == GIT_CREDTYPE_DEFAULT &&
+ (t->auth_mechanism & GIT_HTTP_AUTH_NEGOTIATE)) {
+ if (apply_negotiate_credential(buf, t, t->cred) < 0)
+ return -1;
+ } else
+#endif
+
if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
- t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&
- apply_basic_credential(buf, t->cred) < 0)
- return -1;
+ t->auth_mechanism == GIT_HTTP_AUTH_BASIC) {
+ if (apply_basic_credential(buf, t, t->cred) < 0)
+ return -1;
+ }
/* Use url-parsed basic auth if username and password are both provided */
if (!t->cred && t->connection_data.user && t->connection_data.pass) {
if (!t->url_cred && git_cred_userpass_plaintext_new(&t->url_cred,
t->connection_data.user, t->connection_data.pass) < 0)
return -1;
- if (apply_basic_credential(buf, t->url_cred) < 0) return -1;
+ if (apply_basic_credential(buf, t, t->url_cred) < 0) return -1;
}
git_buf_puts(buf, "\r\n");
@@ -158,19 +380,32 @@ static int gen_request(
return 0;
}
-static int parse_unauthorized_response(
+static int parse_authenticate_response(
git_vector *www_authenticate,
int *allowed_types,
- http_authmechanism_t *auth_mechanism)
+ http_authmechanism_t *auth_mechanism,
+ char **auth_challenge)
{
unsigned i;
char *entry;
git_vector_foreach(www_authenticate, i, entry) {
- if (!strncmp(entry, basic_authtype, 5) &&
+ if (!strncmp(entry, negotiate_authtype, 9) &&
+ (entry[9] == '\0' || entry[9] == ' ')) {
+ *allowed_types |= GIT_CREDTYPE_DEFAULT;
+ *auth_mechanism = GIT_HTTP_AUTH_NEGOTIATE;
+
+ *auth_challenge = git__strdup(entry);
+ GITERR_CHECK_ALLOC(*auth_challenge);
+ }
+
+ else if (!strncmp(entry, basic_authtype, 5) &&
(entry[5] == '\0' || entry[5] == ' ')) {
*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
*auth_mechanism = GIT_HTTP_AUTH_BASIC;
+
+ *auth_challenge = git__strdup(entry);
+ GITERR_CHECK_ALLOC(*auth_challenge);
}
}
@@ -248,7 +483,7 @@ static int on_headers_complete(http_parser *parser)
http_subtransport *t = ctx->t;
http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
- int error = 0, no_callback = 0;
+ int error = 0, no_callback = 0, allowed_auth_types = 0;
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
@@ -256,26 +491,31 @@ static int on_headers_complete(http_parser *parser)
if (on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
- /* Check for an authentication failure. */
+ /* Capture authentication headers which may be a 401 (authentication
+ * is not complete) or a 200 (simply informing us that auth *is*
+ * complete.)
+ */
+ git__free(t->auth_challenge);
+ t->auth_challenge = NULL;
- if (parser->status_code == 401 &&
- get_verb == s->verb) {
+ if (parse_authenticate_response(&t->www_authenticate,
+ &allowed_auth_types,
+ &t->auth_mechanism,
+ &t->auth_challenge) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 && get_verb == s->verb) {
if (!t->owner->cred_acquire_cb) {
no_callback = 1;
} else {
- int allowed_types = 0;
-
- if (parse_unauthorized_response(&t->www_authenticate,
- &allowed_types, &t->auth_mechanism) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- if (allowed_types &&
- (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+ if (allowed_auth_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_auth_types))) {
error = t->owner->cred_acquire_cb(&t->cred,
t->owner->url,
t->connection_data.user,
- allowed_types,
+ allowed_auth_types,
t->owner->cred_acquire_payload);
if (error == GIT_PASSTHROUGH) {
@@ -511,10 +751,8 @@ replay:
clear_parser_state(t);
- if (gen_request(&request, s, 0) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, 0) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
git_buf_free(&request);
@@ -613,10 +851,8 @@ static int http_stream_write_chunked(
clear_parser_state(t);
- if (gen_request(&request, s, 0) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, 0) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
git_buf_free(&request);
@@ -688,10 +924,8 @@ static int http_stream_write_single(
clear_parser_state(t);
- if (gen_request(&request, s, len) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, len) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
goto on_error;
@@ -855,6 +1089,24 @@ static int http_action(
return -1;
}
+static void clear_negotiate_state(http_subtransport *t)
+{
+#ifdef GIT_GSSAPI
+ OM_uint32 status_minor;
+
+ if (t->negotiate_context != GSS_C_NO_CONTEXT) {
+ gss_delete_sec_context(&status_minor, &t->negotiate_context, GSS_C_NO_BUFFER);
+ t->negotiate_context = GSS_C_NO_CONTEXT;
+ }
+
+ git_buf_free(&t->negotiate_target);
+
+ t->negotiate_configured = 0;
+ t->negotiate_complete = 0;
+ t->negotiate_oid = NULL;
+#endif
+}
+
static int http_close(git_smart_subtransport *subtransport)
{
http_subtransport *t = (http_subtransport *) subtransport;
@@ -876,6 +1128,11 @@ static int http_close(git_smart_subtransport *subtransport)
t->url_cred = NULL;
}
+ git__free(t->auth_challenge);
+ t->auth_challenge = NULL;
+
+ clear_negotiate_state(t);
+
gitno_connection_data_free_ptrs(&t->connection_data);
return 0;