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 static void sip_sec_gssapi_print_gss_error0(char *func
,
71 OM_uint32 message_context
= 0;
72 gss_buffer_desc status_string
;
75 gss_display_status(&minor
,
82 SIPE_DEBUG_ERROR("sip_sec_gssapi: GSSAPI error in %s (%s): %s",
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
,
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
)
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
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
)) {
123 (void) type
; /* keep compiler happy */
125 ret
= gss_add_oid_set_member(&minor
,
126 (gss_OID
) gss_mech_krb5
,
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
,
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
);
154 /* sip-sec-mech.h API implementation for Kerberos/GSSAPI */
157 sip_sec_acquire_cred__gssapi(SipSecContext context
,
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
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");
181 mechs_set
= create_mechs_set(context
->type
);
182 if (mechs_set
== GSS_C_NO_OID_SET
)
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",
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",
214 g_strfreev(user_realm
);
216 /* Otherwise use username as is */
217 username_new
= g_strdup(username
);
219 SIPE_DEBUG_INFO("sip_sec_acquire_cred__gssapi: username '%s'",
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
,
228 (gss_OID
) GSS_C_NT_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
);
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
,
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
);
259 ((context_gssapi
) context
)->cred_gssapi
= credentials
;
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");
274 #ifdef HAVE_GSSAPI_ONLY
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
)
285 ret
= gss_acquire_cred(&minor
,
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
);
300 ((context_gssapi
) context
)->cred_gssapi
= credentials
;
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
;
317 OM_uint32 minor
, minor_ignore
;
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
,
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
,
350 (gss_OID
) GSS_KRB5_NT_PRINCIPAL_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
);
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
,
369 (gss_OID
) gss_mech_krb5
,
372 GSS_C_NO_CHANNEL_BINDINGS
,
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
);
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
;
400 * @param message a NULL terminated string to sign
403 sip_sec_make_signature__gssapi(SipSecContext context
,
404 const gchar
*message
,
405 SipSecBuffer
*signature
)
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
,
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
);
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
);
435 * @param message a NULL terminated string to check signature of
438 sip_sec_verify_signature__gssapi(SipSecContext context
,
439 const gchar
*message
,
440 SipSecBuffer signature
)
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
,
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
);
469 sip_sec_destroy_sec_context__gssapi(SipSecContext context
)
471 context_gssapi ctx
= (context_gssapi
) context
;
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
;
497 sip_sec_context_name__gssapi(SIPE_UNUSED_PARAMETER SipSecContext context
)
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 */