2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
8 #include "auth_negotiate.h"
10 #if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK)
15 #include "git2/sys/credential.h"
17 #ifdef GIT_GSSFRAMEWORK
19 #elif defined(GIT_GSSAPI)
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" };
29 static gss_OID negotiate_oids
[] =
30 { &negotiate_oid_spnego
, &negotiate_oid_krb5
, NULL
};
33 git_http_auth_context parent
;
34 unsigned configured
: 1,
38 gss_ctx_id_t gss_context
;
40 } http_auth_negotiate_context
;
42 static void negotiate_err_set(
43 OM_uint32 status_major
,
44 OM_uint32 status_minor
,
47 gss_buffer_desc buffer
= GSS_C_EMPTY_BUFFER
;
48 OM_uint32 status_display
, context
= 0;
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
);
57 git_error_set(GIT_ERROR_NET
, "%s: unknown negotiate error (%d.%d)",
58 message
, status_major
, status_minor
);
62 static int negotiate_set_challenge(
63 git_http_auth_context
*c
,
64 const char *challenge
)
66 http_auth_negotiate_context
*ctx
= (http_auth_negotiate_context
*)c
;
68 assert(ctx
&& ctx
->configured
&& challenge
);
70 git__free(ctx
->challenge
);
72 ctx
->challenge
= git__strdup(challenge
);
73 GIT_ERROR_CHECK_ALLOC(ctx
->challenge
);
78 static void negotiate_context_dispose(http_auth_negotiate_context
*ctx
)
80 OM_uint32 status_minor
;
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
;
88 git_buf_dispose(&ctx
->target
);
90 git__free(ctx
->challenge
);
91 ctx
->challenge
= NULL
;
94 static int negotiate_next_token(
96 git_http_auth_context
*c
,
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
;
108 size_t challenge_len
;
111 assert(buf
&& ctx
&& ctx
->configured
&& cred
&& cred
->credtype
== GIT_CREDENTIAL_DEFAULT
);
116 target_buffer
.value
= (void *)ctx
->target
.ptr
;
117 target_buffer
.length
= ctx
->target
.size
;
119 status_major
= gss_import_name(&status_minor
, &target_buffer
,
120 GSS_C_NT_HOSTBASED_SERVICE
, &server
);
122 if (GSS_ERROR(status_major
)) {
123 negotiate_err_set(status_major
, status_minor
,
124 "could not parse principal");
129 challenge_len
= ctx
->challenge
? strlen(ctx
->challenge
) : 0;
131 if (challenge_len
< 9 || memcmp(ctx
->challenge
, "Negotiate", 9) != 0) {
132 git_error_set(GIT_ERROR_NET
, "server did not request negotiate");
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");
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
);
152 mech
= &negotiate_oid_spnego
;
154 status_major
= gss_init_sec_context(
160 GSS_C_DELEG_FLAG
| GSS_C_MUTUAL_FLAG
,
162 GSS_C_NO_CHANNEL_BINDINGS
,
169 if (GSS_ERROR(status_major
)) {
170 negotiate_err_set(status_major
, status_minor
, "negotiate failure");
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
);
182 if (output_token
.length
== 0) {
183 git_error_set(GIT_ERROR_NET
, "GSSAPI did not return token");
188 git_buf_puts(buf
, "Negotiate ");
189 git_buf_encode_base64(buf
, output_token
.value
, output_token
.length
);
191 if (git_buf_oom(buf
))
195 gss_release_name(&status_minor
, &server
);
196 gss_release_buffer(&status_minor
, (gss_buffer_t
) &output_token
);
197 git_buf_dispose(&input_buf
);
201 static int negotiate_is_complete(git_http_auth_context
*c
)
203 http_auth_negotiate_context
*ctx
= (http_auth_negotiate_context
*)c
;
207 return (ctx
->complete
== 1);
210 static void negotiate_context_free(git_http_auth_context
*c
)
212 http_auth_negotiate_context
*ctx
= (http_auth_negotiate_context
*)c
;
214 negotiate_context_dispose(ctx
);
223 static int negotiate_init_context(
224 http_auth_negotiate_context
*ctx
,
225 const git_net_url
*url
)
227 OM_uint32 status_major
, status_minor
;
229 gss_OID_set mechanism_list
;
232 /* Query supported mechanisms looking for SPNEGO) */
233 status_major
= gss_indicate_mechs(&status_minor
, &mechanism_list
);
235 if (GSS_ERROR(status_major
)) {
236 negotiate_err_set(status_major
, status_minor
,
237 "could not query mechanisms");
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
];
246 if (item
->length
== (*oid
)->length
&&
247 memcmp(item
->elements
, (*oid
)->elements
, item
->length
) == 0) {
259 gss_release_oid_set(&status_minor
, &mechanism_list
);
262 git_error_set(GIT_ERROR_NET
, "negotiate authentication is not supported");
266 git_buf_puts(&ctx
->target
, "HTTP@");
267 git_buf_puts(&ctx
->target
, url
->host
);
269 if (git_buf_oom(&ctx
->target
))
272 ctx
->gss_context
= GSS_C_NO_CONTEXT
;
278 int git_http_auth_negotiate(
279 git_http_auth_context
**out
,
280 const git_net_url
*url
)
282 http_auth_negotiate_context
*ctx
;
286 ctx
= git__calloc(1, sizeof(http_auth_negotiate_context
));
287 GIT_ERROR_CHECK_ALLOC(ctx
);
289 if (negotiate_init_context(ctx
, url
) < 0) {
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
;
302 *out
= (git_http_auth_context
*)ctx
;
307 #endif /* GIT_GSSAPI */