security: improve Kerberos w/o Single Sign-On & SSPI
[siplcs.git] / src / core / sip-sec-krb5.c
blobd9e023113a7b3190d57412f9fb25a3854bc69369
1 /**
2 * @file sip-sec-krb5.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 #include <glib.h>
26 #include <string.h>
27 #include <gssapi/gssapi.h>
28 #include <gssapi/gssapi_krb5.h>
29 #include <krb5.h>
31 #include "sipe-common.h"
32 #include "sip-sec.h"
33 #include "sip-sec-mech.h"
34 #include "sip-sec-krb5.h"
35 #include "sipe-backend.h"
37 /* Security context for Kerberos */
38 typedef struct _context_krb5 {
39 struct sip_sec_context common;
40 gss_cred_id_t cred_krb5;
41 gss_ctx_id_t ctx_krb5;
42 const gchar *domain;
43 const gchar *username;
44 const gchar *password;
45 gboolean retry_auth;
46 } *context_krb5;
48 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor);
50 static gboolean sip_sec_krb5_obtain_tgt(context_krb5 context);
52 static void sip_sec_krb5_destroy_context(context_krb5 context)
54 OM_uint32 ret;
55 OM_uint32 minor;
57 if (context->ctx_krb5 != GSS_C_NO_CONTEXT) {
58 ret = gss_delete_sec_context(&minor, &(context->ctx_krb5), GSS_C_NO_BUFFER);
59 if (GSS_ERROR(ret)) {
60 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
61 SIPE_DEBUG_ERROR("sip_sec_krb5_destroy_context: failed to delete security context (ret=%d)", (int)ret);
63 context->ctx_krb5 = GSS_C_NO_CONTEXT;
66 if (context->cred_krb5) {
67 ret = gss_release_cred(&minor, &(context->cred_krb5));
68 if (GSS_ERROR(ret)) {
69 sip_sec_krb5_print_gss_error("gss_release_cred", ret, minor);
70 SIPE_DEBUG_ERROR("sip_sec_krb5_destroy_context: failed to release credentials (ret=%d)", (int)ret);
72 context->cred_krb5 = NULL;
76 static gboolean sip_sec_krb5_acquire_credentials(context_krb5 context)
78 OM_uint32 ret;
79 OM_uint32 minor;
80 gss_cred_id_t credentials;
82 /* Acquire default user credentials */
83 ret = gss_acquire_cred(&minor,
84 GSS_C_NO_NAME,
85 GSS_C_INDEFINITE,
86 GSS_C_NO_OID_SET,
87 GSS_C_INITIATE,
88 &credentials,
89 NULL,
90 NULL);
92 if (GSS_ERROR(ret)) {
93 sip_sec_krb5_print_gss_error("gss_acquire_cred", ret, minor);
94 SIPE_DEBUG_ERROR("sip_sec_krb5_acquire_credentials: failed to acquire credentials (ret=%d)", (int)ret);
95 return(FALSE);
96 } else {
97 context->cred_krb5 = credentials;
98 return(TRUE);
102 static gboolean sip_sec_krb5_initialize_context(context_krb5 context,
103 SipSecBuffer in_buff,
104 SipSecBuffer *out_buff,
105 const char *service_name)
107 OM_uint32 ret;
108 OM_uint32 minor, minor_ignore;
109 OM_uint32 expiry;
110 gss_buffer_desc input_token;
111 gss_buffer_desc output_token;
112 gss_buffer_desc input_name_buffer;
113 gss_name_t target_name;
115 input_name_buffer.value = (void *) service_name;
116 input_name_buffer.length = strlen(service_name) + 1;
118 ret = gss_import_name(&minor,
119 &input_name_buffer,
120 (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
121 &target_name);
122 if (GSS_ERROR(ret)) {
123 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
124 SIPE_DEBUG_ERROR("sip_sec_krb5_initialize_context: failed to construct target name (ret=%d)", (int)ret);
125 return(FALSE);
128 input_token.length = in_buff.length;
129 input_token.value = in_buff.value;
131 output_token.length = 0;
132 output_token.value = NULL;
134 ret = gss_init_sec_context(&minor,
135 context->cred_krb5,
136 &(context->ctx_krb5),
137 target_name,
138 GSS_C_NO_OID,
139 GSS_C_INTEG_FLAG,
140 GSS_C_INDEFINITE,
141 GSS_C_NO_CHANNEL_BINDINGS,
142 &input_token,
143 NULL,
144 &output_token,
145 NULL,
146 &expiry);
147 gss_release_name(&minor_ignore, &target_name);
149 if (GSS_ERROR(ret)) {
150 gss_release_buffer(&minor_ignore, &output_token);
151 sip_sec_krb5_print_gss_error("gss_init_sec_context", ret, minor);
152 SIPE_DEBUG_ERROR("sip_sec_krb5_initialize_context: failed to initialize context (ret=%d)", (int)ret);
153 return(FALSE);
156 out_buff->length = output_token.length;
157 out_buff->value = g_memdup(output_token.value, output_token.length);
158 gss_release_buffer(&minor_ignore, &output_token);
160 context->common.expires = (int)expiry;
162 /* Authentication is completed */
163 context->common.is_ready = TRUE;
165 return(TRUE);
168 /* sip-sec-mech.h API implementation for Kerberos/GSS-API */
170 static sip_uint32
171 sip_sec_acquire_cred__krb5(SipSecContext context,
172 const char *domain,
173 const char *username,
174 const char *password)
176 context_krb5 ctx = (context_krb5)context;
178 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_acquire_cred__krb5: started");
180 /* remember authentication information */
181 ctx->domain = domain;
182 ctx->username = username;
183 ctx->password = password;
186 * This will be TRUE for SIP, which is the first time when we'll try
187 * to authenticate with Kerberos. We'll allow one retry with the
188 * authentication information provided by the user (see above).
190 * This will be FALSE for HTTP connections. If Kerberos authentication
191 * succeeded for Kerberos, then there is no need to retry.
193 ctx->retry_auth = !context->is_connection_based;
195 return(sip_sec_krb5_acquire_credentials(ctx) ?
196 SIP_SEC_E_OK: SIP_SEC_E_INTERNAL_ERROR);
199 static sip_uint32
200 sip_sec_init_sec_context__krb5(SipSecContext context,
201 SipSecBuffer in_buff,
202 SipSecBuffer *out_buff,
203 const char *service_name)
205 OM_uint32 ret;
206 OM_uint32 minor;
207 context_krb5 ctx = (context_krb5) context;
208 gboolean result;
210 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__krb5: started");
212 /* Delete old context first */
213 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
214 ret = gss_delete_sec_context(&minor,
215 &(ctx->ctx_krb5),
216 GSS_C_NO_BUFFER);
217 if (GSS_ERROR(ret)) {
218 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
219 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
221 ctx->ctx_krb5 = GSS_C_NO_CONTEXT;
224 result = sip_sec_krb5_initialize_context(ctx,
225 in_buff,
226 out_buff,
227 service_name);
230 * If context initialization fails retry after trying to obtaining
231 * a TGT. This will will only succeed if we have been provided with
232 * valid authentication information by the user.
234 if (!result && ctx->retry_auth) {
235 sip_sec_krb5_destroy_context(ctx);
236 result = sip_sec_krb5_obtain_tgt(ctx) &&
237 sip_sec_krb5_acquire_credentials(ctx) &&
238 sip_sec_krb5_initialize_context(ctx,
239 in_buff,
240 out_buff,
241 service_name);
244 /* Only retry once */
245 ctx->retry_auth = FALSE;
247 return(result ? SIP_SEC_E_OK: SIP_SEC_E_INTERNAL_ERROR);
251 * @param message a NULL terminated string to sign
253 static sip_uint32
254 sip_sec_make_signature__krb5(SipSecContext context,
255 const char *message,
256 SipSecBuffer *signature)
258 OM_uint32 ret;
259 OM_uint32 minor;
260 gss_buffer_desc input_message;
261 gss_buffer_desc output_token;
263 input_message.value = (void *)message;
264 input_message.length = strlen(input_message.value);
266 ret = gss_get_mic(&minor,
267 ((context_krb5)context)->ctx_krb5,
268 GSS_C_QOP_DEFAULT,
269 &input_message,
270 &output_token);
272 if (GSS_ERROR(ret)) {
273 sip_sec_krb5_print_gss_error("gss_get_mic", ret, minor);
274 SIPE_DEBUG_ERROR("sip_sec_make_signature__krb5: failed to make signature (ret=%d)", (int)ret);
275 return SIP_SEC_E_INTERNAL_ERROR;
276 } else {
277 signature->length = output_token.length;
278 signature->value = g_memdup(output_token.value,
279 output_token.length);
280 gss_release_buffer(&minor, &output_token);
281 return SIP_SEC_E_OK;
286 * @param message a NULL terminated string to check signature of
288 static sip_uint32
289 sip_sec_verify_signature__krb5(SipSecContext context,
290 const char *message,
291 SipSecBuffer signature)
293 OM_uint32 ret;
294 OM_uint32 minor;
295 gss_buffer_desc input_message;
296 gss_buffer_desc input_token;
298 input_message.value = (void *)message;
299 input_message.length = strlen(input_message.value);
301 input_token.value = signature.value;
302 input_token.length = signature.length;
304 ret = gss_verify_mic(&minor,
305 ((context_krb5)context)->ctx_krb5,
306 &input_message,
307 &input_token,
308 NULL);
310 if (GSS_ERROR(ret)) {
311 sip_sec_krb5_print_gss_error("gss_verify_mic", ret, minor);
312 SIPE_DEBUG_ERROR("sip_sec_verify_signature__krb5: failed to make signature (ret=%d)", (int)ret);
313 return SIP_SEC_E_INTERNAL_ERROR;
314 } else {
315 return SIP_SEC_E_OK;
319 static void
320 sip_sec_destroy_sec_context__krb5(SipSecContext context)
322 sip_sec_krb5_destroy_context((context_krb5) context);
323 g_free(context);
326 SipSecContext
327 sip_sec_create_context__krb5(SIPE_UNUSED_PARAMETER guint type)
329 context_krb5 context = g_malloc0(sizeof(struct _context_krb5));
330 if (!context) return(NULL);
332 context->common.acquire_cred_func = sip_sec_acquire_cred__krb5;
333 context->common.init_context_func = sip_sec_init_sec_context__krb5;
334 context->common.destroy_context_func = sip_sec_destroy_sec_context__krb5;
335 context->common.make_signature_func = sip_sec_make_signature__krb5;
336 context->common.verify_signature_func = sip_sec_verify_signature__krb5;
338 context->ctx_krb5 = GSS_C_NO_CONTEXT;
340 return((SipSecContext) context);
343 gboolean sip_sec_password__krb5(void)
345 /* Kerberos supports Single-Sign On */
346 return(FALSE);
349 static void
350 sip_sec_krb5_print_gss_error0(char *func,
351 OM_uint32 status,
352 int type)
354 OM_uint32 minor;
355 OM_uint32 message_context = 0;
356 gss_buffer_desc status_string;
358 do {
359 gss_display_status(&minor,
360 status,
361 type,
362 GSS_C_NO_OID,
363 &message_context,
364 &status_string);
366 SIPE_DEBUG_ERROR("sip_sec_krb5: GSS-API error in %s (%s): %s", func, (type == GSS_C_GSS_CODE ? "GSS" : "Mech"), (char *)status_string.value);
367 gss_release_buffer(&minor, &status_string);
368 } while (message_context != 0);
372 * Prints out errors of GSS-API function invocation
374 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor)
376 sip_sec_krb5_print_gss_error0(func, ret, GSS_C_GSS_CODE);
377 sip_sec_krb5_print_gss_error0(func, minor, GSS_C_MECH_CODE);
381 * Prints out errors of Kerberos 5 function invocation
383 static void
384 sip_sec_krb5_print_error(const char *func,
385 krb5_context context,
386 krb5_error_code ret);
389 * Obtains Kerberos TGT and stores it in default credentials cache.
390 * Similar what kinit util would do.
391 * Can be checked with klist util.
393 * kinit would require the following name:
394 * alice@ATLANTA.LOCAL
395 * where 'alice' is a username and
396 * 'ATLANTA.LOCAL' is a realm (domain) .
398 static gboolean sip_sec_krb5_obtain_tgt(context_krb5 ctx)
400 krb5_context context = NULL;
401 krb5_error_code ret;
402 char *realm;
403 char *username;
404 gchar **user_realm;
406 if (!ctx->username && !ctx->password) {
407 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_krb5_obtain_tgt: no valid authentication information provided");
408 return(FALSE);
411 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: started");
413 user_realm = g_strsplit(ctx->username, "@", 2);
414 if (user_realm[1]) {
415 /* "user@domain" -> use domain as realm */
416 realm = g_ascii_strup(user_realm[1], -1);
417 username = g_strdup(user_realm[0]);
418 } else {
419 /* use provided domain as realm */
420 realm = g_ascii_strup(ctx->domain ? ctx->domain : "", -1);
421 username = g_strdup(ctx->username);
423 g_strfreev(user_realm);
425 /* Obtait TGT */
426 ret = krb5_init_context(&context);
427 if (ret) {
428 sip_sec_krb5_print_error("krb5_init_context", context, ret);
429 } else {
430 krb5_principal principal = NULL;
432 ret = krb5_build_principal(context, &principal, strlen(realm), realm, username, NULL);
433 if (ret) {
434 sip_sec_krb5_print_error("krb5_build_principal", context, ret);
435 } else {
436 krb5_creds credentials;
438 memset(&credentials, 0, sizeof(krb5_creds));
440 ret = krb5_get_init_creds_password(context, &credentials, principal, (char *)ctx->password, NULL, NULL, 0, NULL, NULL);
441 if (ret) {
442 sip_sec_krb5_print_error("krb5_get_init_creds_password", context, ret);
443 } else {
444 krb5_ccache ccdef = NULL;
446 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT obtained");
448 /* Store TGT in default credential cache */
449 ret = krb5_cc_default(context, &ccdef);
450 if (ret) {
451 sip_sec_krb5_print_error("krb5_cc_default", context, ret);
452 } else {
453 /* First try without initializing */
454 ret = krb5_cc_store_cred(context, ccdef, &credentials);
455 if (ret) {
456 ret = krb5_cc_initialize(context, ccdef, credentials.client);
457 if (ret) {
458 sip_sec_krb5_print_error("krb5_cc_initialize", context, ret);
459 } else {
460 /* Second try after initializing the default credential cache */
461 ret = krb5_cc_store_cred(context, ccdef, &credentials);
462 if (ret) {
463 sip_sec_krb5_print_error("krb5_cc_store_cred", context, ret);
464 } else {
465 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT stored in default credentials cache");
469 krb5_cc_close(context, ccdef);
471 krb5_free_cred_contents(context, &credentials);
473 krb5_free_principal(context, principal);
475 krb5_free_context(context);
477 g_free(username);
478 g_free(realm);
480 return(!ret);
483 static void
484 sip_sec_krb5_print_error(const char *func,
485 krb5_context context,
486 krb5_error_code ret)
488 const char *error_message = krb5_get_error_message(context, ret);
489 SIPE_DEBUG_ERROR("Kerberos 5 ERROR in %s: %s", func, error_message);
490 krb5_free_error_message(context, error_message);
494 Local Variables:
495 mode: c
496 c-file-style: "bsd"
497 indent-tabs-mode: t
498 tab-width: 8
499 End: