]> git.proxmox.com Git - libgit2.git/blob - src/transports/auth_negotiate.c
25c865c15a1bd48e73fd97b274bd6192b4495876
[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 #ifdef GIT_GSSAPI
11
12 #include "git2.h"
13 #include "buffer.h"
14 #include "auth.h"
15
16 #include <gssapi.h>
17 #include <krb5.h>
18
19 static gss_OID_desc negotiate_oid_spnego =
20 { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
21 static gss_OID_desc negotiate_oid_krb5 =
22 { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
23
24 static gss_OID negotiate_oids[] =
25 { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
26
27 typedef struct {
28 git_http_auth_context parent;
29 unsigned configured : 1,
30 complete : 1;
31 git_buf target;
32 char *challenge;
33 gss_ctx_id_t gss_context;
34 gss_OID oid;
35 } http_auth_negotiate_context;
36
37 static void negotiate_err_set(
38 OM_uint32 status_major,
39 OM_uint32 status_minor,
40 const char *message)
41 {
42 gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
43 OM_uint32 status_display, context = 0;
44
45 if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE,
46 GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) {
47 git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)",
48 message, (int)buffer.length, (const char *)buffer.value,
49 status_major, status_minor);
50 gss_release_buffer(&status_minor, &buffer);
51 } else {
52 git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)",
53 message, status_major, status_minor);
54 }
55 }
56
57 static int negotiate_set_challenge(
58 git_http_auth_context *c,
59 const char *challenge)
60 {
61 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
62
63 assert(ctx && ctx->configured && challenge);
64
65 git__free(ctx->challenge);
66
67 ctx->challenge = git__strdup(challenge);
68 GIT_ERROR_CHECK_ALLOC(ctx->challenge);
69
70 return 0;
71 }
72
73 static int negotiate_next_token(
74 git_buf *buf,
75 git_http_auth_context *c,
76 const char *header_name,
77 git_cred *cred)
78 {
79 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
80 OM_uint32 status_major, status_minor;
81 gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
82 input_token = GSS_C_EMPTY_BUFFER,
83 output_token = GSS_C_EMPTY_BUFFER;
84 gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
85 git_buf input_buf = GIT_BUF_INIT;
86 gss_name_t server = NULL;
87 gss_OID mech;
88 size_t challenge_len;
89 int error = 0;
90
91 assert(buf && ctx && ctx->configured && cred && cred->credtype == GIT_CREDTYPE_DEFAULT);
92
93 if (ctx->complete)
94 return 0;
95
96 target_buffer.value = (void *)ctx->target.ptr;
97 target_buffer.length = ctx->target.size;
98
99 status_major = gss_import_name(&status_minor, &target_buffer,
100 GSS_C_NT_HOSTBASED_SERVICE, &server);
101
102 if (GSS_ERROR(status_major)) {
103 negotiate_err_set(status_major, status_minor,
104 "Could not parse principal");
105 error = -1;
106 goto done;
107 }
108
109 challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
110
111 if (challenge_len < 9) {
112 git_error_set(GIT_ERROR_NET, "no negotiate challenge sent from server");
113 error = -1;
114 goto done;
115 } else if (challenge_len > 9) {
116 if (git_buf_decode_base64(&input_buf,
117 ctx->challenge + 10, challenge_len - 10) < 0) {
118 git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server");
119 error = -1;
120 goto done;
121 }
122
123 input_token.value = input_buf.ptr;
124 input_token.length = input_buf.size;
125 input_token_ptr = &input_token;
126 } else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
127 git_error_set(GIT_ERROR_NET, "could not restart authentication");
128 error = -1;
129 goto done;
130 }
131
132 mech = &negotiate_oid_spnego;
133
134 if (GSS_ERROR(status_major = gss_init_sec_context(
135 &status_minor,
136 GSS_C_NO_CREDENTIAL,
137 &ctx->gss_context,
138 server,
139 mech,
140 GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
141 GSS_C_INDEFINITE,
142 GSS_C_NO_CHANNEL_BINDINGS,
143 input_token_ptr,
144 NULL,
145 &output_token,
146 NULL,
147 NULL))) {
148 negotiate_err_set(status_major, status_minor, "Negotiate failure");
149 error = -1;
150 goto done;
151 }
152
153 /* This message merely told us auth was complete; we do not respond. */
154 if (status_major == GSS_S_COMPLETE) {
155 ctx->complete = 1;
156 goto done;
157 }
158
159 git_buf_printf(buf, "%s: Negotiate ", header_name);
160 git_buf_encode_base64(buf, output_token.value, output_token.length);
161 git_buf_puts(buf, "\r\n");
162
163 if (git_buf_oom(buf))
164 error = -1;
165
166 done:
167 gss_release_name(&status_minor, &server);
168 gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
169 git_buf_dispose(&input_buf);
170 return error;
171 }
172
173 static void negotiate_context_free(git_http_auth_context *c)
174 {
175 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
176 OM_uint32 status_minor;
177
178 if (ctx->gss_context != GSS_C_NO_CONTEXT) {
179 gss_delete_sec_context(
180 &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER);
181 ctx->gss_context = GSS_C_NO_CONTEXT;
182 }
183
184 git_buf_dispose(&ctx->target);
185
186 git__free(ctx->challenge);
187
188 ctx->configured = 0;
189 ctx->complete = 0;
190 ctx->oid = NULL;
191
192 git__free(ctx);
193 }
194
195 static int negotiate_init_context(
196 http_auth_negotiate_context *ctx,
197 const gitno_connection_data *connection_data)
198 {
199 OM_uint32 status_major, status_minor;
200 gss_OID item, *oid;
201 gss_OID_set mechanism_list;
202 size_t i;
203
204 /* Query supported mechanisms looking for SPNEGO) */
205 if (GSS_ERROR(status_major =
206 gss_indicate_mechs(&status_minor, &mechanism_list))) {
207 negotiate_err_set(status_major, status_minor,
208 "could not query mechanisms");
209 return -1;
210 }
211
212 if (mechanism_list) {
213 for (oid = negotiate_oids; *oid; oid++) {
214 for (i = 0; i < mechanism_list->count; i++) {
215 item = &mechanism_list->elements[i];
216
217 if (item->length == (*oid)->length &&
218 memcmp(item->elements, (*oid)->elements, item->length) == 0) {
219 ctx->oid = *oid;
220 break;
221 }
222
223 }
224
225 if (ctx->oid)
226 break;
227 }
228 }
229
230 gss_release_oid_set(&status_minor, &mechanism_list);
231
232 if (!ctx->oid) {
233 git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported");
234 return -1;
235 }
236
237 git_buf_puts(&ctx->target, "HTTP@");
238 git_buf_puts(&ctx->target, connection_data->host);
239
240 if (git_buf_oom(&ctx->target))
241 return -1;
242
243 ctx->gss_context = GSS_C_NO_CONTEXT;
244 ctx->configured = 1;
245
246 return 0;
247 }
248
249 int git_http_auth_negotiate(
250 git_http_auth_context **out,
251 const gitno_connection_data *connection_data)
252 {
253 http_auth_negotiate_context *ctx;
254
255 *out = NULL;
256
257 ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
258 GIT_ERROR_CHECK_ALLOC(ctx);
259
260 if (negotiate_init_context(ctx, connection_data) < 0) {
261 git__free(ctx);
262 return -1;
263 }
264
265 ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE;
266 ctx->parent.credtypes = GIT_CREDTYPE_DEFAULT;
267 ctx->parent.set_challenge = negotiate_set_challenge;
268 ctx->parent.next_token = negotiate_next_token;
269 ctx->parent.free = negotiate_context_free;
270
271 *out = (git_http_auth_context *)ctx;
272
273 return 0;
274 }
275
276 #endif /* GIT_GSSAPI */
277