auth_negotiate.c 7.22 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "auth_negotiate.h"

10
#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK)
11 12 13

#include "git2.h"
#include "auth.h"
14
#include "git2/sys/credential.h"
15

16 17 18
#ifdef GIT_GSSFRAMEWORK
#import <GSS/GSS.h>
#elif defined(GIT_GSSAPI)
19 20
#include <gssapi.h>
#include <krb5.h>
21
#endif
22 23 24 25 26 27 28 29 30 31 32 33 34

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 };

typedef struct {
	git_http_auth_context parent;
	unsigned configured : 1,
		complete : 1;
35
	git_str target;
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
	char *challenge;
	gss_ctx_id_t gss_context;
	gss_OID oid;
} http_auth_negotiate_context;

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) {
51
		git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)",
52 53 54 55
			message, (int)buffer.length, (const char *)buffer.value,
			status_major, status_minor);
		gss_release_buffer(&status_minor, &buffer);
	} else {
56
		git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)",
57 58 59 60 61 62 63 64 65 66
			message, status_major, status_minor);
	}
}

static int negotiate_set_challenge(
	git_http_auth_context *c,
	const char *challenge)
{
	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;

67 68 69
	GIT_ASSERT_ARG(ctx);
	GIT_ASSERT_ARG(challenge);
	GIT_ASSERT(ctx->configured);
70 71 72 73

	git__free(ctx->challenge);

	ctx->challenge = git__strdup(challenge);
74
	GIT_ERROR_CHECK_ALLOC(ctx->challenge);
75 76 77 78

	return 0;
}

79 80 81 82 83 84 85 86 87 88
static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
{
	OM_uint32 status_minor;

	if (ctx->gss_context != GSS_C_NO_CONTEXT) {
		gss_delete_sec_context(
		    &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER);
		ctx->gss_context = GSS_C_NO_CONTEXT;
	}

89
	git_str_dispose(&ctx->target);
90 91 92 93 94

	git__free(ctx->challenge);
	ctx->challenge = NULL;
}

95
static int negotiate_next_token(
96
	git_str *buf,
97
	git_http_auth_context *c,
98
	git_credential *cred)
99 100 101 102 103 104 105
{
	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
	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;
106
	git_str input_buf = GIT_STR_INIT;
107 108 109 110 111
	gss_name_t server = NULL;
	gss_OID mech;
	size_t challenge_len;
	int error = 0;

112 113 114 115 116 117
	GIT_ASSERT_ARG(buf);
	GIT_ASSERT_ARG(ctx);
	GIT_ASSERT_ARG(cred);

	GIT_ASSERT(ctx->configured);
	GIT_ASSERT(cred->credtype == GIT_CREDENTIAL_DEFAULT);
118 119 120 121 122 123 124 125 126 127 128 129

	if (ctx->complete)
		return 0;

	target_buffer.value = (void *)ctx->target.ptr;
	target_buffer.length = ctx->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,
130
			"could not parse principal");
131 132 133 134 135 136
		error = -1;
		goto done;
	}

	challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;

137 138
	if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) {
		git_error_set(GIT_ERROR_NET, "server did not request negotiate");
139 140
		error = -1;
		goto done;
141 142 143
	}

	if (challenge_len > 9) {
144
		if (git_str_decode_base64(&input_buf,
145
				ctx->challenge + 10, challenge_len - 10) < 0) {
146
			git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server");
147 148 149 150 151 152 153 154
			error = -1;
			goto done;
		}

		input_token.value = input_buf.ptr;
		input_token.length = input_buf.size;
		input_token_ptr = &input_token;
	} else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
155
		negotiate_context_dispose(ctx);
156 157 158 159
	}

	mech = &negotiate_oid_spnego;

160
	status_major = gss_init_sec_context(
161 162 163 164 165 166 167 168 169 170 171 172
		&status_minor,
		GSS_C_NO_CREDENTIAL,
		&ctx->gss_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,
173 174 175
		NULL);

	if (GSS_ERROR(status_major)) {
Edward Thomson committed
176
		negotiate_err_set(status_major, status_minor, "negotiate failure");
177 178 179 180 181 182
		error = -1;
		goto done;
	}

	/* This message merely told us auth was complete; we do not respond. */
	if (status_major == GSS_S_COMPLETE) {
183
		negotiate_context_dispose(ctx);
184 185 186 187
		ctx->complete = 1;
		goto done;
	}

188 189 190 191 192 193
	if (output_token.length == 0) {
		git_error_set(GIT_ERROR_NET, "GSSAPI did not return token");
		error = -1;
		goto done;
	}

194 195
	git_str_puts(buf, "Negotiate ");
	git_str_encode_base64(buf, output_token.value, output_token.length);
196

197
	if (git_str_oom(buf))
198 199 200 201 202
		error = -1;

done:
	gss_release_name(&status_minor, &server);
	gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
203
	git_str_dispose(&input_buf);
204 205 206
	return error;
}

207 208 209 210
static int negotiate_is_complete(git_http_auth_context *c)
{
	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;

211
	GIT_ASSERT_ARG(ctx);
212 213 214 215

	return (ctx->complete == 1);
}

216 217 218 219
static void negotiate_context_free(git_http_auth_context *c)
{
	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;

220
	negotiate_context_dispose(ctx);
221 222 223 224 225 226 227 228 229 230

	ctx->configured = 0;
	ctx->complete = 0;
	ctx->oid = NULL;

	git__free(ctx);
}

static int negotiate_init_context(
	http_auth_negotiate_context *ctx,
231
	const git_net_url *url)
232 233 234 235 236 237 238
{
	OM_uint32 status_major, status_minor;
	gss_OID item, *oid;
	gss_OID_set mechanism_list;
	size_t i;

	/* Query supported mechanisms looking for SPNEGO) */
239 240 241
	status_major = gss_indicate_mechs(&status_minor, &mechanism_list);

	if (GSS_ERROR(status_major)) {
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
		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) {
					ctx->oid = *oid;
					break;
				}

			}

			if (ctx->oid)
				break;
		}
	}

	gss_release_oid_set(&status_minor, &mechanism_list);

	if (!ctx->oid) {
268
		git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported");
269
		return GIT_EAUTH;
270 271
	}

272 273
	git_str_puts(&ctx->target, "HTTP@");
	git_str_puts(&ctx->target, url->host);
274

275
	if (git_str_oom(&ctx->target))
276 277 278 279 280 281 282 283 284 285
		return -1;

	ctx->gss_context = GSS_C_NO_CONTEXT;
	ctx->configured = 1;

	return 0;
}

int git_http_auth_negotiate(
	git_http_auth_context **out,
286
	const git_net_url *url)
287 288 289 290 291 292
{
	http_auth_negotiate_context *ctx;

	*out = NULL;

	ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
293
	GIT_ERROR_CHECK_ALLOC(ctx);
294

295
	if (negotiate_init_context(ctx, url) < 0) {
296 297 298 299
		git__free(ctx);
		return -1;
	}

300
	ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE;
301
	ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT;
302
	ctx->parent.connection_affinity = 1;
303 304
	ctx->parent.set_challenge = negotiate_set_challenge;
	ctx->parent.next_token = negotiate_next_token;
305
	ctx->parent.is_complete = negotiate_is_complete;
306 307 308 309 310 311 312 313 314
	ctx->parent.free = negotiate_context_free;

	*out = (git_http_auth_context *)ctx;

	return 0;
}

#endif /* GIT_GSSAPI */