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