1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "auth_negotiate.h"
9 
10 #if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK)
11 
12 #include "git2.h"
13 #include "buffer.h"
14 #include "auth.h"
15 #include "git2/sys/credential.h"
16 
17 #ifdef GIT_GSSFRAMEWORK
18 #import <GSS/GSS.h>
19 #elif defined(GIT_GSSAPI)
20 #include <gssapi.h>
21 #include <krb5.h>
22 #endif
23 
24 static gss_OID_desc negotiate_oid_spnego =
25 	{ 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
26 static gss_OID_desc negotiate_oid_krb5 =
27 	{ 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
28 
29 static gss_OID negotiate_oids[] =
30 	{ &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
31 
32 typedef struct {
33 	git_http_auth_context parent;
34 	unsigned configured : 1,
35 		complete : 1;
36 	git_buf target;
37 	char *challenge;
38 	gss_ctx_id_t gss_context;
39 	gss_OID oid;
40 } http_auth_negotiate_context;
41 
negotiate_err_set(OM_uint32 status_major,OM_uint32 status_minor,const char * message)42 static void negotiate_err_set(
43 	OM_uint32 status_major,
44 	OM_uint32 status_minor,
45 	const char *message)
46 {
47 	gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
48 	OM_uint32 status_display, context = 0;
49 
50 	if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE,
51 		GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) {
52 		git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)",
53 			message, (int)buffer.length, (const char *)buffer.value,
54 			status_major, status_minor);
55 		gss_release_buffer(&status_minor, &buffer);
56 	} else {
57 		git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)",
58 			message, status_major, status_minor);
59 	}
60 }
61 
negotiate_set_challenge(git_http_auth_context * c,const char * challenge)62 static int negotiate_set_challenge(
63 	git_http_auth_context *c,
64 	const char *challenge)
65 {
66 	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
67 
68 	GIT_ASSERT_ARG(ctx);
69 	GIT_ASSERT_ARG(challenge);
70 	GIT_ASSERT(ctx->configured);
71 
72 	git__free(ctx->challenge);
73 
74 	ctx->challenge = git__strdup(challenge);
75 	GIT_ERROR_CHECK_ALLOC(ctx->challenge);
76 
77 	return 0;
78 }
79 
negotiate_context_dispose(http_auth_negotiate_context * ctx)80 static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
81 {
82 	OM_uint32 status_minor;
83 
84 	if (ctx->gss_context != GSS_C_NO_CONTEXT) {
85 		gss_delete_sec_context(
86 		    &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER);
87 		ctx->gss_context = GSS_C_NO_CONTEXT;
88 	}
89 
90 	git_buf_dispose(&ctx->target);
91 
92 	git__free(ctx->challenge);
93 	ctx->challenge = NULL;
94 }
95 
negotiate_next_token(git_buf * buf,git_http_auth_context * c,git_credential * cred)96 static int negotiate_next_token(
97 	git_buf *buf,
98 	git_http_auth_context *c,
99 	git_credential *cred)
100 {
101 	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
102 	OM_uint32 status_major, status_minor;
103 	gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
104 		input_token = GSS_C_EMPTY_BUFFER,
105 		output_token = GSS_C_EMPTY_BUFFER;
106 	gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
107 	git_buf input_buf = GIT_BUF_INIT;
108 	gss_name_t server = NULL;
109 	gss_OID mech;
110 	size_t challenge_len;
111 	int error = 0;
112 
113 	GIT_ASSERT_ARG(buf);
114 	GIT_ASSERT_ARG(ctx);
115 	GIT_ASSERT_ARG(cred);
116 
117 	GIT_ASSERT(ctx->configured);
118 	GIT_ASSERT(cred->credtype == GIT_CREDENTIAL_DEFAULT);
119 
120 	if (ctx->complete)
121 		return 0;
122 
123 	target_buffer.value = (void *)ctx->target.ptr;
124 	target_buffer.length = ctx->target.size;
125 
126 	status_major = gss_import_name(&status_minor, &target_buffer,
127 		GSS_C_NT_HOSTBASED_SERVICE, &server);
128 
129 	if (GSS_ERROR(status_major)) {
130 		negotiate_err_set(status_major, status_minor,
131 			"could not parse principal");
132 		error = -1;
133 		goto done;
134 	}
135 
136 	challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
137 
138 	if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) {
139 		git_error_set(GIT_ERROR_NET, "server did not request negotiate");
140 		error = -1;
141 		goto done;
142 	}
143 
144 	if (challenge_len > 9) {
145 		if (git_buf_decode_base64(&input_buf,
146 				ctx->challenge + 10, challenge_len - 10) < 0) {
147 			git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server");
148 			error = -1;
149 			goto done;
150 		}
151 
152 		input_token.value = input_buf.ptr;
153 		input_token.length = input_buf.size;
154 		input_token_ptr = &input_token;
155 	} else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
156 		negotiate_context_dispose(ctx);
157 	}
158 
159 	mech = &negotiate_oid_spnego;
160 
161 	status_major = gss_init_sec_context(
162 		&status_minor,
163 		GSS_C_NO_CREDENTIAL,
164 		&ctx->gss_context,
165 		server,
166 		mech,
167 		GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
168 		GSS_C_INDEFINITE,
169 		GSS_C_NO_CHANNEL_BINDINGS,
170 		input_token_ptr,
171 		NULL,
172 		&output_token,
173 		NULL,
174 		NULL);
175 
176 	if (GSS_ERROR(status_major)) {
177 		negotiate_err_set(status_major, status_minor, "negotiate failure");
178 		error = -1;
179 		goto done;
180 	}
181 
182 	/* This message merely told us auth was complete; we do not respond. */
183 	if (status_major == GSS_S_COMPLETE) {
184 		negotiate_context_dispose(ctx);
185 		ctx->complete = 1;
186 		goto done;
187 	}
188 
189 	if (output_token.length == 0) {
190 		git_error_set(GIT_ERROR_NET, "GSSAPI did not return token");
191 		error = -1;
192 		goto done;
193 	}
194 
195 	git_buf_puts(buf, "Negotiate ");
196 	git_buf_encode_base64(buf, output_token.value, output_token.length);
197 
198 	if (git_buf_oom(buf))
199 		error = -1;
200 
201 done:
202 	gss_release_name(&status_minor, &server);
203 	gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
204 	git_buf_dispose(&input_buf);
205 	return error;
206 }
207 
negotiate_is_complete(git_http_auth_context * c)208 static int negotiate_is_complete(git_http_auth_context *c)
209 {
210 	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
211 
212 	GIT_ASSERT_ARG(ctx);
213 
214 	return (ctx->complete == 1);
215 }
216 
negotiate_context_free(git_http_auth_context * c)217 static void negotiate_context_free(git_http_auth_context *c)
218 {
219 	http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
220 
221 	negotiate_context_dispose(ctx);
222 
223 	ctx->configured = 0;
224 	ctx->complete = 0;
225 	ctx->oid = NULL;
226 
227 	git__free(ctx);
228 }
229 
negotiate_init_context(http_auth_negotiate_context * ctx,const git_net_url * url)230 static int negotiate_init_context(
231 	http_auth_negotiate_context *ctx,
232 	const git_net_url *url)
233 {
234 	OM_uint32 status_major, status_minor;
235 	gss_OID item, *oid;
236 	gss_OID_set mechanism_list;
237 	size_t i;
238 
239 	/* Query supported mechanisms looking for SPNEGO) */
240 	status_major = gss_indicate_mechs(&status_minor, &mechanism_list);
241 
242 	if (GSS_ERROR(status_major)) {
243 		negotiate_err_set(status_major, status_minor,
244 			"could not query mechanisms");
245 		return -1;
246 	}
247 
248 	if (mechanism_list) {
249 		for (oid = negotiate_oids; *oid; oid++) {
250 			for (i = 0; i < mechanism_list->count; i++) {
251 				item = &mechanism_list->elements[i];
252 
253 				if (item->length == (*oid)->length &&
254 					memcmp(item->elements, (*oid)->elements, item->length) == 0) {
255 					ctx->oid = *oid;
256 					break;
257 				}
258 
259 			}
260 
261 			if (ctx->oid)
262 				break;
263 		}
264 	}
265 
266 	gss_release_oid_set(&status_minor, &mechanism_list);
267 
268 	if (!ctx->oid) {
269 		git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported");
270 		return GIT_EAUTH;
271 	}
272 
273 	git_buf_puts(&ctx->target, "HTTP@");
274 	git_buf_puts(&ctx->target, url->host);
275 
276 	if (git_buf_oom(&ctx->target))
277 		return -1;
278 
279 	ctx->gss_context = GSS_C_NO_CONTEXT;
280 	ctx->configured = 1;
281 
282 	return 0;
283 }
284 
git_http_auth_negotiate(git_http_auth_context ** out,const git_net_url * url)285 int git_http_auth_negotiate(
286 	git_http_auth_context **out,
287 	const git_net_url *url)
288 {
289 	http_auth_negotiate_context *ctx;
290 
291 	*out = NULL;
292 
293 	ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
294 	GIT_ERROR_CHECK_ALLOC(ctx);
295 
296 	if (negotiate_init_context(ctx, url) < 0) {
297 		git__free(ctx);
298 		return -1;
299 	}
300 
301 	ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE;
302 	ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT;
303 	ctx->parent.connection_affinity = 1;
304 	ctx->parent.set_challenge = negotiate_set_challenge;
305 	ctx->parent.next_token = negotiate_next_token;
306 	ctx->parent.is_complete = negotiate_is_complete;
307 	ctx->parent.free = negotiate_context_free;
308 
309 	*out = (git_http_auth_context *)ctx;
310 
311 	return 0;
312 }
313 
314 #endif /* GIT_GSSAPI */
315 
316