]> git.proxmox.com Git - libgit2.git/blob - src/transports/auth_negotiate.c
New upstream version 1.1.0+dfsg.1
[libgit2.git] / src / transports / auth_negotiate.c
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
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
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 assert(ctx && ctx->configured && challenge);
69
70 git__free(ctx->challenge);
71
72 ctx->challenge = git__strdup(challenge);
73 GIT_ERROR_CHECK_ALLOC(ctx->challenge);
74
75 return 0;
76 }
77
78 static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
79 {
80 OM_uint32 status_minor;
81
82 if (ctx->gss_context != GSS_C_NO_CONTEXT) {
83 gss_delete_sec_context(
84 &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER);
85 ctx->gss_context = GSS_C_NO_CONTEXT;
86 }
87
88 git_buf_dispose(&ctx->target);
89
90 git__free(ctx->challenge);
91 ctx->challenge = NULL;
92 }
93
94 static int negotiate_next_token(
95 git_buf *buf,
96 git_http_auth_context *c,
97 git_credential *cred)
98 {
99 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
100 OM_uint32 status_major, status_minor;
101 gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
102 input_token = GSS_C_EMPTY_BUFFER,
103 output_token = GSS_C_EMPTY_BUFFER;
104 gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
105 git_buf input_buf = GIT_BUF_INIT;
106 gss_name_t server = NULL;
107 gss_OID mech;
108 size_t challenge_len;
109 int error = 0;
110
111 assert(buf && ctx && ctx->configured && cred && cred->credtype == GIT_CREDENTIAL_DEFAULT);
112
113 if (ctx->complete)
114 return 0;
115
116 target_buffer.value = (void *)ctx->target.ptr;
117 target_buffer.length = ctx->target.size;
118
119 status_major = gss_import_name(&status_minor, &target_buffer,
120 GSS_C_NT_HOSTBASED_SERVICE, &server);
121
122 if (GSS_ERROR(status_major)) {
123 negotiate_err_set(status_major, status_minor,
124 "could not parse principal");
125 error = -1;
126 goto done;
127 }
128
129 challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
130
131 if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) {
132 git_error_set(GIT_ERROR_NET, "server did not request negotiate");
133 error = -1;
134 goto done;
135 }
136
137 if (challenge_len > 9) {
138 if (git_buf_decode_base64(&input_buf,
139 ctx->challenge + 10, challenge_len - 10) < 0) {
140 git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server");
141 error = -1;
142 goto done;
143 }
144
145 input_token.value = input_buf.ptr;
146 input_token.length = input_buf.size;
147 input_token_ptr = &input_token;
148 } else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
149 negotiate_context_dispose(ctx);
150 }
151
152 mech = &negotiate_oid_spnego;
153
154 status_major = gss_init_sec_context(
155 &status_minor,
156 GSS_C_NO_CREDENTIAL,
157 &ctx->gss_context,
158 server,
159 mech,
160 GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
161 GSS_C_INDEFINITE,
162 GSS_C_NO_CHANNEL_BINDINGS,
163 input_token_ptr,
164 NULL,
165 &output_token,
166 NULL,
167 NULL);
168
169 if (GSS_ERROR(status_major)) {
170 negotiate_err_set(status_major, status_minor, "negotiate failure");
171 error = -1;
172 goto done;
173 }
174
175 /* This message merely told us auth was complete; we do not respond. */
176 if (status_major == GSS_S_COMPLETE) {
177 negotiate_context_dispose(ctx);
178 ctx->complete = 1;
179 goto done;
180 }
181
182 if (output_token.length == 0) {
183 git_error_set(GIT_ERROR_NET, "GSSAPI did not return token");
184 error = -1;
185 goto done;
186 }
187
188 git_buf_puts(buf, "Negotiate ");
189 git_buf_encode_base64(buf, output_token.value, output_token.length);
190
191 if (git_buf_oom(buf))
192 error = -1;
193
194 done:
195 gss_release_name(&status_minor, &server);
196 gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
197 git_buf_dispose(&input_buf);
198 return error;
199 }
200
201 static int negotiate_is_complete(git_http_auth_context *c)
202 {
203 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
204
205 assert(ctx);
206
207 return (ctx->complete == 1);
208 }
209
210 static void negotiate_context_free(git_http_auth_context *c)
211 {
212 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
213
214 negotiate_context_dispose(ctx);
215
216 ctx->configured = 0;
217 ctx->complete = 0;
218 ctx->oid = NULL;
219
220 git__free(ctx);
221 }
222
223 static int negotiate_init_context(
224 http_auth_negotiate_context *ctx,
225 const git_net_url *url)
226 {
227 OM_uint32 status_major, status_minor;
228 gss_OID item, *oid;
229 gss_OID_set mechanism_list;
230 size_t i;
231
232 /* Query supported mechanisms looking for SPNEGO) */
233 status_major = gss_indicate_mechs(&status_minor, &mechanism_list);
234
235 if (GSS_ERROR(status_major)) {
236 negotiate_err_set(status_major, status_minor,
237 "could not query mechanisms");
238 return -1;
239 }
240
241 if (mechanism_list) {
242 for (oid = negotiate_oids; *oid; oid++) {
243 for (i = 0; i < mechanism_list->count; i++) {
244 item = &mechanism_list->elements[i];
245
246 if (item->length == (*oid)->length &&
247 memcmp(item->elements, (*oid)->elements, item->length) == 0) {
248 ctx->oid = *oid;
249 break;
250 }
251
252 }
253
254 if (ctx->oid)
255 break;
256 }
257 }
258
259 gss_release_oid_set(&status_minor, &mechanism_list);
260
261 if (!ctx->oid) {
262 git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported");
263 return -1;
264 }
265
266 git_buf_puts(&ctx->target, "HTTP@");
267 git_buf_puts(&ctx->target, url->host);
268
269 if (git_buf_oom(&ctx->target))
270 return -1;
271
272 ctx->gss_context = GSS_C_NO_CONTEXT;
273 ctx->configured = 1;
274
275 return 0;
276 }
277
278 int git_http_auth_negotiate(
279 git_http_auth_context **out,
280 const git_net_url *url)
281 {
282 http_auth_negotiate_context *ctx;
283
284 *out = NULL;
285
286 ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
287 GIT_ERROR_CHECK_ALLOC(ctx);
288
289 if (negotiate_init_context(ctx, url) < 0) {
290 git__free(ctx);
291 return -1;
292 }
293
294 ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE;
295 ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT;
296 ctx->parent.connection_affinity = 1;
297 ctx->parent.set_challenge = negotiate_set_challenge;
298 ctx->parent.next_token = negotiate_next_token;
299 ctx->parent.is_complete = negotiate_is_complete;
300 ctx->parent.free = negotiate_context_free;
301
302 *out = (git_http_auth_context *)ctx;
303
304 return 0;
305 }
306
307 #endif /* GIT_GSSAPI */
308