2 * @file sip-sec-gssapi.c
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]
42 #include <gssapi/gssapi.h>
43 #ifdef HAVE_GSSAPI_PASSWORD_SUPPORT
44 #include <gssapi/gssapi_ext.h>
46 #include <gssapi/gssapi_krb5.h>
47 #ifdef HAVE_GSSAPI_ONLY
48 #include <gssapi/gssapi_ntlmssp.h>
51 #include "sipe-common.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
;
66 #ifdef HAVE_GSSAPI_ONLY
67 static const gss_OID_desc gss_mech_ntlmssp
= {
68 GSS_NTLMSSP_OID_LENGTH
,
69 GSS_NTLMSSP_OID_STRING
72 static const gss_OID_desc gss_mech_spnego
= {
74 "\x2b\x06\x01\x05\x05\x02"
78 #define SIP_SEC_FLAG_GSSAPI_SIP_NTLM 0x00010000
79 #define SIP_SEC_FLAG_GSSAPI_NEGOTIATE_FALLBACK 0x00020000
81 static void sip_sec_gssapi_print_gss_error0(char *func
,
86 OM_uint32 message_context
= 0;
87 gss_buffer_desc status_string
;
90 gss_display_status(&minor
,
97 SIPE_DEBUG_ERROR("sip_sec_gssapi: GSSAPI error in %s (%s): %s",
99 (type
== GSS_C_GSS_CODE
? "GSS" : "Mech"),
100 (gchar
*) status_string
.value
);
101 gss_release_buffer(&minor
, &status_string
);
102 } while (message_context
!= 0);
105 /* Prints out errors of GSSAPI function invocation */
106 static void sip_sec_gssapi_print_gss_error(char *func
,
110 sip_sec_gssapi_print_gss_error0(func
, ret
, GSS_C_GSS_CODE
);
111 sip_sec_gssapi_print_gss_error0(func
, minor
, GSS_C_MECH_CODE
);
114 #if defined(HAVE_GSSAPI_PASSWORD_SUPPORT) || defined(HAVE_GSSAPI_ONLY)
115 /* NOTE: releases "set" on error */
116 static gboolean
add_mech(gss_OID_set set
,
123 ret
= gss_add_oid_set_member(&minor
, mech
, &set
);
124 if (GSS_ERROR(ret
)) {
125 sip_sec_gssapi_print_gss_error("gss_add_oid_set_member", ret
, minor
);
126 SIPE_DEBUG_ERROR("add_mech: can't add %s to mech set (ret=%d)", name
, (int)ret
);
127 gss_release_oid_set(&minor
, &set
);
130 SIPE_DEBUG_INFO("add_mech: added %s to mech set", name
);
135 static gss_OID_set
create_mechs_set(guint type
)
139 gss_OID_set set
= GSS_C_NO_OID_SET
;
143 ret
= gss_create_empty_oid_set(&minor
, &set
);
144 if (GSS_ERROR(ret
)) {
145 sip_sec_gssapi_print_gss_error("gss_create_empty_oid_set", ret
, minor
);
146 SIPE_DEBUG_ERROR("create_mechs_set: can't create mech set (ret=%d)", (int)ret
);
147 return(GSS_C_NO_OID_SET
);
150 #ifdef HAVE_GSSAPI_ONLY
152 case SIPE_AUTHENTICATION_TYPE_NTLM
:
153 mech_oid
= (gss_OID
) &gss_mech_ntlmssp
;
157 case SIPE_AUTHENTICATION_TYPE_KERBEROS
:
159 (void) type
; /* keep compiler happy */
161 mech_oid
= (gss_OID
) gss_mech_krb5
;
163 #ifdef HAVE_GSSAPI_ONLY
166 case SIPE_AUTHENTICATION_TYPE_NEGOTIATE
:
167 mech_oid
= (gss_OID
) &gss_mech_spnego
;
172 SIPE_DEBUG_ERROR("create_mechs_set: invoked with invalid type %d",
174 gss_release_oid_set(&minor
, &set
);
175 return(GSS_C_NO_OID_SET
);
180 return(add_mech(set
, mech_oid
, name
) ? set
: GSS_C_NO_OID_SET
);
184 #ifdef HAVE_GSSAPI_ONLY
185 static gss_OID_set
create_neg_mechs_set(void)
189 gss_OID_set set
= GSS_C_NO_OID_SET
;
191 ret
= gss_create_empty_oid_set(&minor
, &set
);
192 if (GSS_ERROR(ret
)) {
193 sip_sec_gssapi_print_gss_error("gss_create_empty_oid_set", ret
, minor
);
194 SIPE_DEBUG_ERROR("create_neg_mechs_set: can't create mech set (ret=%d)", (int)ret
);
195 return(GSS_C_NO_OID_SET
);
198 return((add_mech(set
, (gss_OID
) gss_mech_krb5
, "Kerberos") &&
199 add_mech(set
, (gss_OID
) &gss_mech_ntlmssp
, "NTLM")) ?
200 set
: GSS_C_NO_OID_SET
);
203 static gboolean
gssntlm_reset_mic_sequence(context_gssapi context
)
207 gss_buffer_desc value
;
208 guint sequence
= 100;
210 static const gss_OID_desc set_sequence_num_oid
= {
211 GSS_NTLMSSP_SET_SEQ_NUM_OID_LENGTH
,
212 GSS_NTLMSSP_SET_SEQ_NUM_OID_STRING
215 value
.length
= sizeof(sequence
);
216 value
.value
= &sequence
;
218 ret
= gss_set_sec_context_option(&minor
,
219 &context
->ctx_gssapi
,
220 (gss_OID_desc
*) &set_sequence_num_oid
,
222 if (GSS_ERROR(ret
)) {
223 sip_sec_gssapi_print_gss_error("gss_set_sec_context_option", ret
, minor
);
224 SIPE_DEBUG_ERROR("gssntlm_reset_mic_sequence: failed to reset MIC sequence number (ret=%d)", (int)ret
);
232 static void drop_gssapi_context(SipSecContext context
)
234 context_gssapi ctx
= (context_gssapi
) context
;
238 ret
= gss_delete_sec_context(&minor
,
241 if (GSS_ERROR(ret
)) {
242 sip_sec_gssapi_print_gss_error("gss_delete_sec_context", ret
, minor
);
243 SIPE_DEBUG_ERROR("drop_gssapi_context: failed to delete security context (ret=%d)", (int)ret
);
245 ctx
->ctx_gssapi
= GSS_C_NO_CONTEXT
;
246 context
->flags
&= ~SIP_SEC_FLAG_COMMON_READY
;
249 /* sip-sec-mech.h API implementation for Kerberos/GSSAPI */
252 sip_sec_acquire_cred__gssapi(SipSecContext context
,
254 const gchar
*username
,
255 const gchar
*password
)
257 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_acquire_cred__gssapi: started");
259 /* this is the first time we are allowed to set private flags */
260 if (((context
->flags
& SIP_SEC_FLAG_COMMON_HTTP
) == 0) &&
261 (context
->type
== SIPE_AUTHENTICATION_TYPE_NTLM
))
262 context
->flags
|= SIP_SEC_FLAG_GSSAPI_SIP_NTLM
;
264 /* With SSO we use the default credentials */
265 if ((context
->flags
& SIP_SEC_FLAG_COMMON_SSO
) == 0) {
266 #ifdef HAVE_GSSAPI_PASSWORD_SUPPORT
269 OM_uint32 minor
, minor_ignore
;
270 gss_OID_set mechs_set
;
271 gss_cred_id_t credentials
;
272 gss_buffer_desc input_name_buffer
;
273 gss_name_t user_name
;
275 /* Without SSO we need user name and password */
276 if (!username
|| !password
) {
277 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__gssapi: no valid authentication information provided");
281 mechs_set
= create_mechs_set(context
->type
);
282 if (mechs_set
== GSS_C_NO_OID_SET
)
285 /* Construct user name to acquire credentials for */
286 if (!is_empty(domain
)) {
287 /* User specified a domain */
288 gchar
*realm
= g_ascii_strup(domain
, -1);
290 username_new
= g_strdup_printf("%s@%s",
295 } else if (strchr(username
, '@')) {
296 /* No domain, username matches XXX@YYY */
297 gchar
**user_realm
= g_strsplit(username
, "@", 2);
298 gchar
*realm
= g_ascii_strup(user_realm
[1], -1);
301 * We should escape the "@" to generate a enterprise
302 * principal, i.e. XXX\@YYY
304 * But krb5 libraries currently don't support this:
306 * http://krbdev.mit.edu/rt/Ticket/Display.html?id=7729
308 * username_new = g_strdup_printf("%s\\@%s",
310 username_new
= g_strdup_printf("%s@%s",
314 g_strfreev(user_realm
);
316 /* Otherwise use username as is */
317 username_new
= g_strdup(username
);
319 SIPE_DEBUG_INFO("sip_sec_acquire_cred__gssapi: username '%s'",
322 /* Import user name into GSS format */
323 input_name_buffer
.value
= (void *) username_new
;
324 input_name_buffer
.length
= strlen(username_new
) + 1;
326 ret
= gss_import_name(&minor
,
328 (gss_OID
) GSS_C_NT_USER_NAME
,
330 g_free(username_new
);
332 if (GSS_ERROR(ret
)) {
333 sip_sec_gssapi_print_gss_error("gss_import_name", ret
, minor
);
334 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to construct user name (ret=%d)", (int)ret
);
335 gss_release_oid_set(&minor
, &mechs_set
);
339 /* Acquire user credentials with password */
340 input_name_buffer
.value
= (void *) password
;
341 input_name_buffer
.length
= strlen(password
) + 1;
342 ret
= gss_acquire_cred_with_password(&minor
,
351 gss_release_name(&minor_ignore
, &user_name
);
352 gss_release_oid_set(&minor_ignore
, &mechs_set
);
354 if (GSS_ERROR(ret
)) {
355 sip_sec_gssapi_print_gss_error("gss_acquire_cred_with_password", ret
, minor
);
356 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to acquire credentials (ret=%d)", (int)ret
);
360 ((context_gssapi
) context
)->cred_gssapi
= credentials
;
362 #ifdef HAVE_GSSAPI_ONLY
363 if (context
->type
== SIPE_AUTHENTICATION_TYPE_NEGOTIATE
) {
364 mechs_set
= create_neg_mechs_set();
365 if (mechs_set
== GSS_C_NO_OID_SET
)
368 ret
= gss_set_neg_mechs(&minor
,
371 gss_release_oid_set(&minor_ignore
, &mechs_set
);
373 if (GSS_ERROR(ret
)) {
374 sip_sec_gssapi_print_gss_error("gss_set_neg_mechs", ret
, minor
);
375 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to set negotiate mechanisms (ret=%d)", (int)ret
);
383 * non-SSO support requires gss_acquire_cred_with_password()
384 * which is not available on older GSSAPI releases.
386 (void) domain
; /* keep compiler happy */
387 (void) username
; /* keep compiler happy */
388 (void) password
; /* keep compiler happy */
389 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__gssapi: non-SSO mode not supported");
393 #ifdef HAVE_GSSAPI_ONLY
396 OM_uint32 minor
, minor_ignore
;
397 gss_OID_set mechs_set
;
398 gss_cred_id_t credentials
;
400 mechs_set
= create_mechs_set(context
->type
);
401 if (mechs_set
== GSS_C_NO_OID_SET
)
404 ret
= gss_acquire_cred(&minor
,
412 gss_release_oid_set(&minor_ignore
, &mechs_set
);
414 if (GSS_ERROR(ret
)) {
415 sip_sec_gssapi_print_gss_error("gss_acquire_cred", ret
, minor
);
416 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to acquire credentials (ret=%d)", (int)ret
);
420 ((context_gssapi
) context
)->cred_gssapi
= credentials
;
422 if (context
->type
== SIPE_AUTHENTICATION_TYPE_NEGOTIATE
) {
423 mechs_set
= create_neg_mechs_set();
424 if (mechs_set
== GSS_C_NO_OID_SET
)
427 ret
= gss_set_neg_mechs(&minor
,
430 gss_release_oid_set(&minor_ignore
, &mechs_set
);
432 if (GSS_ERROR(ret
)) {
433 sip_sec_gssapi_print_gss_error("gss_set_neg_mechs", ret
, minor
);
434 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__gssapi: failed to set negotiate mechanisms (ret=%d)", (int)ret
);
445 sip_sec_init_sec_context__gssapi(SipSecContext context
,
446 SipSecBuffer in_buff
,
447 SipSecBuffer
*out_buff
,
448 const gchar
*service_name
)
450 context_gssapi ctx
= (context_gssapi
) context
;
452 OM_uint32 minor
, minor_ignore
;
454 OM_uint32 flags
= GSS_C_INTEG_FLAG
;
455 gss_OID name_oid
, mech_oid
;
456 gss_buffer_desc input_token
;
457 gss_buffer_desc output_token
;
458 gss_name_t target_name
;
459 #ifdef HAVE_GSSAPI_ONLY
460 gchar
*hostbased_service_name
= NULL
;
463 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__gssapi: started");
466 * If authentication was already completed, then this mean a new
467 * authentication handshake has started on the existing connection.
468 * We must throw away the old context, because we need a new one.
470 if ((context
->flags
& SIP_SEC_FLAG_COMMON_READY
) &&
471 (ctx
->ctx_gssapi
!= GSS_C_NO_CONTEXT
)) {
472 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__gssapi: dropping old context");
473 drop_gssapi_context(context
);
476 #ifdef HAVE_GSSAPI_ONLY
477 switch(context
->type
) {
478 case SIPE_AUTHENTICATION_TYPE_NTLM
:
479 name_oid
= (gss_OID
) GSS_C_NT_HOSTBASED_SERVICE
;
480 mech_oid
= (gss_OID
) &gss_mech_ntlmssp
;
481 input_token
.value
= (void *) service_name
;
482 if (context
->flags
& SIP_SEC_FLAG_GSSAPI_SIP_NTLM
)
483 flags
|= GSS_C_DATAGRAM_FLAG
;
486 case SIPE_AUTHENTICATION_TYPE_KERBEROS
:
488 name_oid
= (gss_OID
) GSS_KRB5_NT_PRINCIPAL_NAME
;
489 mech_oid
= (gss_OID
) gss_mech_krb5
;
490 input_token
.value
= (void *) service_name
;
491 #ifdef HAVE_GSSAPI_ONLY
494 case SIPE_AUTHENTICATION_TYPE_NEGOTIATE
: {
495 gchar
**type_service
;
498 * Some servers do not accept SPNEGO for Negotiate.
499 * If come back here with an existing security context
500 * and NULL input token we will fall back to NTLM
502 if (ctx
->ctx_gssapi
&& (in_buff
.value
== NULL
)) {
504 /* Only try this once */
505 if (context
->flags
& SIP_SEC_FLAG_GSSAPI_NEGOTIATE_FALLBACK
) {
506 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_init_sec_context__gssapi: SPNEGO-to-NTLM fallback failed");
510 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__gssapi: SPNEGO failed. Falling back to NTLM");
511 drop_gssapi_context(context
);
513 context
->flags
|= SIP_SEC_FLAG_GSSAPI_NEGOTIATE_FALLBACK
;
516 /* Convert to hostbased so NTLM fallback can work */
517 type_service
= g_strsplit(service_name
, "/", 2);
518 if (type_service
[1]) {
519 gchar
*type_lower
= g_ascii_strdown(type_service
[0], -1);
520 hostbased_service_name
= g_strdup_printf("%s@%s",
524 input_token
.value
= (void *) hostbased_service_name
;
526 input_token
.value
= (void *) service_name
;
528 g_strfreev(type_service
);
530 name_oid
= (gss_OID
) GSS_C_NT_HOSTBASED_SERVICE
;
531 if (context
->flags
& SIP_SEC_FLAG_GSSAPI_NEGOTIATE_FALLBACK
) {
532 mech_oid
= (gss_OID
) &gss_mech_ntlmssp
;
534 mech_oid
= (gss_OID
) &gss_mech_spnego
;
535 flags
|= GSS_C_MUTUAL_FLAG
;
541 SIPE_DEBUG_ERROR("sip_sec_gssapi_initialize_context invoked for invalid type %d",
547 /* Import service name to GSS */
548 input_token
.length
= strlen(input_token
.value
) + 1;
550 ret
= gss_import_name(&minor
,
555 #ifdef HAVE_GSSAPI_ONLY
556 g_free(hostbased_service_name
);
559 if (GSS_ERROR(ret
)) {
560 sip_sec_gssapi_print_gss_error("gss_import_name", ret
, minor
);
561 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__gssapi: failed to construct target name (ret=%d)", (int)ret
);
566 input_token
.length
= in_buff
.length
;
567 input_token
.value
= in_buff
.value
;
569 output_token
.length
= 0;
570 output_token
.value
= NULL
;
572 ret
= gss_init_sec_context(&minor
,
579 GSS_C_NO_CHANNEL_BINDINGS
,
585 gss_release_name(&minor_ignore
, &target_name
);
587 if (GSS_ERROR(ret
)) {
588 gss_release_buffer(&minor_ignore
, &output_token
);
589 sip_sec_gssapi_print_gss_error("gss_init_sec_context", ret
, minor
);
590 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__gssapi: failed to initialize context (ret=%d)", (int)ret
);
594 out_buff
->length
= output_token
.length
;
595 if (out_buff
->length
)
596 out_buff
->value
= g_memdup(output_token
.value
, output_token
.length
);
598 /* Special case: empty token */
599 out_buff
->value
= (guint8
*) g_strdup("");
601 gss_release_buffer(&minor_ignore
, &output_token
);
603 context
->expires
= (int)expiry
;
605 if (ret
== GSS_S_COMPLETE
) {
606 /* Authentication is completed */
607 context
->flags
|= SIP_SEC_FLAG_COMMON_READY
;
609 #ifdef HAVE_GSSAPI_ONLY
610 if ((context
->flags
& SIP_SEC_FLAG_GSSAPI_SIP_NTLM
) &&
611 !gssntlm_reset_mic_sequence(ctx
))
620 * @param message a NULL terminated string to sign
623 sip_sec_make_signature__gssapi(SipSecContext context
,
624 const gchar
*message
,
625 SipSecBuffer
*signature
)
629 gss_buffer_desc input_message
;
630 gss_buffer_desc output_token
;
632 input_message
.value
= (void *)message
;
633 input_message
.length
= strlen(input_message
.value
);
635 ret
= gss_get_mic(&minor
,
636 ((context_gssapi
)context
)->ctx_gssapi
,
641 if (GSS_ERROR(ret
)) {
642 sip_sec_gssapi_print_gss_error("gss_get_mic", ret
, minor
);
643 SIPE_DEBUG_ERROR("sip_sec_make_signature__gssapi: failed to make signature (ret=%d)", (int)ret
);
646 signature
->length
= output_token
.length
;
647 signature
->value
= g_memdup(output_token
.value
,
648 output_token
.length
);
649 gss_release_buffer(&minor
, &output_token
);
655 * @param message a NULL terminated string to check signature of
658 sip_sec_verify_signature__gssapi(SipSecContext context
,
659 const gchar
*message
,
660 SipSecBuffer signature
)
664 gss_buffer_desc input_message
;
665 gss_buffer_desc input_token
;
667 input_message
.value
= (void *)message
;
668 input_message
.length
= strlen(input_message
.value
);
670 input_token
.value
= signature
.value
;
671 input_token
.length
= signature
.length
;
673 ret
= gss_verify_mic(&minor
,
674 ((context_gssapi
)context
)->ctx_gssapi
,
679 if (GSS_ERROR(ret
)) {
680 sip_sec_gssapi_print_gss_error("gss_verify_mic", ret
, minor
);
681 SIPE_DEBUG_ERROR("sip_sec_verify_signature__gssapi: failed to make signature (ret=%d)", (int)ret
);
689 sip_sec_destroy_sec_context__gssapi(SipSecContext context
)
691 context_gssapi ctx
= (context_gssapi
) context
;
695 if (ctx
->ctx_gssapi
!= GSS_C_NO_CONTEXT
)
696 drop_gssapi_context(context
);
698 if (ctx
->cred_gssapi
!= GSS_C_NO_CREDENTIAL
) {
699 ret
= gss_release_cred(&minor
, &(ctx
->cred_gssapi
));
700 if (GSS_ERROR(ret
)) {
701 sip_sec_gssapi_print_gss_error("gss_release_cred", ret
, minor
);
702 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__gssapi: failed to release credentials (ret=%d)", (int)ret
);
704 ctx
->cred_gssapi
= GSS_C_NO_CREDENTIAL
;
711 sip_sec_context_name__gssapi(SipSecContext context
)
713 const gchar
*name
= "Kerberos";
715 #ifdef HAVE_GSSAPI_ONLY
716 switch(context
->type
) {
717 case SIPE_AUTHENTICATION_TYPE_NTLM
:
721 case SIPE_AUTHENTICATION_TYPE_NEGOTIATE
:
722 if (context
->flags
& SIP_SEC_FLAG_GSSAPI_NEGOTIATE_FALLBACK
)
731 #ifdef HAVE_GSSAPI_ONLY
735 (void) context
; /* keep compiler happy */
741 sip_sec_create_context__gssapi(SIPE_UNUSED_PARAMETER guint type
)
743 context_gssapi context
= g_malloc0(sizeof(struct _context_gssapi
));
744 if (!context
) return(NULL
);
746 context
->common
.acquire_cred_func
= sip_sec_acquire_cred__gssapi
;
747 context
->common
.init_context_func
= sip_sec_init_sec_context__gssapi
;
748 context
->common
.destroy_context_func
= sip_sec_destroy_sec_context__gssapi
;
749 context
->common
.make_signature_func
= sip_sec_make_signature__gssapi
;
750 context
->common
.verify_signature_func
= sip_sec_verify_signature__gssapi
;
751 context
->common
.context_name_func
= sip_sec_context_name__gssapi
;
753 context
->cred_gssapi
= GSS_C_NO_CREDENTIAL
;
754 context
->ctx_gssapi
= GSS_C_NO_CONTEXT
;
756 return((SipSecContext
) context
);
759 gboolean
sip_sec_password__gssapi(void)
761 /* Kerberos supports Single-Sign On */