]>
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 ET |
13 | #include "buffer.h" |
14 | #include "auth.h" | |
22a2d3d5 | 15 | #include "git2/sys/credential.h" |
23135afa | 16 | |
22a2d3d5 UG |
17 | #ifdef GIT_GSSFRAMEWORK |
18 | #import <GSS/GSS.h> | |
19 | #elif defined(GIT_GSSAPI) | |
23135afa ET |
20 | #include <gssapi.h> |
21 | #include <krb5.h> | |
22a2d3d5 | 22 | #endif |
23135afa ET |
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) { | |
ac3d33df | 52 | git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)", |
23135afa ET |
53 | message, (int)buffer.length, (const char *)buffer.value, |
54 | status_major, status_minor); | |
55 | gss_release_buffer(&status_minor, &buffer); | |
56 | } else { | |
ac3d33df | 57 | git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)", |
23135afa ET |
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 | ||
c25aa7cd PP |
68 | GIT_ASSERT_ARG(ctx); |
69 | GIT_ASSERT_ARG(challenge); | |
70 | GIT_ASSERT(ctx->configured); | |
23135afa ET |
71 | |
72 | git__free(ctx->challenge); | |
73 | ||
74 | ctx->challenge = git__strdup(challenge); | |
ac3d33df | 75 | GIT_ERROR_CHECK_ALLOC(ctx->challenge); |
23135afa ET |
76 | |
77 | return 0; | |
78 | } | |
79 | ||
22a2d3d5 UG |
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 | ||
23135afa ET |
96 | static int negotiate_next_token( |
97 | git_buf *buf, | |
98 | git_http_auth_context *c, | |
22a2d3d5 | 99 | git_credential *cred) |
23135afa ET |
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 | ||
c25aa7cd PP |
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); | |
23135afa ET |
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, | |
22a2d3d5 | 131 | "could not parse principal"); |
23135afa ET |
132 | error = -1; |
133 | goto done; | |
134 | } | |
135 | ||
136 | challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; | |
137 | ||
22a2d3d5 UG |
138 | if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) { |
139 | git_error_set(GIT_ERROR_NET, "server did not request negotiate"); | |
23135afa ET |
140 | error = -1; |
141 | goto done; | |
22a2d3d5 UG |
142 | } |
143 | ||
144 | if (challenge_len > 9) { | |
23135afa ET |
145 | if (git_buf_decode_base64(&input_buf, |
146 | ctx->challenge + 10, challenge_len - 10) < 0) { | |
ac3d33df | 147 | git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server"); |
23135afa ET |
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) { | |
22a2d3d5 | 156 | negotiate_context_dispose(ctx); |
23135afa ET |
157 | } |
158 | ||
159 | mech = &negotiate_oid_spnego; | |
160 | ||
22a2d3d5 | 161 | status_major = gss_init_sec_context( |
23135afa ET |
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, | |
22a2d3d5 UG |
174 | NULL); |
175 | ||
176 | if (GSS_ERROR(status_major)) { | |
177 | negotiate_err_set(status_major, status_minor, "negotiate failure"); | |
23135afa ET |
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) { | |
22a2d3d5 | 184 | negotiate_context_dispose(ctx); |
23135afa ET |
185 | ctx->complete = 1; |
186 | goto done; | |
187 | } | |
188 | ||
22a2d3d5 UG |
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 "); | |
23135afa | 196 | git_buf_encode_base64(buf, output_token.value, output_token.length); |
23135afa ET |
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); | |
ac3d33df | 204 | git_buf_dispose(&input_buf); |
23135afa ET |
205 | return error; |
206 | } | |
207 | ||
22a2d3d5 | 208 | static int negotiate_is_complete(git_http_auth_context *c) |
23135afa ET |
209 | { |
210 | http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; | |
23135afa | 211 | |
c25aa7cd | 212 | GIT_ASSERT_ARG(ctx); |
23135afa | 213 | |
22a2d3d5 UG |
214 | return (ctx->complete == 1); |
215 | } | |
23135afa | 216 | |
22a2d3d5 UG |
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); | |
23135afa ET |
222 | |
223 | ctx->configured = 0; | |
224 | ctx->complete = 0; | |
225 | ctx->oid = NULL; | |
226 | ||
227 | git__free(ctx); | |
228 | } | |
229 | ||
230 | static int negotiate_init_context( | |
231 | http_auth_negotiate_context *ctx, | |
22a2d3d5 | 232 | const git_net_url *url) |
23135afa ET |
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) */ | |
22a2d3d5 UG |
240 | status_major = gss_indicate_mechs(&status_minor, &mechanism_list); |
241 | ||
242 | if (GSS_ERROR(status_major)) { | |
23135afa ET |
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) { | |
ac3d33df | 269 | git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported"); |
c25aa7cd | 270 | return GIT_EAUTH; |
23135afa ET |
271 | } |
272 | ||
273 | git_buf_puts(&ctx->target, "HTTP@"); | |
22a2d3d5 | 274 | git_buf_puts(&ctx->target, url->host); |
23135afa ET |
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 | ||
285 | int git_http_auth_negotiate( | |
286 | git_http_auth_context **out, | |
22a2d3d5 | 287 | const git_net_url *url) |
23135afa ET |
288 | { |
289 | http_auth_negotiate_context *ctx; | |
290 | ||
291 | *out = NULL; | |
292 | ||
293 | ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); | |
ac3d33df | 294 | GIT_ERROR_CHECK_ALLOC(ctx); |
23135afa | 295 | |
22a2d3d5 | 296 | if (negotiate_init_context(ctx, url) < 0) { |
23135afa ET |
297 | git__free(ctx); |
298 | return -1; | |
299 | } | |
300 | ||
22a2d3d5 UG |
301 | ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE; |
302 | ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT; | |
303 | ctx->parent.connection_affinity = 1; | |
23135afa ET |
304 | ctx->parent.set_challenge = negotiate_set_challenge; |
305 | ctx->parent.next_token = negotiate_next_token; | |
22a2d3d5 | 306 | ctx->parent.is_complete = negotiate_is_complete; |
23135afa ET |
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 |