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
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
73 #define SIP_SEC_FLAG_GSSAPI_SIP_NTLM 0x00010000
75 static void sip_sec_gssapi_print_gss_error0(char *func
,
80 OM_uint32 message_context
= 0;
81 gss_buffer_desc status_string
;
84 gss_display_status(&minor
,
91 SIPE_DEBUG_ERROR("sip_sec_gssapi: GSSAPI error in %s (%s): %s",
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
,
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
)
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
) {
125 (void) type
; /* keep compiler happy */
127 ret
= gss_add_oid_set_member(&minor
,
128 (gss_OID
) gss_mech_krb5
,
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
,
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
);
156 #ifdef HAVE_GSSAPI_ONLY
157 static gboolean
gssntlm_reset_mic_sequence(context_gssapi context
)
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
,
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
);
186 static void drop_gssapi_context(SipSecContext context
)
188 context_gssapi ctx
= (context_gssapi
) context
;
192 ret
= gss_delete_sec_context(&minor
,
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 */
206 sip_sec_acquire_cred__gssapi(SipSecContext context
,
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
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");
235 mechs_set
= create_mechs_set(context
->type
);
236 if (mechs_set
== GSS_C_NO_OID_SET
)
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",
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",
268 g_strfreev(user_realm
);
270 /* Otherwise use username as is */
271 username_new
= g_strdup(username
);
273 SIPE_DEBUG_INFO("sip_sec_acquire_cred__gssapi: username '%s'",
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
,
282 (gss_OID
) GSS_C_NT_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
);
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
,
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
);
313 ((context_gssapi
) context
)->cred_gssapi
= credentials
;
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");
328 #ifdef HAVE_GSSAPI_ONLY
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
)
339 ret
= gss_acquire_cred(&minor
,
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
);
354 ((context_gssapi
) context
)->cred_gssapi
= credentials
;
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
;
371 OM_uint32 minor
, minor_ignore
;
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
;
401 case SIPE_AUTHENTICATION_TYPE_KERBEROS
:
403 name_oid
= (gss_OID
) GSS_KRB5_NT_PRINCIPAL_NAME
;
404 mech_oid
= (gss_OID
) gss_mech_krb5
;
405 #ifdef HAVE_GSSAPI_ONLY
409 SIPE_DEBUG_ERROR("sip_sec_gssapi_initialize_context invoked for invalid type %d",
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
,
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
);
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
,
444 GSS_C_NO_CHANNEL_BINDINGS
,
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
);
459 out_buff
->length
= output_token
.length
;
460 if (out_buff
->length
)
461 out_buff
->value
= g_memdup(output_token
.value
, output_token
.length
);
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
))
485 * @param message a NULL terminated string to sign
488 sip_sec_make_signature__gssapi(SipSecContext context
,
489 const gchar
*message
,
490 SipSecBuffer
*signature
)
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
,
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
);
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
);
520 * @param message a NULL terminated string to check signature of
523 sip_sec_verify_signature__gssapi(SipSecContext context
,
524 const gchar
*message
,
525 SipSecBuffer signature
)
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
,
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
);
554 sip_sec_destroy_sec_context__gssapi(SipSecContext context
)
556 context_gssapi ctx
= (context_gssapi
) context
;
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
;
576 sip_sec_context_name__gssapi(SipSecContext context
)
578 #ifndef HAVE_GSSAPI_ONLY
579 (void) context
; /* keep compiler happy */
581 if (context
->type
== SIPE_AUTHENTICATION_TYPE_NTLM
)
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 */