security: replace SPNEGO with internal negotiate
[siplcs.git] / src / core / sip-sec-gssapi.c
blob1365e68ddbb85a01af3ff144e817b7566399f92f
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
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 #ifdef HAVE_GSSAPI_ONLY
67 static const gss_OID_desc gss_mech_ntlmssp = {
68 GSS_NTLMSSP_OID_LENGTH,
69 GSS_NTLMSSP_OID_STRING
71 #endif
73 #define SIP_SEC_FLAG_GSSAPI_SIP_NTLM 0x00010000
75 static void sip_sec_gssapi_print_gss_error0(char *func,
76 OM_uint32 status,
77 int type)
79 OM_uint32 minor;
80 OM_uint32 message_context = 0;
81 gss_buffer_desc status_string;
83 do {
84 gss_display_status(&minor,
85 status,
86 type,
87 GSS_C_NO_OID,
88 &message_context,
89 &status_string);
91 SIPE_DEBUG_ERROR("sip_sec_gssapi: GSSAPI error in %s (%s): %s",
92 func,
93 (type == GSS_C_GSS_CODE ? "GSS" : "Mech"),
94 (gchar *) status_string.value);
95 gss_release_buffer(&minor, &status_string);
96 } while (message_context != 0);
99 /* Prints out errors of GSSAPI function invocation */
100 static void sip_sec_gssapi_print_gss_error(char *func,
101 OM_uint32 ret,
102 OM_uint32 minor)
104 sip_sec_gssapi_print_gss_error0(func, ret, GSS_C_GSS_CODE);
105 sip_sec_gssapi_print_gss_error0(func, minor, GSS_C_MECH_CODE);
108 #if defined(HAVE_GSSAPI_PASSWORD_SUPPORT) || defined(HAVE_GSSAPI_ONLY)
109 static gss_OID_set create_mechs_set(guint type)
111 OM_uint32 ret;
112 OM_uint32 minor;
113 gss_OID_set set = GSS_C_NO_OID_SET;
115 ret = gss_create_empty_oid_set(&minor, &set);
116 if (GSS_ERROR(ret)) {
117 sip_sec_gssapi_print_gss_error("gss_create_empty_oid_set", ret, minor);
118 SIPE_DEBUG_ERROR("create_mech_set: can't create mech set (ret=%d)", (int)ret);
119 return(GSS_C_NO_OID_SET);
122 #ifdef HAVE_GSSAPI_ONLY
123 if (type == SIPE_AUTHENTICATION_TYPE_KERBEROS) {
124 #else
125 (void) type; /* keep compiler happy */
126 #endif
127 ret = gss_add_oid_set_member(&minor,
128 (gss_OID) gss_mech_krb5,
129 &set);
130 if (GSS_ERROR(ret)) {
131 sip_sec_gssapi_print_gss_error("gss_add_oid_set_member(krb5)", ret, minor);
132 SIPE_DEBUG_ERROR("create_mech_set: can't add Kerberos to mech set (ret=%d)", (int)ret);
133 gss_release_oid_set(&minor, &set);
134 return(GSS_C_NO_OID_SET);
136 #ifdef HAVE_GSSAPI_ONLY
139 if (type == SIPE_AUTHENTICATION_TYPE_NTLM) {
140 ret = gss_add_oid_set_member(&minor,
141 (gss_OID) &gss_mech_ntlmssp,
142 &set);
143 if (GSS_ERROR(ret)) {
144 sip_sec_gssapi_print_gss_error("gss_add_oid_set_member(ntlmssp)", ret, minor);
145 SIPE_DEBUG_ERROR("create_mech_set: can't add NTLM to mech set (ret=%d)", (int)ret);
146 gss_release_oid_set(&minor, &set);
147 return(GSS_C_NO_OID_SET);
150 #endif
152 return(set);
154 #endif
156 #ifdef HAVE_GSSAPI_ONLY
157 static gboolean gssntlm_reset_mic_sequence(context_gssapi context)
159 OM_uint32 ret;
160 OM_uint32 minor;
161 gss_buffer_desc value;
162 guint sequence = 100;
164 static const gss_OID_desc set_sequence_num_oid = {
165 GSS_NTLMSSP_SET_SEQ_NUM_OID_LENGTH,
166 GSS_NTLMSSP_SET_SEQ_NUM_OID_STRING
169 value.length = sizeof(sequence);
170 value.value = &sequence;
172 ret = gss_set_sec_context_option(&minor,
173 &context->ctx_gssapi,
174 (gss_OID_desc *) &set_sequence_num_oid,
175 &value);
176 if (GSS_ERROR(ret)) {
177 sip_sec_gssapi_print_gss_error("gss_set_sec_context_option", ret, minor);
178 SIPE_DEBUG_ERROR("gssntlm_reset_mic_sequence: failed to reset MIC sequence number (ret=%d)", (int)ret);
179 return(FALSE);
182 return(TRUE);
184 #endif
186 static void drop_gssapi_context(SipSecContext context)
188 context_gssapi ctx = (context_gssapi) context;
189 OM_uint32 ret;
190 OM_uint32 minor;
192 ret = gss_delete_sec_context(&minor,
193 &(ctx->ctx_gssapi),
194 GSS_C_NO_BUFFER);
195 if (GSS_ERROR(ret)) {
196 sip_sec_gssapi_print_gss_error("gss_delete_sec_context", ret, minor);
197 SIPE_DEBUG_ERROR("drop_gssapi_context: failed to delete security context (ret=%d)", (int)ret);
199 ctx->ctx_gssapi = GSS_C_NO_CONTEXT;
200 context->flags &= ~SIP_SEC_FLAG_COMMON_READY;
203 /* sip-sec-mech.h API implementation for Kerberos/GSSAPI */
205 static gboolean
206 sip_sec_acquire_cred__gssapi(SipSecContext context,
207 const gchar *domain,
208 const gchar *username,
209 const gchar *password)
211 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_acquire_cred__gssapi: started");
213 /* this is the first time we are allowed to set private flags */
214 if (((context->flags & SIP_SEC_FLAG_COMMON_HTTP) == 0) &&
215 (context->type == SIPE_AUTHENTICATION_TYPE_NTLM))
216 context->flags |= SIP_SEC_FLAG_GSSAPI_SIP_NTLM;
218 /* With SSO we use the default credentials */
219 if ((context->flags & SIP_SEC_FLAG_COMMON_SSO) == 0) {
220 #ifdef HAVE_GSSAPI_PASSWORD_SUPPORT
221 gchar *username_new;
222 OM_uint32 ret;
223 OM_uint32 minor, minor_ignore;
224 gss_OID_set mechs_set;
225 gss_cred_id_t credentials;
226 gss_buffer_desc input_name_buffer;
227 gss_name_t user_name;
229 /* Without SSO we need user name and password */
230 if (!username || !password) {
231 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__gssapi: no valid authentication information provided");
232 return(FALSE);
235 mechs_set = create_mechs_set(context->type);
236 if (mechs_set == GSS_C_NO_OID_SET)
237 return(FALSE);
239 /* Construct user name to acquire credentials for */
240 if (!is_empty(domain)) {
241 /* User specified a domain */
242 gchar *realm = g_ascii_strup(domain, -1);
244 username_new = g_strdup_printf("%s@%s",
245 username,
246 realm);
247 g_free(realm);
249 } else if (strchr(username, '@')) {
250 /* No domain, username matches XXX@YYY */
251 gchar **user_realm = g_strsplit(username, "@", 2);
252 gchar *realm = g_ascii_strup(user_realm[1], -1);
255 * We should escape the "@" to generate a enterprise
256 * principal, i.e. XXX\@YYY
258 * But krb5 libraries currently don't support this:
260 * http://krbdev.mit.edu/rt/Ticket/Display.html?id=7729
262 * username_new = g_strdup_printf("%s\\@%s",
264 username_new = g_strdup_printf("%s@%s",
265 user_realm[0],
266 realm);
267 g_free(realm);
268 g_strfreev(user_realm);
269 } else {
270 /* Otherwise use username as is */
271 username_new = g_strdup(username);
273 SIPE_DEBUG_INFO("sip_sec_acquire_cred__gssapi: username '%s'",
274 username_new);
276 /* Import user name into GSS format */
277 input_name_buffer.value = (void *) username_new;
278 input_name_buffer.length = strlen(username_new) + 1;
280 ret = gss_import_name(&minor,
281 &input_name_buffer,
282 (gss_OID) GSS_C_NT_USER_NAME,
283 &user_name);
284 g_free(username_new);
286 if (GSS_ERROR(ret)) {
287 sip_sec_gssapi_print_gss_error("gss_import_name", ret, minor);
288 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to construct user name (ret=%d)", (int)ret);
289 gss_release_oid_set(&minor, &mechs_set);
290 return(FALSE);
293 /* Acquire user credentials with password */
294 input_name_buffer.value = (void *) password;
295 input_name_buffer.length = strlen(password) + 1;
296 ret = gss_acquire_cred_with_password(&minor,
297 user_name,
298 &input_name_buffer,
299 GSS_C_INDEFINITE,
300 mechs_set,
301 GSS_C_INITIATE,
302 &credentials,
303 NULL,
304 NULL);
305 gss_release_name(&minor_ignore, &user_name);
306 gss_release_oid_set(&minor, &mechs_set);
308 if (GSS_ERROR(ret)) {
309 sip_sec_gssapi_print_gss_error("gss_acquire_cred_with_password", ret, minor);
310 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to acquire credentials (ret=%d)", (int)ret);
311 return(FALSE);
312 } else {
313 ((context_gssapi) context)->cred_gssapi = credentials;
314 return(TRUE);
316 #else
318 * non-SSO support requires gss_acquire_cred_with_password()
319 * which is not available on older GSSAPI releases.
321 (void) domain; /* keep compiler happy */
322 (void) username; /* keep compiler happy */
323 (void) password; /* keep compiler happy */
324 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__gssapi: non-SSO mode not supported");
325 return(FALSE);
326 #endif
328 #ifdef HAVE_GSSAPI_ONLY
329 else {
330 OM_uint32 ret;
331 OM_uint32 minor;
332 gss_OID_set mechs_set;
333 gss_cred_id_t credentials;
335 mechs_set = create_mechs_set(context->type);
336 if (mechs_set == GSS_C_NO_OID_SET)
337 return(FALSE);
339 ret = gss_acquire_cred(&minor,
340 GSS_C_NO_NAME,
341 GSS_C_INDEFINITE,
342 mechs_set,
343 GSS_C_INITIATE,
344 &credentials,
345 NULL,
346 NULL);
347 gss_release_oid_set(&minor, &mechs_set);
349 if (GSS_ERROR(ret)) {
350 sip_sec_gssapi_print_gss_error("gss_acquire_cred", ret, minor);
351 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to acquire credentials (ret=%d)", (int)ret);
352 return(FALSE);
353 } else {
354 ((context_gssapi) context)->cred_gssapi = credentials;
355 return(TRUE);
358 #else
359 return(TRUE);
360 #endif
363 static gboolean
364 sip_sec_init_sec_context__gssapi(SipSecContext context,
365 SipSecBuffer in_buff,
366 SipSecBuffer *out_buff,
367 const gchar *service_name)
369 context_gssapi ctx = (context_gssapi) context;
370 OM_uint32 ret;
371 OM_uint32 minor, minor_ignore;
372 OM_uint32 expiry;
373 OM_uint32 flags = GSS_C_INTEG_FLAG;
374 gss_OID name_oid, mech_oid;
375 gss_buffer_desc input_token;
376 gss_buffer_desc output_token;
377 gss_name_t target_name;
379 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__gssapi: started");
382 * If authentication was already completed, then this mean a new
383 * authentication handshake has started on the existing connection.
384 * We must throw away the old context, because we need a new one.
386 if ((context->flags & SIP_SEC_FLAG_COMMON_READY) &&
387 (ctx->ctx_gssapi != GSS_C_NO_CONTEXT)) {
388 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__gssapi: dropping old context");
389 drop_gssapi_context(context);
392 #ifdef HAVE_GSSAPI_ONLY
393 switch(context->type) {
394 case SIPE_AUTHENTICATION_TYPE_NTLM:
395 name_oid = (gss_OID) GSS_C_NT_HOSTBASED_SERVICE;
396 mech_oid = (gss_OID) &gss_mech_ntlmssp;
397 if (context->flags & SIP_SEC_FLAG_GSSAPI_SIP_NTLM)
398 flags |= GSS_C_DATAGRAM_FLAG;
399 break;
401 case SIPE_AUTHENTICATION_TYPE_KERBEROS:
402 #endif
403 name_oid = (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME;
404 mech_oid = (gss_OID) gss_mech_krb5;
405 #ifdef HAVE_GSSAPI_ONLY
406 break;
408 default:
409 SIPE_DEBUG_ERROR("sip_sec_gssapi_initialize_context invoked for invalid type %d",
410 context->type);
411 return(FALSE);
413 #endif
415 /* Import service name to GSS */
416 input_token.value = (void *) service_name;
417 input_token.length = strlen(input_token.value) + 1;
419 ret = gss_import_name(&minor,
420 &input_token,
421 name_oid,
422 &target_name);
424 if (GSS_ERROR(ret)) {
425 sip_sec_gssapi_print_gss_error("gss_import_name", ret, minor);
426 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__gssapi: failed to construct target name (ret=%d)", (int)ret);
427 return(FALSE);
430 /* Create context */
431 input_token.length = in_buff.length;
432 input_token.value = in_buff.value;
434 output_token.length = 0;
435 output_token.value = NULL;
437 ret = gss_init_sec_context(&minor,
438 ctx->cred_gssapi,
439 &(ctx->ctx_gssapi),
440 target_name,
441 mech_oid,
442 flags,
443 GSS_C_INDEFINITE,
444 GSS_C_NO_CHANNEL_BINDINGS,
445 &input_token,
446 NULL,
447 &output_token,
448 NULL,
449 &expiry);
450 gss_release_name(&minor_ignore, &target_name);
452 if (GSS_ERROR(ret)) {
453 gss_release_buffer(&minor_ignore, &output_token);
454 sip_sec_gssapi_print_gss_error("gss_init_sec_context", ret, minor);
455 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__gssapi: failed to initialize context (ret=%d)", (int)ret);
456 return(FALSE);
459 out_buff->length = output_token.length;
460 if (out_buff->length)
461 out_buff->value = g_memdup(output_token.value, output_token.length);
462 else
463 /* Special case: empty token */
464 out_buff->value = (guint8 *) g_strdup("");
466 gss_release_buffer(&minor_ignore, &output_token);
468 context->expires = (int)expiry;
470 if (ret == GSS_S_COMPLETE) {
471 /* Authentication is completed */
472 context->flags |= SIP_SEC_FLAG_COMMON_READY;
474 #ifdef HAVE_GSSAPI_ONLY
475 if ((context->flags & SIP_SEC_FLAG_GSSAPI_SIP_NTLM) &&
476 !gssntlm_reset_mic_sequence(ctx))
477 return(FALSE);
478 #endif
481 return(TRUE);
485 * @param message a NULL terminated string to sign
487 static gboolean
488 sip_sec_make_signature__gssapi(SipSecContext context,
489 const gchar *message,
490 SipSecBuffer *signature)
492 OM_uint32 ret;
493 OM_uint32 minor;
494 gss_buffer_desc input_message;
495 gss_buffer_desc output_token;
497 input_message.value = (void *)message;
498 input_message.length = strlen(input_message.value);
500 ret = gss_get_mic(&minor,
501 ((context_gssapi)context)->ctx_gssapi,
502 GSS_C_QOP_DEFAULT,
503 &input_message,
504 &output_token);
506 if (GSS_ERROR(ret)) {
507 sip_sec_gssapi_print_gss_error("gss_get_mic", ret, minor);
508 SIPE_DEBUG_ERROR("sip_sec_make_signature__gssapi: failed to make signature (ret=%d)", (int)ret);
509 return FALSE;
510 } else {
511 signature->length = output_token.length;
512 signature->value = g_memdup(output_token.value,
513 output_token.length);
514 gss_release_buffer(&minor, &output_token);
515 return TRUE;
520 * @param message a NULL terminated string to check signature of
522 static gboolean
523 sip_sec_verify_signature__gssapi(SipSecContext context,
524 const gchar *message,
525 SipSecBuffer signature)
527 OM_uint32 ret;
528 OM_uint32 minor;
529 gss_buffer_desc input_message;
530 gss_buffer_desc input_token;
532 input_message.value = (void *)message;
533 input_message.length = strlen(input_message.value);
535 input_token.value = signature.value;
536 input_token.length = signature.length;
538 ret = gss_verify_mic(&minor,
539 ((context_gssapi)context)->ctx_gssapi,
540 &input_message,
541 &input_token,
542 NULL);
544 if (GSS_ERROR(ret)) {
545 sip_sec_gssapi_print_gss_error("gss_verify_mic", ret, minor);
546 SIPE_DEBUG_ERROR("sip_sec_verify_signature__gssapi: failed to make signature (ret=%d)", (int)ret);
547 return FALSE;
548 } else {
549 return TRUE;
553 static void
554 sip_sec_destroy_sec_context__gssapi(SipSecContext context)
556 context_gssapi ctx = (context_gssapi) context;
557 OM_uint32 ret;
558 OM_uint32 minor;
560 if (ctx->ctx_gssapi != GSS_C_NO_CONTEXT)
561 drop_gssapi_context(context);
563 if (ctx->cred_gssapi != GSS_C_NO_CREDENTIAL) {
564 ret = gss_release_cred(&minor, &(ctx->cred_gssapi));
565 if (GSS_ERROR(ret)) {
566 sip_sec_gssapi_print_gss_error("gss_release_cred", ret, minor);
567 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__gssapi: failed to release credentials (ret=%d)", (int)ret);
569 ctx->cred_gssapi = GSS_C_NO_CREDENTIAL;
572 g_free(context);
575 static const gchar *
576 sip_sec_context_name__gssapi(SipSecContext context)
578 #ifndef HAVE_GSSAPI_ONLY
579 (void) context; /* keep compiler happy */
580 #else
581 if (context->type == SIPE_AUTHENTICATION_TYPE_NTLM)
582 return("NTLM");
583 else
584 #endif
585 return("Kerberos");
588 SipSecContext
589 sip_sec_create_context__gssapi(SIPE_UNUSED_PARAMETER guint type)
591 context_gssapi context = g_malloc0(sizeof(struct _context_gssapi));
592 if (!context) return(NULL);
594 context->common.acquire_cred_func = sip_sec_acquire_cred__gssapi;
595 context->common.init_context_func = sip_sec_init_sec_context__gssapi;
596 context->common.destroy_context_func = sip_sec_destroy_sec_context__gssapi;
597 context->common.make_signature_func = sip_sec_make_signature__gssapi;
598 context->common.verify_signature_func = sip_sec_verify_signature__gssapi;
599 context->common.context_name_func = sip_sec_context_name__gssapi;
601 context->cred_gssapi = GSS_C_NO_CREDENTIAL;
602 context->ctx_gssapi = GSS_C_NO_CONTEXT;
604 return((SipSecContext) context);
607 gboolean sip_sec_password__gssapi(void)
609 /* Kerberos supports Single-Sign On */
610 return(FALSE);
614 Local Variables:
615 mode: c
616 c-file-style: "bsd"
617 indent-tabs-mode: t
618 tab-width: 8
619 End: