security: add Negotiate to GSSAPI acquire_cred()
[siplcs.git] / src / core / sip-sec-gssapi.c
blob3b15153e949db6c50b52e289b8bfcba1d3991935
1 /**
2 * @file sip-sec-gssapi.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2013 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * This module implements sip-sec authentication API using GSSAPI.
27 * It can be compiled in two different modes:
29 * - Kerberos-only: NTLM & SPNEGO are using SIPE internal implementation
30 * [HAVE_GSSAPI_ONLY is not defined]
32 * - pure GSSAPI: this modules handles Kerberos, NTLM & SPNEGO
33 * [HAVE_GSSAPI_ONLY is defined]
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #include <glib.h>
41 #include <string.h>
42 #include <gssapi/gssapi.h>
43 #ifdef HAVE_GSSAPI_PASSWORD_SUPPORT
44 #include <gssapi/gssapi_ext.h>
45 #endif
46 #include <gssapi/gssapi_krb5.h>
47 #ifdef HAVE_GSSAPI_ONLY
48 #include <gssapi/gssapi_ntlmssp.h>
49 #endif
51 #include "sipe-common.h"
52 #include "sip-sec.h"
53 #include "sip-sec-mech.h"
54 #include "sip-sec-gssapi.h"
55 #include "sipe-backend.h"
56 #include "sipe-core.h"
57 #include "sipe-utils.h"
59 /* Security context for Kerberos */
60 typedef struct _context_gssapi {
61 struct sip_sec_context common;
62 gss_cred_id_t cred_gssapi;
63 gss_ctx_id_t ctx_gssapi;
64 } *context_gssapi;
66 static void sip_sec_gssapi_print_gss_error0(char *func,
67 OM_uint32 status,
68 int type)
70 OM_uint32 minor;
71 OM_uint32 message_context = 0;
72 gss_buffer_desc status_string;
74 do {
75 gss_display_status(&minor,
76 status,
77 type,
78 GSS_C_NO_OID,
79 &message_context,
80 &status_string);
82 SIPE_DEBUG_ERROR("sip_sec_gssapi: GSSAPI error in %s (%s): %s",
83 func,
84 (type == GSS_C_GSS_CODE ? "GSS" : "Mech"),
85 (gchar *) status_string.value);
86 gss_release_buffer(&minor, &status_string);
87 } while (message_context != 0);
90 /* Prints out errors of GSSAPI function invocation */
91 static void sip_sec_gssapi_print_gss_error(char *func,
92 OM_uint32 ret,
93 OM_uint32 minor)
95 sip_sec_gssapi_print_gss_error0(func, ret, GSS_C_GSS_CODE);
96 sip_sec_gssapi_print_gss_error0(func, minor, GSS_C_MECH_CODE);
99 static gss_OID_set create_mechs_set(guint type)
101 OM_uint32 ret;
102 OM_uint32 minor;
103 gss_OID_set set = GSS_C_NO_OID_SET;
105 #ifdef HAVE_GSSAPI_ONLY
106 static const gss_OID_desc gss_mech_ntlmssp = {
107 GSS_NTLMSSP_OID_LENGTH,
108 GSS_NTLMSSP_OID_STRING
110 #endif
112 ret = gss_create_empty_oid_set(&minor, &set);
113 if (GSS_ERROR(ret)) {
114 sip_sec_gssapi_print_gss_error("gss_create_empty_oid_set", ret, minor);
115 SIPE_DEBUG_ERROR("create_mech_set: can't create mech set (ret=%d)", (int)ret);
116 return(GSS_C_NO_OID_SET);
119 #ifdef HAVE_GSSAPI_ONLY
120 if ((type == SIPE_AUTHENTICATION_TYPE_NEGOTIATE) ||
121 (type == SIPE_AUTHENTICATION_TYPE_KERBEROS)) {
122 #else
123 (void) type; /* keep compiler happy */
124 #endif
125 ret = gss_add_oid_set_member(&minor,
126 (gss_OID) gss_mech_krb5,
127 &set);
128 if (GSS_ERROR(ret)) {
129 sip_sec_gssapi_print_gss_error("gss_add_oid_set_member(krb5)", ret, minor);
130 SIPE_DEBUG_ERROR("create_mech_set: can't add Kerberos to mech set (ret=%d)", (int)ret);
131 gss_release_oid_set(&minor, &set);
132 return(GSS_C_NO_OID_SET);
134 #ifdef HAVE_GSSAPI_ONLY
137 if ((type == SIPE_AUTHENTICATION_TYPE_NEGOTIATE) ||
138 (type == SIPE_AUTHENTICATION_TYPE_NTLM)) {
139 ret = gss_add_oid_set_member(&minor,
140 (gss_OID) &gss_mech_ntlmssp,
141 &set);
142 if (GSS_ERROR(ret)) {
143 sip_sec_gssapi_print_gss_error("gss_add_oid_set_member(ntlmssp)", ret, minor);
144 SIPE_DEBUG_ERROR("create_mech_set: can't add NTLM to mech set (ret=%d)", (int)ret);
145 gss_release_oid_set(&minor, &set);
146 return(GSS_C_NO_OID_SET);
149 #endif
151 return(set);
154 /* sip-sec-mech.h API implementation for Kerberos/GSSAPI */
156 static gboolean
157 sip_sec_acquire_cred__gssapi(SipSecContext context,
158 const gchar *domain,
159 const gchar *username,
160 const gchar *password)
162 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_acquire_cred__gssapi: started");
164 /* With SSO we use the default credentials */
165 if ((context->flags & SIP_SEC_FLAG_COMMON_SSO) == 0) {
166 #ifdef HAVE_GSSAPI_PASSWORD_SUPPORT
167 gchar *username_new;
168 OM_uint32 ret;
169 OM_uint32 minor, minor_ignore;
170 gss_OID_set mechs_set;
171 gss_cred_id_t credentials;
172 gss_buffer_desc input_name_buffer;
173 gss_name_t user_name;
175 /* Without SSO we need user name and password */
176 if (!username || !password) {
177 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__gssapi: no valid authentication information provided");
178 return(FALSE);
181 mechs_set = create_mechs_set(context->type);
182 if (mechs_set == GSS_C_NO_OID_SET)
183 return(FALSE);
185 /* Construct user name to acquire credentials for */
186 if (!is_empty(domain)) {
187 /* User specified a domain */
188 gchar *realm = g_ascii_strup(domain, -1);
190 username_new = g_strdup_printf("%s@%s",
191 username,
192 realm);
193 g_free(realm);
195 } else if (strchr(username, '@')) {
196 /* No domain, username matches XXX@YYY */
197 gchar **user_realm = g_strsplit(username, "@", 2);
198 gchar *realm = g_ascii_strup(user_realm[1], -1);
201 * We should escape the "@" to generate a enterprise
202 * principal, i.e. XXX\@YYY
204 * But krb5 libraries currently don't support this:
206 * http://krbdev.mit.edu/rt/Ticket/Display.html?id=7729
208 * username_new = g_strdup_printf("%s\\@%s",
210 username_new = g_strdup_printf("%s@%s",
211 user_realm[0],
212 realm);
213 g_free(realm);
214 g_strfreev(user_realm);
215 } else {
216 /* Otherwise use username as is */
217 username_new = g_strdup(username);
219 SIPE_DEBUG_INFO("sip_sec_acquire_cred__gssapi: username '%s'",
220 username_new);
222 /* Import user name into GSS format */
223 input_name_buffer.value = (void *) username_new;
224 input_name_buffer.length = strlen(username_new) + 1;
226 ret = gss_import_name(&minor,
227 &input_name_buffer,
228 (gss_OID) GSS_C_NT_USER_NAME,
229 &user_name);
230 g_free(username_new);
232 if (GSS_ERROR(ret)) {
233 sip_sec_gssapi_print_gss_error("gss_import_name", ret, minor);
234 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to construct user name (ret=%d)", (int)ret);
235 gss_release_oid_set(&minor, &mechs_set);
236 return(FALSE);
239 /* Acquire user credentials with password */
240 input_name_buffer.value = (void *) password;
241 input_name_buffer.length = strlen(password) + 1;
242 ret = gss_acquire_cred_with_password(&minor,
243 user_name,
244 &input_name_buffer,
245 GSS_C_INDEFINITE,
246 mechs_set,
247 GSS_C_INITIATE,
248 &credentials,
249 NULL,
250 NULL);
251 gss_release_name(&minor_ignore, &user_name);
252 gss_release_oid_set(&minor, &mechs_set);
254 if (GSS_ERROR(ret)) {
255 sip_sec_gssapi_print_gss_error("gss_acquire_cred_with_password", ret, minor);
256 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to acquire credentials (ret=%d)", (int)ret);
257 return(FALSE);
258 } else {
259 ((context_gssapi) context)->cred_gssapi = credentials;
260 return(TRUE);
262 #else
264 * non-SSO support requires gss_acquire_cred_with_password()
265 * which is not available on older GSSAPI releases.
267 (void) domain; /* keep compiler happy */
268 (void) username; /* keep compiler happy */
269 (void) password; /* keep compiler happy */
270 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__gssapi: non-SSO mode not supported");
271 return(FALSE);
272 #endif
274 #ifdef HAVE_GSSAPI_ONLY
275 else {
276 OM_uint32 ret;
277 OM_uint32 minor;
278 gss_OID_set mechs_set;
279 gss_cred_id_t credentials;
281 mechs_set = create_mechs_set(context->type);
282 if (mechs_set == GSS_C_NO_OID_SET)
283 return(FALSE);
285 ret = gss_acquire_cred(&minor,
286 GSS_C_NO_NAME,
287 GSS_C_INDEFINITE,
288 mechs_set,
289 GSS_C_INITIATE,
290 &credentials,
291 NULL,
292 NULL);
293 gss_release_oid_set(&minor, &mechs_set);
295 if (GSS_ERROR(ret)) {
296 sip_sec_gssapi_print_gss_error("gss_acquire_cred", ret, minor);
297 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to acquire credentials (ret=%d)", (int)ret);
298 return(FALSE);
299 } else {
300 ((context_gssapi) context)->cred_gssapi = credentials;
301 return(TRUE);
304 #else
305 return(TRUE);
306 #endif
309 static gboolean
310 sip_sec_init_sec_context__gssapi(SipSecContext context,
311 SipSecBuffer in_buff,
312 SipSecBuffer *out_buff,
313 const gchar *service_name)
315 context_gssapi ctx = (context_gssapi) context;
316 OM_uint32 ret;
317 OM_uint32 minor, minor_ignore;
318 OM_uint32 expiry;
319 gss_buffer_desc input_token;
320 gss_buffer_desc output_token;
321 gss_name_t target_name;
323 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__gssapi: started");
326 * If authentication was already completed, then this mean a new
327 * authentication handshake has started on the existing connection.
328 * We must throw away the old context, because we need a new one.
330 if ((context->flags & SIP_SEC_FLAG_COMMON_READY) &&
331 (ctx->ctx_gssapi != GSS_C_NO_CONTEXT)) {
332 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__gssapi: dropping old context");
333 ret = gss_delete_sec_context(&minor,
334 &(ctx->ctx_gssapi),
335 GSS_C_NO_BUFFER);
336 if (GSS_ERROR(ret)) {
337 sip_sec_gssapi_print_gss_error("gss_delete_sec_context", ret, minor);
338 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__gssapi: failed to delete security context (ret=%d)", (int)ret);
340 ctx->ctx_gssapi = GSS_C_NO_CONTEXT;
341 context->flags &= ~SIP_SEC_FLAG_COMMON_READY;
344 /* Import service name to GSS */
345 input_token.value = (void *) service_name;
346 input_token.length = strlen(service_name) + 1;
348 ret = gss_import_name(&minor,
349 &input_token,
350 (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
351 &target_name);
352 if (GSS_ERROR(ret)) {
353 sip_sec_gssapi_print_gss_error("gss_import_name", ret, minor);
354 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__gssapi: failed to construct target name (ret=%d)", (int)ret);
355 return(FALSE);
358 /* Create context */
359 input_token.length = in_buff.length;
360 input_token.value = in_buff.value;
362 output_token.length = 0;
363 output_token.value = NULL;
365 ret = gss_init_sec_context(&minor,
366 ctx->cred_gssapi,
367 &(ctx->ctx_gssapi),
368 target_name,
369 (gss_OID) gss_mech_krb5,
370 GSS_C_INTEG_FLAG,
371 GSS_C_INDEFINITE,
372 GSS_C_NO_CHANNEL_BINDINGS,
373 &input_token,
374 NULL,
375 &output_token,
376 NULL,
377 &expiry);
378 gss_release_name(&minor_ignore, &target_name);
380 if (GSS_ERROR(ret)) {
381 gss_release_buffer(&minor_ignore, &output_token);
382 sip_sec_gssapi_print_gss_error("gss_init_sec_context", ret, minor);
383 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__gssapi: failed to initialize context (ret=%d)", (int)ret);
384 return(FALSE);
387 out_buff->length = output_token.length;
388 out_buff->value = g_memdup(output_token.value, output_token.length);
389 gss_release_buffer(&minor_ignore, &output_token);
391 context->expires = (int)expiry;
393 /* Authentication is completed */
394 context->flags |= SIP_SEC_FLAG_COMMON_READY;
396 return(TRUE);
400 * @param message a NULL terminated string to sign
402 static gboolean
403 sip_sec_make_signature__gssapi(SipSecContext context,
404 const gchar *message,
405 SipSecBuffer *signature)
407 OM_uint32 ret;
408 OM_uint32 minor;
409 gss_buffer_desc input_message;
410 gss_buffer_desc output_token;
412 input_message.value = (void *)message;
413 input_message.length = strlen(input_message.value);
415 ret = gss_get_mic(&minor,
416 ((context_gssapi)context)->ctx_gssapi,
417 GSS_C_QOP_DEFAULT,
418 &input_message,
419 &output_token);
421 if (GSS_ERROR(ret)) {
422 sip_sec_gssapi_print_gss_error("gss_get_mic", ret, minor);
423 SIPE_DEBUG_ERROR("sip_sec_make_signature__gssapi: failed to make signature (ret=%d)", (int)ret);
424 return FALSE;
425 } else {
426 signature->length = output_token.length;
427 signature->value = g_memdup(output_token.value,
428 output_token.length);
429 gss_release_buffer(&minor, &output_token);
430 return TRUE;
435 * @param message a NULL terminated string to check signature of
437 static gboolean
438 sip_sec_verify_signature__gssapi(SipSecContext context,
439 const gchar *message,
440 SipSecBuffer signature)
442 OM_uint32 ret;
443 OM_uint32 minor;
444 gss_buffer_desc input_message;
445 gss_buffer_desc input_token;
447 input_message.value = (void *)message;
448 input_message.length = strlen(input_message.value);
450 input_token.value = signature.value;
451 input_token.length = signature.length;
453 ret = gss_verify_mic(&minor,
454 ((context_gssapi)context)->ctx_gssapi,
455 &input_message,
456 &input_token,
457 NULL);
459 if (GSS_ERROR(ret)) {
460 sip_sec_gssapi_print_gss_error("gss_verify_mic", ret, minor);
461 SIPE_DEBUG_ERROR("sip_sec_verify_signature__gssapi: failed to make signature (ret=%d)", (int)ret);
462 return FALSE;
463 } else {
464 return TRUE;
468 static void
469 sip_sec_destroy_sec_context__gssapi(SipSecContext context)
471 context_gssapi ctx = (context_gssapi) context;
472 OM_uint32 ret;
473 OM_uint32 minor;
475 if (ctx->ctx_gssapi != GSS_C_NO_CONTEXT) {
476 ret = gss_delete_sec_context(&minor, &(ctx->ctx_gssapi), GSS_C_NO_BUFFER);
477 if (GSS_ERROR(ret)) {
478 sip_sec_gssapi_print_gss_error("gss_delete_sec_context", ret, minor);
479 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__gssapi: failed to delete security context (ret=%d)", (int)ret);
481 ctx->ctx_gssapi = GSS_C_NO_CONTEXT;
484 if (ctx->cred_gssapi != GSS_C_NO_CREDENTIAL) {
485 ret = gss_release_cred(&minor, &(ctx->cred_gssapi));
486 if (GSS_ERROR(ret)) {
487 sip_sec_gssapi_print_gss_error("gss_release_cred", ret, minor);
488 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__gssapi: failed to release credentials (ret=%d)", (int)ret);
490 ctx->cred_gssapi = GSS_C_NO_CREDENTIAL;
493 g_free(context);
496 static const gchar *
497 sip_sec_context_name__gssapi(SIPE_UNUSED_PARAMETER SipSecContext context)
499 return("Kerberos");
502 SipSecContext
503 sip_sec_create_context__gssapi(SIPE_UNUSED_PARAMETER guint type)
505 context_gssapi context = g_malloc0(sizeof(struct _context_gssapi));
506 if (!context) return(NULL);
508 context->common.acquire_cred_func = sip_sec_acquire_cred__gssapi;
509 context->common.init_context_func = sip_sec_init_sec_context__gssapi;
510 context->common.destroy_context_func = sip_sec_destroy_sec_context__gssapi;
511 context->common.make_signature_func = sip_sec_make_signature__gssapi;
512 context->common.verify_signature_func = sip_sec_verify_signature__gssapi;
513 context->common.context_name_func = sip_sec_context_name__gssapi;
515 context->cred_gssapi = GSS_C_NO_CREDENTIAL;
516 context->ctx_gssapi = GSS_C_NO_CONTEXT;
518 return((SipSecContext) context);
521 gboolean sip_sec_password__gssapi(void)
523 /* Kerberos supports Single-Sign On */
524 return(FALSE);
528 Local Variables:
529 mode: c
530 c-file-style: "bsd"
531 indent-tabs-mode: t
532 tab-width: 8
533 End: